この章では、Cluster Reconfiguration Notification Protocol (CRNP) について説明します。CRNP を使用することで、フェイルオーバー用のアプリケーションや拡張性のあるアプリケーションを「クラスタ対応」として設定できます。具体的には、Sun Cluster 再構成イベントにアプリケーションを登録し、それらのイベントの後続の非同期通知を受け取ることができます。イベント通知の受信登録が可能なのは、クラスタの内部で動作するデータサービスと、クラスタの外部で動作するアプリケーションです。イベントは、クラスタ内のメンバーシップに変化があった場合と、リソースグループまたはリソースの状態に変化があった場合に生成されます。
SUNW.Event のリソース型実装は、 高可用性を備えた Sun Cluster の CRNP サービスを提供します。このリソース型の実装については、SUNW.Event(5) のマニュアルページで詳しく説明しています。
この章の内容は次のとおりです。
CRNP は、標準の 7 層 OSI (Open System Interconnect) プロトコルスタックにおけるアプリケーション層、プレゼンテーション層、およびセッション層を定義します。トランスポート層は TCP でなければならず、ネットワーク層は IP でなければなりません。CRNP は、データリンク層および物理層とは無関係です。CRNP 内で交換されるアプリケーション層メッセージはすべて、XML 1.0 をベースとしたものです。
CRNP は、クラスタ再構成イベント生成、クラスタへの配信、それらのイベントを要求しているクライアントへの送信を行うメカニズムとデーモンを提供します。
クライアントとの通信を行うのは、cl_apid デーモンです。クラスタ再構成イベントの生成は、Sun Cluster Resource Group Manager (RGM) によって行われます。このデーモンは、syseventd を使用して各ローカルノードにイベントを転送します。cl_apid デーモンは、TCP/IP 上で XML (Extensible Markup Language) を使用して要求クライアントとの通信を行います。
次の図は、CRNP コンポーネント間のイベントの流れを簡単に示したものです。この図では、一方のクライアントはクラスタノード 2 で動作し、他方のクライアントはクラスタに属していないコンピュータ上で動作しています。
クライアントは、サーバーへ登録メッセージ (SC_CALLBACK_RG) を送信することによって通信を開始します。この登録メッセージは、通知を受信したいイベントタイプと、イベントの配信先として使用できるポートを指定するものです。登録用接続のソース IP と指定ポートから、コールバックアドレスが構成されます。
クライアントが配信を希望しているイベントがクラスタ内で生成されるたびに、サーバーはこのコールバックアドレス (IP とポート) を持つクライアントと通信を行い、イベント (SC_EVENT) をクライアントに配信します。サーバーには、そのクラスタ内で稼動している高可用マシンが使用されます。サーバーは、クラスタの再起動後も維持されるストレージにクライアントの登録情報を格納します。
登録解除を行う場合、クライアントはサーバーに登録メッセージ (REMOVE_CLIENT メッセージが入った SC_CALLBACK_RG) を送信します。サーバーから SC_REPLY メッセージを受け取ったあとで、クライアントは接続を閉じることができます。
CRNP は、3 種類の XML ベースのメッセージを使用します。次の表に、これらのメッセージの使用法を示します。これらのメッセージタイプの内容と使用法の詳細は、この章で後述します。
CRNP メッセージのタイプ |
説明 |
---|---|
SC_CALLBACK_REG |
このメッセージには、4 つのフォーム、ADD_CLIENT、REMOVE_CLIENT 、ADD_EVENTS、および REMOVE_EVENTS を指定できます。 これらの各フォームには、次の情報が含まれます。
ADD_CLIENT、ADD_EVENTS、および REMOVE_EVENTS には、バインドされていないイベントタイプリストも含まれます。 これらの各フォームには、次の情報が含まれます。
イベントクラスとイベントサブクラスにより一意の「イベントタイプ」 が定義されます。SC_CALLBACK_REG のクラスを生成する DID (ドキュメントタイプ定義) は、SC_CALLBACK_REG です。この DTD の詳細は、付録 F 「CRNP のドキュメントタイプ定義」を参照してください。 |
SC_REPLY |
このメッセージには次の情報が含まれます。
SC_REPLY のクラスを生成する DTD は SC_REPLY です。この DTD の詳細は、付録 F 「CRNP のドキュメントタイプ定義」を参照してください。 |
SC_EVENT |
このメッセージには次の情報が含まれます。
SC_EVENT 内の値はタイプとしては分類されていません。SC_EVENT のクラスを生成する DTD は SC_EVENT です。この DTD の詳細は、付録 F 「CRNP のドキュメントタイプ定義」を参照してください。 |
ここでは、クラスタ管理者によるサーバーの設定方法、クライアントを識別する方法、情報をアプリケーション層とセッション層に送信する方法、およびエラー状況について説明します。
システム管理者は、汎用 IP アドレス (クラスタ内の特定のマシン専用でない IP アドレス) とポート番号を使用してサーバーを構成し、クライアントとなるマシンにこのネットワークアドレスを公開する必要があります。CRNP では、クライアントがこのサーバー名をどのように取得するかは定義されていません。管理者は、ネーミングサービスを使用することも (この場合、クライアントは動的にサーバーのネットワークアドレスを検出できる)、ネットワーク名を構成ファイルに追加してクライアントに読み取らせることもできます。サーバーは、クラスタ内でフェイルオーバーリソースタイプとして動作します。
各クライアントは、そのコールバックアドレス (IP アドレスとポート番号) で識別されます。ポートは SC_CALLBACK_REG メッセージで指定され、IP アドレスは登録用の TCP 接続から取得されます。CRNP は、同じコールバックアドレスを持つ後続の SC_CALLBACK_REG メッセージは同じクライアントから送信されたと想定します。これは、メッセージの送信元であるソースポートが異なる場合でも同様です。
クライアントは、サーバーの IP アドレスとポート番号への TCP 接続を開くことによって、登録を開始します。TCP 接続が確立され書き込みの用意ができたところで、クライアントはその登録メッセージを送信する必要があります。この登録メッセージは正しい書式の SC_CALLBACK_REG メッセージでなければならず、メッセージの前後に余分なバイトを含めることはできません。
バイトがすべてストリームに書き込まれたあと、クライアントはサーバーから応答を受け取ることができるように接続をオープン状態に保つ必要があります。クライアントが不正な書式のメッセージを送信した場合、サーバーはそのクライアントを登録せず、クライアントに対してエラー応答を送信します。しかし、サーバーが応答を送信する前にクライアントがソケット接続を閉じた場合、サーバーはそのクライアントを正常なクライアントとして登録します。
クライアントは、いつでもサーバーと通信を行うことができます。サーバーと通信を行うごとに、クライアントは SC_CALLBACK_REG メッセージを送信する必要があります。書式が不正なメッセージ、順不同のメッセージ、無効なメッセージなどを受け取った場合、サーバーはクライアントにエラー応答を送信します。
クライアントは、それ自体が ADD_CLIENT メッセージを送信するまでは ADD_EVENTS、REMOVE_EVENTS 、REMOVE_CLIENT メッセージを送信できません。クライアントは、それ自体が ADD_CLIENT メッセージを送信しないかぎり REMOVE_CLIENT メッセージを送信できません。
クライアントが ADD_CLIENT メッセージを送信したが、そのクライアントがすでに登録されていたという場合は、サーバーがこのメッセージを黙認することがあります。このような場合、サーバーは報告なしに古いクライアント登録を削除し、2 つめの ADD_CLIENT メッセージに指定された新しいクライアント登録に置き換えます。
通常、クライアントはその起動時に ADD_CLIENT メッセージを送信することによって、サーバーに一度だけ登録を行います。また、登録の解除もサーバーに REMOVE_CLIENT メッセージを送信して一度だけ行います。しかし、CRNP はクライアントが必要に応じてイベントタイプリストを動的に変更できるだけの柔軟性を備えています。
ADD_CLIENT、REMOVE_CLIENT、 ADD_EVENTS、および REMOVE_EVENTS メッセージには、それぞれイベントリストが含まれます。次の表は、CRNP が受け付けるイベントタイプを、必要となる名前と値のペアと共に示して説明しています。
クライアントが以下の作業のどちらか一方を行うと、サーバーはクライアントに通知することなくこれらのメッセージを無視します。
まだ登録が行われていないイベントタイプを1 つ以上指定する REMOVE_EVENTS メッセージを送信する
同じイベントタイプを 2 度登録する
クラスとサブクラス |
名前と値のペア |
説明 |
---|---|---|
EC_Cluster ESC_cluster_membership |
必須: なし (省略可能) なし |
クラスタメンバーシップの変更 (ノードの停止またはクラスタへの結合) に関連するあらゆるイベントに登録する |
EC_Cluster ESC_cluster_rg_state |
次の条件で 1 つ必要: rg_name 値のタイプ: 文字列型 (省略可能) なし |
rg_name (リソースグループ名) のあらゆる状態変更イベントに登録する |
EC_Cluster ESC_cluster_r_state |
次の条件で 1 つ必要: r_name 値のタイプ: 文字列型 (省略可能) なし |
r_name (リソース名) のあらゆる状態変更イベントに登録する |
EC_Cluster なし |
必須: なし (省略可能) なし |
あらゆる Sun Cluster イベントに登録する |
登録を処理したあと、登録要求を受信したサーバーは、クライアントが開いた TCP 接続上で SC_REPLY メッセージを送信します。このあとサーバーは接続を閉じます。クライアントは、サーバーから SC_REPLY メッセージを受信するまで TCP 接続をオープン状態に保つ必要があります。
クライアントは次のような作業を行います。
サーバーに対して TCP 接続を開きます。
接続が「writable (書き込み可能)」になるまで待機します。
SC_CALLBACK_REG メッセージ (このメッセージには ADD_CLIENT メッセージが入っている) を送信します。
サーバーから SC_REPLY メッセージが到着するのを待機します。
サーバーから SC_REPLY メッセージを受け取ります。
サーバーが接続を閉じたことを示すインジケータを受信します (ソケットから 0 バイトを読み取る)。
接続を閉じます。
その後クライアントは以下の作業を行います。
サーバーに対して TCP 接続を開きます。
接続が「writable (書き込み可能)」になるまで待機します。
SC_CALLBACK_REG メッセージ (このメッセージには REMOVE_CLIENT メッセージが入っている) を送信します。
サーバーから SC_REPLY メッセージが到着するのを待機します。
サーバーから SC_REPLY メッセージを受け取ります。
サーバーが接続を閉じたことを示すインジケータを受信します (ソケットから 0 バイトを読み取る)。
接続を閉じます。
クライアントから SC_CALLBACK_REG メッセージを受け取るたびに、サーバーは同じ接続に SC_REPLY メッセージを送信します。このメッセージは、処理が正常に完了したか失敗したかを示すものです。SC_REPLY メッセージの XML ドキュメントタイプ定義とこのメッセージ内で示されるエラーメッセージについては、「SC_REPLY XML DTD」を参照してください。
SC_REPLY メッセージでは、処理が成功したか失敗したかが示されます。このメッセージには、CRNP メッセージのバージョン、ステータスコード、およびステータスコードの詳細を説明したステータスメッセージが含まれます。次の表は、ステータスコードの値を説明しています。
ステータスコード |
説明 |
---|---|
OK |
メッセージは正常に処理されました。 |
RETRY |
一時的なエラーのためにクライアントの登録はサーバーに拒否されました。クライアントは別の引数を使用して登録をもう一度試す必要があります。 |
LOW_RESOURCE |
クライアントのリソースが少ないため、クライアントはあとでもう一度試す必要があります。クラスタのクラスタ管理者は、クラスタのリソースを増やすこともできます。 |
SYSTEM_ERROR |
重大な問題が発生しました。クラスタのクラスタ管理者に連絡してください。 |
FAIL |
承認の失敗などの問題が発生し、登録が失敗しました。 |
MALFORMED |
XML 要求の形式が正しくないため解析が失敗しました。 |
INVALID |
XML 要求が無効です (XML 仕様を満たしていない)。 |
VERSION_TOO_HIGH |
メッセージのバージョンが高すぎて、メッセージを正常に処理できませんでした。 |
VERSION_TOO_LOW |
メッセージのバージョンが低すぎて、メッセージを正常に処理できませんでした。 |
通常、SC_CALLBACK_REG メッセージを送信するクライアントは登録の成功または失敗を知らせる応答を受け取ります。
しかし、クライアントが登録を試みる際にサーバーからの SC_REPLY メッセージの送信を妨げるエラーが発生することがあります。この場合、エラーが発生する前に登録が正常に完了することも、登録が失敗することも、あるいは登録処理が行われないまま終了することもあります。
サーバーはクラスタ内においてフェイルオーバー (高可用) サーバーとして機能する必要があるため、このエラー状況はサービスの終了を意味するわけではありません。実際、サーバーは新しく登録されたクライアントに対してすぐにイベント送信を開始できます。
これらの状況を修復するには、クライアントは次の作業を行う必要があります。
SC_REPLY メッセージを待機している登録用接続にアプリケーションレベルのタイムアウトを強制します(このあと、登録を再試行する必要がある)。
イベントコールバックの登録を行う前に、イベント配信用のコールバック IP アドレスとポート番号で待機を開始します。クライアントは、登録確認メッセージとイベント配信を同時に待機することになります。確認メッセージを受信する前にイベントを受信し始めた場合は、クライアントはそのまま登録接続を閉じる必要があります。
イベントがクラスタの中で生成されると、CRNP サーバーはそれらのタイプのイベントを要求した各クライアントに、イベントを配信します。この配信では、クライアントのコールバックアドレスに SC_EVENT メッセージが送信されます。各イベントの配信は、新たな TCP 接続で行われます。
クライアントが ADD_CLIENT メッセージまたは ADD_EVENT メッセージが入った SC_CALLBACK_REG メッセージを通してイベントタイプの配信登録を行うと、サーバーはただちにクライアントに対してそのタイプの最新イベントを送信します。クライアントは、後続のイベントを送信するシステムの現在の状態を判断できます。
クライアントに対して TCP 接続を開始する際に、サーバーはその接続に SC_EVENT メッセージを 1 つだけ送信します。サーバーは全二重通信を閉じます。
クライアントは次のような作業を行います。
サーバーが TCP 接続を開始するのを待機します。
サーバーからの着信接続を受け入れます。
サーバーから SC_EVENT メッセージが到着するのを待機します。
サーバーからの SC_EVENT メッセージを読み取ります。
サーバーが接続を閉じたことを示すインジケータを受信します (ソケットから 0 バイトを読み取る)。
接続を閉じます。
すべてのクライアントが登録を終了した時点で、それらのクライアントはイベント配信のための着信接続を受け入れるために常にコールバックアドレス (IP アドレスとポート番号) で待機する必要があります。
クライアントとの通信に失敗してイベントを配信できなかった場合、サーバーはユーザーが設定してある回数と周期に従ってイベントの配信を繰り返し試みます。それらの試行がすべて失敗に終わった場合、そのクライアントはサーバーのクライアントリストから削除されます。イベントをそれ以上受け取るためには、クライアントは ADD_CLIENT メッセージが入った SC_CALLBACK_REG メッセージを別途送信して登録をもう一度行う必要があります。
クラスタ内では、クライアントごとに配信順序を守るという方法で、総合的にイベント生成を順序付けます。たとえば、クラスタ内でイベントA の生成後イベント B が生成された場合、クライアント X はイベント A を受け取ってからイベント B を受け取ります。しかし、全クライアントに対するイベント配信の全体的な順序付けは保持されません。 つまり、クライアント Y はクライアント X がイベント A を受け取る前にイベント A と B の両方を受け取る可能性があります。この方法では、低速のクライアントのために全クライアントへの配信が停滞するということがありません。
サーバーが配信するイベントはすべて (サブクラス用の最初のイベントとサーバーエラーのあとに発生するイベントを除く)、クラスタが生成する実際のイベントに応答して発生します。ただし、クラスタで生成されるイベントを見逃すようなエラーが発生する場合は、サーバーは各イベントタイプの現在のシステム状態を示すイベントをそれらのイベントタイプごとに生成します。各イベントは、そのイベントタイプの配信登録を行なったクライアントに送信されます。
イベント配信は、「1 回以上」というセマンティクスに従って行われます。つまり、サーバーは 1 台のクライアントに対して同じイベントを複数回送信できます。この許可は、サーバーが一時的に停止して復帰した際に、クライアントが最新の情報を受け取ったかどうかをサーバーが判断できないという場合に不可欠なものです。
SC_EVENT メッセージには、クラスタの中で生成され、SC_EVENT XML メッセージの形式に合わせて変換された、実際のメッセージが含まれています。次の表は、CRNP が配信するイベントタイプ (名前と値のペア、パブリッシャー、ベンダーなど) を説明したものです。
state_list の配列要素は、node_list の配列要素と同期をとるように配置されます。つまり、node_list 配列内で最初に出現しているノードの状態は、state_list 配列の先頭に示されます。
ev_ で始まるほかの名前や、それらの名前に関連した値が存在する場合がありますが、クライアントによる使用を意図したものではありません。
クラスとサブクラス |
パブリッシャーとベンダー |
名前と値のペア |
---|---|---|
EC_Cluster ESC_cluster_membership |
パブリッシャー: rgm ベンダー: SUNW |
名前: node_list 値のタイプ: 文字配列 名前: state_list state_list には、ASCII 形式の数字だけが入っています。各数字は、クラスタにおけるそのノードの現在のインカーネーション番号を示します。この番号が前のメッセージで受信した番号と同じである場合、ノードとクラスタの関係は変化していません (離脱、結合、または再結合が行われていない)。インカーネーション番号が –1 の場合、ノードはクラスタのメンバーではありません。インカーネーション番号が負の値以外の数字である場合、ノードはクラスタのメンバーです。 値のタイプ: 文字配列 |
EC_Cluster ESC_cluster_rg_state |
パブリッシャー: rgm ベンダー: SUNW |
名前: rg_name 値のタイプ: 文字列型 名前: node_list 値のタイプ: 文字配列 名前: state_list state_list には、リソースグループの状態を示す文字列が入っています。有効な値は、scha_cmds(1HA) コマンドで取得できる値です。 値のタイプ: 文字配列 |
EC_Cluster ESC_cluster_r_state |
パブリッシャー: rgm ベンダー: SUNW |
名前: r_name 値のタイプ: 文字列型 名前: node_list 値のタイプ: 文字配列 名前: state_list state_list には、リソースの状態を示す文字列が入っています。有効な値は、scha_cmds(1HA) コマンドで取得できる値です。 値のタイプ: 文字配列 |
サーバーは、TCP ラッパーの形式を使用してクライアントを認証します。この場合、登録メッセージのソース IP アドレス (これはイベントの配信先であるコールバック IP アドレスとしても使用される) がサーバー側の「許可されたユーザー」リストに含まれていなければなりません。ソース IP アドレスと登録メッセージが「拒否されたクライアント」リストに存在してはなりません。ソース IP アドレスと登録メッセージがリスト中に存在しない場合、サーバーは要求を拒否し、クライアントに対してエラー応答を返します。
サーバーが SC_CALLBACK_REG ADD_CLIENT メッセージを受け取る場合、そのクライアントの後続の SC_CALLBACK_REG メッセージには最初のメッセージ内のものと同じソース IP アドレスが含まれていなければなりません。
この条件を満たさない SC_CALLBACK_REG を受信した場合、CRNP サーバーは次のどちらかを実行します。
要求を無視し、クライアントにエラー応答を送信する
その要求が新しいクライアントからのものであると想定する (SC_CALLBACK_REG メッセージの内容にもとづいて判断)
このセキュリティーメカニズムは、正規クライアントの登録の解除を試みるサービス拒否攻撃の防止に役立ちます。
クライアントも、同様のサーバー認証を行う必要があります。クライアントは、それ自体が使用した登録 IP アドレスおよびポート番号と同じソース IP アドレスおよびポート番号を持つサーバーからのイベント配信を受け入れるだけです。
CRNP サービスのクライアントはクラスタを保護するファイアウォール内に配置されるのが一般的なため、CRNP にセキュリティーメカニズムは提供されていません。
以下の例は、CRNP を使用する CrnpClient というシンプルな Java アプリケーションを作成する方法を示しています。このアプリケーションでは、クラスタ上の CRNP サーバーへのイベントコールバックの登録、イベントコールバックの待機、イベントの処理 (内容の出力) を行い、終了前にイベントコールバック要求の登録解除を行います。終了前にイベントコールバック要求の登録解除を行います。
この例を参照する場合は、以下の点に注意してください。
このアプリケーション例では、JAXP (Java API for XML Processing) を使用して XML の生成と解析を行なっています。この例は JAXP の使用方法を説明したものではありません。JAXP の詳細は、http://java.sun.com/xml/jaxp/index.html で説明しています。
この例は、付録 G 「CrnpClient.java アプリケーション」 に示されている完全なアプリケーションコードを断片的に示したものです。この章の例は個々の概念を効果的に示すことをねらっており、付録 G 「CrnpClient.java アプリケーション」 に示されている完全なアプリケーションコードと多少異なります。
また、簡潔に示すため、この章の例ではコード例からコメントを除いてあります。付録 G 「CrnpClient.java アプリケーション」 に示されている完全なアプリケーションコードにはコメントが含まれています。
この例に示しているアプリケーションは終了するだけでほとんどのエラー状況に対応できるものですが、ユーザーが実際に使用するアプリケーションではエラーを徹底的に処理する必要があります。
JAXP と、正しいバージョンの Java コンパイラおよび Virtual Machine をダウンロードし、インストールを行います。
作業手順は、http://java.sun.com/xml/jaxp/index.htmlに示されています。
この例は、バージョン 1.3.1 以降の Java を必要とします。
ソースファイルが置かれているディレクトリから、次のように入力します。
% javac -classpath jaxp-root/dom.jar:jaxp-rootjaxp-api. \ jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \ .jar:jaxp-root/xsltc.jar -sourcepath . source-filename.java |
上記コマンドの jaxp-root には、JAXP jar ファイルが置かれているディレクトリの絶対パスまたは相対パスを指定してください。source-filename には、Java ソースファイルの名前を指定してください。
コンパイルコマンド行の classpath は、コンパイラで JAXP クラスを見つけるための指定です。
アプリケーションの実行時に、アプリケーションが正しい JAXP クラスファイルを読み込むことができるように classpath を指定します (classpath の最初のパスは現在のディレクトリ)。
% java -cp .:jaxp-root/dom.jar:jaxp-rootjaxp-api. \ jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \ .jar:jaxp-root/xsltc.jar source-filename arguments |
以上で環境の構成が終了し、アプリケーションの開発を行える状況となります。
アプリケーション例のこの段階では、コマンド行引数を解析して CrnpClient オブジェクトの構築を行うメインメソッドを使用し、CrnpClient という基本的なクラスを作成します。このオブジェクトは、コマンド行引数をこのクラスに渡し、ユーザーがアプリケーションを終了するのを待って CrnpClient で shutdown を呼び出し、その後終了します。
CrnpClient クラスのコンストラクタは、以下の作業を実行する必要があります。
オブジェクトを処理する XML を設定する
イベントコールバックを待機するスレッドを作成する
CRNP サーバーと通信し、イベントコールバックを受け取る登録をする
上記のロジックを実装する Java コードを作成します。
次の例は、CrnpClient クラスのスケルトンコードを示しています。コンストラクタ内で参照される 4 つのヘルパーメソッドと停止メソッドの実装は、この章で後述します。ここでは、ユーザーが必要とするパッケージをすべてインポートするコードを示しています。
import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import org.w3c.dom.*; import java.net.*; import java.io.*; import java.util.*; class CrnpClient { public static void main(String []args) { InetAddress regIp = null; int regPort = 0, localPort = 0; try { regIp = InetAddress.getByName(args[0]); regPort = (new Integer(args[1])).intValue(); localPort = (new Integer(args[2])).intValue(); } catch (UnknownHostException e) { System.out.println(e); System.exit(1); } CrnpClient client = new CrnpClient(regIp, regPort, localPort, args); System.out.println("Hit return to terminate demo..."); try { System.in.read(); } catch (IOException e) { System.out.println(e.toString()); } client.shutdown(); System.exit(0); } public CrnpClient(InetAddress regIpIn, int regPortIn, int localPortIn, String []clArgs) { try { regIp = regIpIn; regPort = regPortIn; localPort = localPortIn; regs = clArgs; setupXmlProcessing(); createEvtRecepThr(); registerCallbacks(); } catch (Exception e) { System.out.println(e.toString()); System.exit(1); } } public void shutdown() { try { unregister(); } catch (Exception e) { System.out.println(e); System.exit(1); } } private InetAddress regIp; private int regPort; private EventReceptionThread evtThr; private String regs[]; public int localPort; public DocumentBuilderFactory dbf; }
メンバー変数についての詳細はこの章で後述します。
コマンド行引数の解析方法については、付録 G 「CrnpClient.java アプリケーション」 内のコードを参照してください。
イベント受信はコード内で個別のスレッドで行われるようにする必要があります。これは、イベントスレッドがイベントコールバックを待機している間アプリケーションが継続してほかの作業を行えるようにするためです。
XML の設定についてはこの章で後述します。
コード内で、ServerSocket を作成してソケットにイベントが到着するのを待機する EventReceptionThread という Thread サブクラスを定義します。
サンプルコードのこの部分では、イベントの読み取りもイベントの処理も行われません。イベントの読み取りと処理についてはこの章で後述します。EventReceptionThread は、ワイルドカード IP アドレス上に ServerSocket を作成します。 EventReceptionThread は、CrnpClient オブジェクトにイベントを送信して処理できるように、CrnpClient オブジェクトに対する参照も維持します。
class EventReceptionThread extends Thread { public EventReceptionThread(CrnpClient clientIn) throws IOException { client = clientIn; listeningSock = new ServerSocket(client.localPort, 50, InetAddress.getLocalHost()); } public void run() { try { DocumentBuilder db = client.dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); while(true) { Socket sock = listeningSock.accept(); // ソケットストリームからイベントを作成し、処理する。 sock.close(); } // 到達不能 } catch (Exception e) { System.out.println(e); System.exit(1); } } /* プライベートメンバー変数 */ private ServerSocket listeningSock; private CrnpClient client; }
createEvtRecepThr オブジェクトを構築します。
private void createEvtRecepThr() throws Exception { evtThr = new EventReceptionThread(this); evtThr.start(); }
登録は以下の作業によって行います。
登録用の IP アドレスとポートに対して基本的な TCP ソケットを開く
XML 登録メッセージを作成する
ソケット上で XML 登録メッセージを送信する
ソケットから XML 応答メッセージを読み取る
ソケットを閉じる
上記のロジックを実装する Java コードを作成します。
以下のコード例は、CrnpClient クラスの registerCallbacks メソッド (CrnpClient コンストラクタによって呼び出される) の実装を示しています。createRegistrationString() と readRegistrationReply() の呼び出しの詳細は、この章で後述します。
regIp と regPort は、ココンストラクタによって設定されるオブジェクトメンバーです。
private void registerCallbacks() throws Exception { Socket sock = new Socket(regIp, regPort); String xmlStr = createRegistrationString(); PrintStream ps = new PrintStream(sock.getOutputStream()); ps.print(xmlStr); readRegistrationReply(sock.getInputStream(); sock.close(); }
unregister メソッドを実装します。
このメソッドは、CrnpClient の shutdown メソッドによって呼び出されます。createUnregistrationString の実装の詳細はこの章で後述します。
private void unregister() throws Exception { Socket sock = new Socket(regIp, regPort); String xmlStr = createUnregistrationString(); PrintStream ps = new PrintStream(sock.getOutputStream()); ps.print(xmlStr); readRegistrationReply(sock.getInputStream()); sock.close(); }
以上で、アプリケーション構造の設定と、通信用のコードの作成が終了しました。次は、XML の生成と解析を行うコードを作成する必要があります。初めに、SC_CALLBACK_REG XML 登録メッセージを生成するコードを作成します。
SC_CALLBACK_REG メッセージは、登録のタイプ (ADD_CLIENT、REMOVE_CLIENT、ADD_EVENTS または REMOVE_EVENTS)、コールバックポート、および要求するイベントの一覧から構成されます。各イベントはクラスとサブクラスから構成され、名前と値のペアリストが続きます。
この例のこの段階では、登録タイプ、コールバックポート、および登録イベントの一覧を格納する CallbackReg クラスを作成します。このクラスは、それ自体を SC_CALLBACK_REG XML メッセージにシリアル化することもできます。
このクラスには、クラスメンバーから SC_CALLBACK_REG XML メッセージ文字列を作成する convertToXml という興味深いメソッドがあります。このメソッドを使用したコードの詳細は、http://java.sun.com/xml/jaxp/index.html の JAXP ドキュメントに記載されています。
次のコード例は、Event クラスの実装を示しています。CallbackReg クラスは、 イベントを 1 つ保存してそのイベントを XML Element に変換できる Event クラスを使用します。
上記のロジックを実装する Java コードを作成します。
class CallbackReg { public static final int ADD_CLIENT = 0; public static final int ADD_EVENTS = 1; public static final int REMOVE_EVENTS = 2; public static final int REMOVE_CLIENT = 3; public CallbackReg() { port = null; regType = null; regEvents = new Vector(); } public void setPort(String portIn) { port = portIn; } public void setRegType(int regTypeIn) { switch (regTypeIn) { case ADD_CLIENT: regType = "ADD_CLIENT"; break; case ADD_EVENTS: regType = "ADD_EVENTS"; break; case REMOVE_CLIENT: regType = "REMOVE_CLIENT"; break; case REMOVE_EVENTS: regType = "REMOVE_EVENTS"; break; default: System.out.println("Error, invalid regType " + regTypeIn); regType = "ADD_CLIENT"; break; } } public void addRegEvent(Event regEvent) { regEvents.add(regEvent); } public String convertToXml() { Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); } catch (ParserConfigurationException pce) { // 指定されたオプションを持つパーサーを構築できない。 pce.printStackTrace(); System.exit(1); } // root 要素を作成する。 Element root = (Element) document.createElement("SC_CALLBACK_REG"); // 属性を追加する。 root.setAttribute("VERSION", "1.0"); root.setAttribute("PORT", port); root.setAttribute("regType", regType); // イベントを追加する。 for (int i = 0; i < regEvents.size(); i++) { Event tempEvent = (Event) (regEvents.elementAt(i)); root.appendChild(tempEvent.createXmlElement(document)); } document.appendChild(root); // 全体を文字列に変換する。 DOMSource domSource = new DOMSource(document); StringWriter strWrite = new StringWriter(); StreamResult streamResult = new StreamResult(strWrite); TransformerFactory tf = TransformerFactory.newInstance(); try { Transformer transformer = tf.newTransformer(); transformer.transform(domSource, streamResult); } catch (TransformerException e) { System.out.println(e.toString()); return (""); } return (strWrite.toString()); } private String port; private String regType; private Vector regEvents; }
Event クラスと NVPair クラスを実装します。
CallbackReg クラスは、NVPair クラスを使用する Event クラスを使用します。
class Event { public Event() { regClass = regSubclass = null; nvpairs = new Vector(); } public void setClass(String classIn) { regClass = classIn; } public void setSubclass(String subclassIn) { regSubclass = subclassIn; } public void addNvpair(NVPair nvpair) { nvpairs.add(nvpair); } public Element createXmlElement(Document doc) { Element event = (Element) doc.createElement("SC_EVENT_REG"); event.setAttribute("CLASS", regClass); if (regSubclass != null) { event.setAttribute("SUBCLASS", regSubclass); } for (int i = 0; i < nvpairs.size(); i++) { NVPair tempNv = (NVPair) (nvpairs.elementAt(i)); event.appendChild(tempNv.createXmlElement(doc)); } return (event); } private String regClass, regSubclass; private Vector nvpairs; } class NVPair { public NVPair() { name = value = null; } public void setName(String nameIn) { name = nameIn; } public void setValue(String valueIn) { value = valueIn; } public Element createXmlElement(Document doc) { Element nvpair = (Element) doc.createElement("NVPAIR"); Element eName = doc.createElement("NAME"); Node nameData = doc.createCDATASection(name); eName.appendChild(nameData); nvpair.appendChild(eName); Element eValue = doc.createElement("VALUE"); Node valueData = doc.createCDATASection(value); eValue.appendChild(valueData); nvpair.appendChild(eValue); return (nvpair); } private String name, value; }
XML メッセージを生成するヘルパークラスの作成が終了したところで、次は createRegistrationString メソッドの実装を記述します。このメソッドは、registerCallbacks メソッドによって呼び出されます (詳細は 「コールバックの登録と登録解除を行う」)。
createRegistrationString は、CallbackReg オブジェクトを構築し、その登録タイプとポートを設定します。続いて、createRegistrationString は、createAllEvent、createMembershipEvent、createRgEvent、および createREvent ヘルパーメソッドを使用して各種のイベントを構築します。各イベントは、CallbackReg オブジェクトが作成されたあとでこのオブジェクトに追加されます。最後に、createRegistrationString は CallbackReg オブジェクト上で convertToXml メソッドを呼び出し、String 形式の XML メッセージを取得します。
regs メンバー変数は、ユーザーがアプリケーションに指定するコマンド行引数を格納します。5 つ目以降の引数は、アプリケーションが登録を行うイベントを指定します。4 つ目の引数は登録のタイプを指定しますが、この例では無視されています。付録 G 「CrnpClient.java アプリケーション」 に挙げられている完全なコードでは、この 4 つ目の引数の使用方法も示されています。
上記のロジックを実装する Java コードを作成します。
private String createRegistrationString() throws Exception { CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.ADD_CLIENT); // イベントを追加する for (int i = 4; i < regs.length; i++) { if (regs[i].equals("M")) { cbReg.addRegEvent(createMembershipEvent()); } else if (regs[i].equals("A")) { cbReg.addRegEvent(createAllEvent()); } else if (regs[i].substring(0,2).equals("RG")) { cbReg.addRegEvent(createRgEvent(regs[i].substring(3))); } else if (regs[i].substring(0,1).equals("R")) { cbReg.addRegEvent(createREvent(regs[i].substring(2))); } } String xmlStr = cbReg.convertToXml(); return (xmlStr); } private Event createAllEvent() { Event allEvent = new Event(); allEvent.setClass("EC_Cluster"); return (allEvent); } private Event createMembershipEvent() { Event membershipEvent = new Event(); membershipEvent.setClass("EC_Cluster"); membershipEvent.setSubclass("ESC_cluster_membership"); return (membershipEvent); } private Event createRgEvent(String rgname) { Event rgStateEvent = new Event(); rgStateEvent.setClass("EC_Cluster"); rgStateEvent.setSubclass("ESC_cluster_rg_state"); NVPair rgNvpair = new NVPair(); rgNvpair.setName("rg_name"); rgNvpair.setValue(rgname); rgStateEvent.addNvpair(rgNvpair); return (rgStateEvent); } private Event createREvent(String rname) { Event rStateEvent = new Event(); rStateEvent.setClass("EC_Cluster"); rStateEvent.setSubclass("ESC_cluster_r_state"); NVPair rNvpair = new NVPair(); rNvpair.setName("r_name"); rNvpair.setValue(rname); rStateEvent.addNvpair(rNvpair); return (rStateEvent); }
登録解除文字列を作成します。
イベントを指定する必要がない分、登録解除文字列の作成は登録文字列の作成よりも簡単です。
private String createUnregistrationString() throws Exception { CallbackReg cbReg = new CallbackReg(); cbReg.setPort("" + localPort); cbReg.setRegType(CallbackReg.REMOVE_CLIENT); String xmlStr = cbReg.convertToXml(); return (xmlStr); }
以上で、アプリケーションの通信用コードと XML 生成コードの生成が終わります。CrnpClient コンストラクタは、setupXmlProcessing メソッドを呼び出します。このメソッドは、DocumentBuilderFactory オブジェクトを作成し、そのオブジェクトに各種の解析プロパティーを設定します。このメソッドの詳細は、JAXP ドキュメントに記載されています。http://java.sun.com/xml/jaxp/index.htmlを参照してください。
上記のロジックを実装する Java コードを作成します。
private void setupXmlProcessing() throws Exception { dbf = DocumentBuilderFactory.newInstance(); // 検証を行う必要はない。 dbf.setValidating(false); dbf.setExpandEntityReferences(false); // コメントと空白文字は無視したい。 dbf.setIgnoringComments(true); dbf.setIgnoringElementContentWhitespace(true); // CDATA セクションを TEXT ノードに結合する。 dbf.setCoalescing(true); }
登録メッセージまたは登録解除メッセージに応答して CRNP サーバーが送信する SC_REPLY XML XML メッセージを解析するには、RegReply ヘルパークラスが必要です。このクラスは、XML ドキュメントから構築できます。このクラスは、ステータスコードとステータスメッセージのアクセッサを提供します。サーバーからの XML ストリームを解析するには、新しい XML ドキュメントを作成してそのドキュメントの解析メソッドを使用する必要がありますこのメソッドの詳細は、http://java.sun.com/xml/jaxp/index.html の JAXP ドキュメントに記載されています。
上記のロジックを実装する Java コードを作成します。
readRegistrationReply メソッドは、新しい RegReply クラスを使用します。
private void readRegistrationReply(InputStream stream) throws Exception { // ドキュメントビルダーを作成する。 DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(new DefaultHandler()); // 入力ファイルを解析する。 Document doc = db.parse(stream); RegReply reply = new RegReply(doc); reply.print(System.out); }
RegReply クラスを実装します。
retrieveValues メソッドは XML ドキュメント内の DOM ツリーを回り、ステータスコードとステータスメッセージを抽出します。詳細は、http://java.sun.com/xml/jaxp/index.html の JAXP ドキュメントに記載されています。
class RegReply { public RegReply(Document doc) { retrieveValues(doc); } public String getStatusCode() { return (statusCode); } public String getStatusMsg() { return (statusMsg); } public void print(PrintStream out) { out.println(statusCode + ": " + (statusMsg != null ? statusMsg : "")); } private void retrieveValues(Document doc) { Node n; NodeList nl; String nodeName; // SC_REPLY 要素を見つける nl = doc.getElementsByTagName("SC_REPLY"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_REPLY node."); return; } n = nl.item(0); // statusCode 属性の値を取得する。 statusCode = ((Element)n).getAttribute("STATUS_CODE"); // SC_STATUS_MSG 要素を検出する nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_STATUS_MSG node."); return; } // TEXT セクションを取得する (存在する場合)。 n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { // 1 つも存在しなくてもエラーではないため、そのまま戻る。 return; } // 値を取得する。 statusMsg = n.getNodeValue(); } private String statusCode; private String statusMsg; }
最後のステップは、実際のコールバックイベントの解析と処理です。この作業をスムーズに行うため、「XML を生成する」で作成した Event クラスを変更します。このクラスを使用して、XML ドキュメントから Event を構築し、XML Element を作成できます。この変更は、XML ドキュメントを受け付ける別のコンストラクタ、retrieveValues メソッド、2 つの新たなメンバー変数 (vendor と publisher ) 、全フィールドのアクセッサメソッド、および出力メソッドを必要とします。
上記のロジックを実装する Java コードを作成します。
このコードは、「登録応答を解析する」で説明している RegReply クラスのコードに似ていることに注目してください。
public Event(Document doc) { nvpairs = new Vector(); retrieveValues(doc); } public void print(PrintStream out) { out.println("\tCLASS=" + regClass); out.println("\tSUBCLASS=" + regSubclass); out.println("\tVENDOR=" + vendor); out.println("\tPUBLISHER=" + publisher); for (int i = 0; i < nvpairs.size(); i++) { NVPair tempNv = (NVPair) (nvpairs.elementAt(i)); out.print("\t\t"); tempNv.print(out); } } private void retrieveValues(Document doc) { Node n; NodeList nl; String nodeName; // SC_EVENT 要素を検出する nl = doc.getElementsByTagName("SC_EVENT"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "SC_EVENT node."); return; } n = nl.item(0); // // CLASS、SUBCLASS、VENDOR、およびPUBLISHER // 属性の値を取得する。 // regClass = ((Element)n).getAttribute("CLASS"); regSubclass = ((Element)n).getAttribute("SUBCLASS"); publisher = ((Element)n).getAttribute("PUBLISHER"); vendor = ((Element)n).getAttribute("VENDOR"); // すべての nv ペアを取得する。 for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) { nvpairs.add(new NVPair((Element)child)); } } public String getRegClass() { return (regClass); } public String getSubclass() { return (regSubclass); } public String getVendor() { return (vendor); } public String getPublisher() { return (publisher); } public Vector getNvpairs() { return (nvpairs); } private String vendor, publisher;
XML 解析をサポートする、NVPair クラスのコンストラクタとメソッドを別途実装します。
手順 1 で Event クラスに変更を加えたため、NVPair クラスにも類似した変更を加える必要があります。
public NVPair(Element elem) { retrieveValues(elem); } public void print(PrintStream out) { out.println("NAME=" + name + " VALUE=" + value); } private void retrieveValues(Element elem) { Node n; NodeList nl; String nodeName; // NAME 要素を検出する。 nl = elem.getElementsByTagName("NAME"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "NAME node."); return; } // TEXT セクションを取得する。 n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Error in parsing: can't find " + "TEXT section."); return; } // 値を取得する。 name = n.getNodeValue(); // ここで要素を取得する。 nl = elem.getElementsByTagName("VALUE"); if (nl.getLength() != 1) { System.out.println("Error in parsing: can't find " + "VALUE node."); return; } // TEXT セクションを取得する n = nl.item(0).getFirstChild(); if (n == null || n.getNodeType() != Node.TEXT_NODE) { System.out.println("Error in parsing: can't find " + "TEXT section."); return; } // 値を取得する value = n.getNodeValue(); } public String getName() { return (name); } public String getValue() { return (value); } }
EventReceptionThread でイベントコールバックを待機する while ループを実装します。
EventReceptionThread については、「イベント受信スレッドを定義する」 を参照してください。
while(true) { Socket sock = listeningSock.accept(); Document doc = db.parse(sock.getInputStream()); Event event = new Event(doc); client.processEvent(event); sock.close(); }
スーパーユーザーになるか、RBAC 承認 solaris.cluster.modify を提供する役割になります。
アプリケーションを実行します。
# java CrnpClient crnpHost crnpPort localPort ... |
CrnpClient アプリケーションの完全なコードは、付録 G 「CrnpClient.java アプリケーション」 に示されています。