2013年11月8日

Kompiraのジョブフローで簡単なステートマシンを実装してみる

はじめに

Kompira のジョブフロー(Kompira独自のスクリプト言語)では外部からのメッセージ受信やデータベースに保存されるオブジェクトのデータ書き換えができるので、これらの機能を使って簡単なステートマシンを実装してみたいと思います。

ちなみに、ステートマシンとは、入力された条件と現在の状態によって次の状態が決まる論理的な回路のことを言い、半導体の設計や、通信プロトコル、言語の文法のモデル化など様々な分野で応用されています。

ここでは、以下のようなステートマシンを考えてみます。


これはネットワーク機器やサーバを定期的に監視して、異常を検出すれば "ERROR" を、復旧を検出すれば "OK" を通知するようなシステムとの連携を想定しています。こうした監視では、一度異常を検出してもすぐに復旧することもあり、そうした場合は「注意」だけで何もしないようにします。連続した2回目の異常検出ではじめて「障害」として障害発生メールを送信し、また、そこから復旧したときは障害復旧メールを送信することにします。

方針

Kompiraでステートマシンを実装する方針として、今回は次のように考えてみます。
  • オブジェクトに保存するデータで「状態」を表現する
  • チャネルから受信したメッセージを「入力条件」として扱う
  • ジョブフローでチャネルからメッセージを受信し、その内容に応じてオブジェクトのデータを書き換えることで「状態の遷移」を行なう
また、各状態ごとに入力条件に応じた動作(アクション)の実行と、次に遷移する状態を示すようオブジェクトを書き換える処理が必要になります。こうした処理自体もジョブフローで状態ごとに記述することになりますので、ジョブフローを状態ごとに作成しておいて、「現在の状態」はそれらのジョブフローのうちの1つへの参照をオブジェクトに保持することで表現してみます。
  • 状態ごとにジョブフローを作成して、入力条件に応じたアクションと状態遷移を実装する。このジョブフローを状態ジョブフローと呼ぶ。
  • オブジェクトには状態ジョブフローへの参照を「現在の状態」として保持する。このオブジェクトを状態オブジェクトと呼ぶ。
加えて、チャネルがメッセージを受信したときに、状態オブジェクトが参照する状態ジョブフローを実行する仕組みが必要になります。この処理は入力ジョブフローとして実装することにします。

実装

オブジェクトやジョブフローは任意の場所に作成することが出来ますが、今回はすべて同じディレクトリ /samples/ステートマシン の中で行なうことにします。

型オブジェクトの作成

状態オブジェクトを作成するために、その型となる型オブジェクトを作成する必要があります。状態オブジェクトでは「ジョブフローへの参照」をデータとして保持するため、フィールド種別を Object、フィールド修飾子を /system/types/Jobflow としたフィールドを用意します。

ここでは以下のように state_jobflow という名前のフィールドをひとつだけ持つ型オブジェクトを「状態オブジェクト型」という名前で作成しました。


状態オブジェクトの作成

上で作成した型オブジェクトを使って「状態」という名前で状態オブジェクトを作成します。ここでは、作成時に状態ジョブフローを選択しておく必要はありません。オブジェクト型で何も参照していないときは、以下のように "no object" と表示されます。


ちなみに、オブジェクトへの参照を持っている時は、以下のように参照先のオブジェクトの名前とパスが表示されます。


入力ジョブフローの作成

チャネルにメッセージが届くと、状態オブジェクトが参照する状態ジョブフローを呼び出す、という処理を繰り返すジョブフローを作成します。チャネルはKompiraが標準で用意している /system/channels/Alert を利用します。

ここでは「イベント受信」という名前で以下のような入力ジョブフローを作成しました。


ポイントとなる箇所を簡単に説明します。

  • 1行目で状態オブジェクトを初期化しています。ここではステートマシンの初期状態である「正常」にするために、state_jobflow フィールドを(同じディレクトリにある) "正常" という名前のジョブフローへの参照で初期化(書き換え)しています。
  • 2~11行目の { while true | ... } というブロックは、... の部分のジョブを条件式が真のあいだ繰り返す、という構文です。ここで条件式は true なので、この while ブロックは永遠に繰り返す、すなわち無限ループとなります。
  • 3行目、チャネル /system/channels/Alert からメッセージを受信します。< と > で囲まれたジョブをイベントジョブと呼び、ジョブフローの実行はイベントが発生するまで待つことになります。チャネルに対するイベントジョブは、メッセージが到着するまで待つことになります。
  • 4行目、チャネルが受信したメッセージを msg という変数に代入しています。ジョブフローでは直前のジョブの実行結果は $RESULT という特殊変数に保持されていますので、3行目のイベントジョブの実行結果として受信したメッセージを受け取っています。
  • 5行目、状態オブジェクトが参照している状態ジョブフローを jf という変数に代入しています。
  • 7行目、ステートマシンの現在の状態に対応する状態ジョブフローを(jf という変数を介して)実行します。状態ジョブフローには受信したメッセージ msg を引数として渡しています。
  • 8行目、状態ジョブフローを実行した結果、状態遷移が起きた様子を状態ジョブフローの名前の変遷で表示します。


状態ジョブフローの作成

状態ジョブフローは状態ごとに作成しますが、基本的には同じ構造を持ったジョブフローとして実装します。メッセージを入力パラメータとして受け取って、その入力条件にしたがってアクションを実行し、状態遷移のために状態オブジェクトの書き換えを行います。

正常状態

「正常」に対応する状態ジョブフローでは、入力条件が "OK" のときは何もせず、"ERROR" のとき「注意」状態に遷移する、という処理を実装します。



この実装でのポイントは以下のとおりです。
  • 1行目の |msg| はこのジョブフローが msg という名前で引数を受け取ることを示しています。
  • 2行目~9行目の { case msg.event | ... } というブロックは、引数で受け取った msg に含まれる event というフィールドの値(文字列)によって処理を分岐する、という構文です。
  • 3~4行目、"OK" の場合は "何もしません" というメッセージをコンソールに表示します
  • 5~6行目、"ERROR" の場合は状態オブジェクトが参照する状態ジョブフロー、すなわち(このジョブフローと同じディレクトリにある) "状態" という名前のオブジェクトの state_jobflow フィールドを、(同じディレクトリにある) "注意" という名前のジョブフローへの参照に書き換えています。
  • 7~8行目、"OK" でも "ERROR" でもない値の場合は未定義としてジョブフローを異常終了します。

注意状態

「注意」に対応する状態ジョブフローでは、入力条件が "OK" のときは「正常」状態に遷移し、"ERROR" のときは障害発生メールを送信して「異常」状態に遷移する、という処理を実装します。



この実装でのポイントは以下のとおりです(共通なものは省略します)。

  • 7行目、"ERROR" のとき mailto 組み込みジョブで障害発生メールを送信しています。

障害状態

「障害」に対応する状態ジョブフローでは、入力条件が "OK" のとき障害復旧メールを送信して「正常」状態に遷移し、"ERROR" のときは何もしない、という処理を実装します。



正常、注意の状態ジョブフローと同様の構造であることが分かると思います。

実装オブジェクト一覧

以上ですべての実装が完了しました。ディレクトリ /samples/ステートマシン の画面で、実装したオブジェクトの一覧が以下のように確認できます。



動作確認

では実際にジョブフローを動かして、ステートマシンの動作を確認してみます。

入力ジョブフローの実行

今回の実装では入力ジョブフローが入力条件の受付からアクションの実行まで、ステートマシンの駆動に関するすべての処理を行ないます。入力ジョブフローとして作成した「イベント受信」の画面で 「▶実行」 ボタンを押してジョブフローを実行します。


ジョブフローを実行するとすぐにチャネルのメッセージ受信になりますので、プロセス情報のステータスは「待ち」となっているはずです。

メッセージの通知方法

Kompiraに付属している kompira_sendevt コマンドを利用して Kompira にメッセージを通知します。メッセージは key=value の辞書型として通知されます。今回の実装では event というキーで "OK""ERROR" という値を入力条件として通知する必要がありますので、それぞれ次のようにコマンドを実行してください。

"OK" を通知する場合

$ /opt/kompira/bin/kompira_sendevt event=OK

"ERROR" を通知する場合

$ /opt/kompira/bin/kompira_sendevt event=ERROR

状態遷移の確認

入力ジョブフローを実行した状態で "OK""ERROR" を通知して、ステートマシンが期待通りにアクションを実行し状態を遷移させるか確認してみます。

正常→注意→障害→正常

正常状態から "ERROR", "ERROR", "OK" とイベントを通知した結果が以下のとおりです。


ステートマシンは想定通りに、正常→注意→障害→正常へと状態遷移できていることが分かります。また、注意状態から障害状態に遷移するときには障害発生メールが、障害状態から正常状態に遷移するときには障害復旧メールが、以下のとおり送信されていました。

障害発生メール



障害復旧メール



正常→注意→正常

正常状態から "ERROR", "OK" とイベントを通知した結果が以下のとおりです。


想定通り、正常→注意→正常へと状態遷移できています。

正常→正常

正常状態から "OK" のイベントを通知した結果が以下のとおりです。


想定どおり、正常→正常のまま状態が遷移していないことが確認できます。

(正常→注意→)障害→障害

障害状態から "ERROR" のイベントを通知した結果が以下のとおりです。


想定どおり、障害→障害のまま状態が遷移していないことが確認できます。

まとめ

今回はKompiraで簡単なステートマシンを作ってみました。状態を保持するオブジェクトと状態ごとのジョブフロー、そしてステートマシンを駆動するジョブフローで実装してみましたが、それぞれシンプルな構造でできたのではないかと思います。

また実際に動作させてみて、ステートマシンが期待通りにアクションの実行と状態の遷移ができていることが確認できました。

もう少し実用的なステートマシンとするにはさらに各状態への入場や出場に対応するアクションの実行や、タイムアウトへの対応などが必要になるでしょう。今回実装したジョブフローをベースに機能拡張することで実現できますので、次の機会に紹介したいと思います。


---
フィックスポイントでは Kompira の訪問デモや無料評価版の提供も行っております。
http://www.kompira.jp/contact/ からお気軽にお問い合わせください。