HinemosのノードをWebサービスAPI経由で定義登録する
「HinemosのジョブネットをWebサービスAPI経由で実行する」および「HinemosのジョブネットをWebサービスAPI経由で定義登録する」の派生。
Hinemosのインストールを ANSIBLE で自動化し、ジョブ定義登録も自動化できるようになった。ところがノードの登録が自動化できていなかった。
対応の道筋はジョブの場合と全く同じで以下の通りにする。使う要素はこれまで同様なので細かい説明は割愛する。
wsimport でスタブクラスを生成
ジョブ機能の場合はエンドポイントは /HinemosWS/JobEndpoint?wsdl であったが、リポジトリ機能の場合は /HinemosWS/RepositoryEndpoint?wsdl になる。
# wsimport -keep http://garnet-vm09:8080/HinemosWS/RepositoryEndpoint?wsdl parsing WSDL... Generating code... Compiling code... # ls -l total 4 drwxr-xr-x 3 root root 4096 May 3 02:17 com # tree com com └── clustercontrol └── ws └── repository ├── AddNode.class ├── AddNode.java ├── (中略) ├── RepositoryEndpoint.class ├── RepositoryEndpoint.java ├── RepositoryEndpointService.class ├── RepositoryEndpointService.java ├── (中略) └── UsedFacility.java 3 directories, 206 files
既存の定義を取得してみてみる
取得用のサービスが何なのか少々迷う。getNodeListAll サービスがそれっぽいのだが、マネージャ側のソースでは getNodeFacilityIdList と getNode を使うように指示がある。ノードなのにファシリティ?と思ってしまうあたり、そろそろノード・スコープ・ファシリティという言葉の整理をしないとまずい。
Hinemos ver4.1 ユーザマニュアル第4版からノードとスコープの説明を抜粋する。
3.1.2 スコープとノード
Hinemosでは、「スコープ」と「ノード」という2つの単位で管理対象を扱います。ノード
実際の管理対象のマシンを仮想化したものです。ノード情報として以下の情報を登録することができます。
・ハードウェア、ネットワーク、OS情報
・サービス(SNMP, WBEM, IPMI, WinRM)
・デバイス情報(CPU, メモリ, NIC, ディスク, ファイルシステム, 汎用デバイス)
・サーバ仮想化、ネットワーク仮想化、クラウド管理
・その他の情報スコープ
複数のノードをグループ化したものです。Hinemosで提供される機能の処理単位の多くは、スコープ単位となっています。スコープに対して行った処理は、登録されている各ノードに反映されることになります。また、スコープは複数のスコープをその下層のスコープとして登録することもできます。この場合は、スコープは階層構造を持ち、ツリーを形成することになります。
ジョブの投げ先を指定する項目の大きなくくりは [Scope] である。実際にノードを Registered Nodes 下から選び画面に表示されるのは Facility Name なのだ。今一スコープ感(謎)がない。Registered Nodes 下のノードは同名で要素が1個だけのスコープなんです、という説明されればぎりぎり納得できるが。
画面上ちょっと微妙な気分だったが、スタブクラスの下記の継承関係を確認したところで、プログラム的にはどう扱えばよいかは理解できた。スコープとノードはファシリティという概念でまとめられているので、ファシリティに対して(ジョブを)投げていると考えれば落ち着く(ファシリティはノードかもしれんしスコープかもしれん)。
前置きが長いが、GetNodeList.java としてノードの一覧を取得するプログラムを作成。getNodeFacilityIdList を使うので、親のスコープとしてRegistered Nodes (のFacilityIDである REGISTERED)を指定。
import com.clustercontrol.ws.repository.*; import javax.xml.ws.BindingProvider; import java.util.Map; import java.util.List; import java.util.ArrayList; public class GetNodeList { private static RepositoryEndpoint servicePort; private static void initServicePort() { // アクセス先/認証情報 final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/RepositoryEndpoint"; final String USERNAME = "hinemos"; final String PASSWORD = "hinemos"; RepositoryEndpointService service = new RepositoryEndpointService(); servicePort = service.getRepositoryEndpointPort(); // アクセス先設定/認証情報設定 BindingProvider bp = (BindingProvider)servicePort; Map<String, Object> requestContext = bp.getRequestContext(); requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT_URL); requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME); requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD); } public static void main( String[] args ) { initServicePort(); // 実行 try { // getNodeListAll では FacilityID, FacilityName, Desc, IPバージョン, IPv4アドレス, IPv6アドレス が得られるが他は含まれない // getNodeFacilityIdList 同様に getNode と併用が必要 // マネージャ側のソースでは getNodeFacilityIdList + getNode を使うように記載あり(何故?) //System.out.println("getNodeListAll starting..."); //List<NodeInfo> nodeList = servicePort.getNodeListAll(); //System.out.println("getNodeListAll complted."); final String PARENT_FACILITY_ID = "REGISTERED"; // Registered Nodes final String OWNER_ROLE_ID = "ALL_USERS"; final int LEVEL = 0; // 0: 全て, 1: 直下の子要素のみ System.out.println("getNodeFacilityIdList starting..."); List<String> nodeFacilityIdList = servicePort.getNodeFacilityIdList(PARENT_FACILITY_ID, OWNER_ROLE_ID, LEVEL); System.out.println("getNodeFacilityIdList completed."); System.out.println("getNode starting..."); List<NodeInfo> nodeList = new ArrayList<NodeInfo>(); for ( String fid : nodeFacilityIdList ) nodeList.add(servicePort.getNode(fid)); System.out.println("getNode completed."); System.out.println("getNodeFacilityIdList/getNode Output:"); for ( NodeInfo n : nodeList ) printNodeInfo(n); } catch ( HinemosUnknown_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidRole_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidUserPass_Exception e ) { e.printStackTrace(System.out); } catch ( FacilityNotFound_Exception e ) { e.printStackTrace(System.out); } } private static void printNodeInfo( NodeInfo node ) { System.out.printf("- FacilityID: %s, FacilityName: %s, FacilityType: %s, FacilityDesc: %s\n", node.getFacilityId(), node.getFacilityName(), node.getFacilityType(), node.getDescription()); System.out.printf(" Created: %s, Modified: %s\n", node.getCreateDatetime(), node.getModifyDatetime()); System.out.printf(" IPversion: %s, IPv4Addr: %s, Platform: %s, NodeName: %s\n", node.getIpAddressVersion(), node.getIpAddressV4(), node.getPlatformFamily(), node.getNodeName()); System.out.printf(" SNMPVersion: %s, SNMPPort: %s\n", node.getSnmpVersion(), node.getSnmpPort()); } }
実行結果は以下の通り、取得できている。
# java GetNodeList getNodeFacilityIdList starting... getNodeFacilityIdList completed. getNode starting... getNode completed. getNodeFacilityIdList/getNode Output: - FacilityID: garnet-vm09, FacilityName: garnet-vm09, FacilityType: 1, FacilityDesc: Created: 1430412693463, Modified: 1430412693463 IPversion: 4, IPv4Addr: 172.16.1.119, Platform: LINUX, NodeName: garnet-vm09 SNMPVersion: 2c, SNMPPort: 161 - FacilityID: LINUX, FacilityName: Linuxノードサンプル, FacilityType: 1, FacilityDesc: Created: 1430594599307, Modified: 1430594599307 IPversion: 4, IPv4Addr: 172.16.1.130, Platform: LINUX, NodeName: linuxnodename SNMPVersion: 2c, SNMPPort: 161 - FacilityID: WINDOWS, FacilityName: Windowsノードサンプル, FacilityType: 1, FacilityDesc: Created: 1430594661268, Modified: 1430594661268 IPversion: 4, IPv4Addr: 172.16.1.1, Platform: WINDOWS, NodeName: windowsnodename SNMPVersion: 2c, SNMPPort: 161
デフォルトの定義情報をシリアライズで保存する
上記の一覧にある LINUX と WINDOWS をシリアライズする。一応差がプラットフォームのところだけか確認する。
FacilityIDさえわかっていれば getNode サービスを直接使えば良いので、取得+シリアライズの簡単なプログラムになる。
MarshalNodeParts.java として作成。
# cat MarshalNodeParts.java import com.clustercontrol.ws.repository.*; import javax.xml.ws.BindingProvider; import java.util.Map; import java.io.*; import javax.xml.bind.JAXB; public class MarshalNodeParts { private static RepositoryEndpoint servicePort; private static void initServicePort() { // アクセス先/認証情報 final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/RepositoryEndpoint"; final String USERNAME = "hinemos"; final String PASSWORD = "hinemos"; RepositoryEndpointService service = new RepositoryEndpointService(); servicePort = service.getRepositoryEndpointPort(); // アクセス先設定/認証情報設定 BindingProvider bp = (BindingProvider)servicePort; Map<String, Object> requestContext = bp.getRequestContext(); requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT_URL); requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME); requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD); } public static void main( String[] args ) { initServicePort(); // 実行 try { final String SAMPLE_LIN_NODE_FACILITY_ID = "LINUX"; final String SAMPLE_WIN_NODE_FACILITY_ID = "WINDOWS"; System.out.println("getNode starting..."); NodeInfo sampleLinNode = servicePort.getNode(SAMPLE_LIN_NODE_FACILITY_ID); NodeInfo sampleWinNode = servicePort.getNode(SAMPLE_WIN_NODE_FACILITY_ID); System.out.println("getNode completed."); System.out.println("marshalling starting..."); OutputStream os = new FileOutputStream(SAMPLE_LIN_NODE_FACILITY_ID + ".xml"); JAXB.marshal(sampleLinNode, os); os.close(); os = new FileOutputStream(SAMPLE_WIN_NODE_FACILITY_ID + ".xml"); JAXB.marshal(sampleWinNode, os); os.close(); System.out.println("marshalling completed..."); } catch ( HinemosUnknown_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidRole_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidUserPass_Exception e ) { e.printStackTrace(System.out); } catch ( FacilityNotFound_Exception e ) { e.printStackTrace(System.out); } catch ( IOException e ) { e.printStackTrace(System.out); } } private static void printNodeInfo( NodeInfo node ) { System.out.printf("- FacilityID: %s, FacilityName: %s, FacilityType: %s, FacilityDesc: %s\n", node.getFacilityId(), node.getFacilityName(), node.getFacilityType(), node.getDescription()); System.out.printf(" Created: %s, Modified: %s\n", node.getCreateDatetime(), node.getModifyDatetime()); System.out.printf(" IPversion: %s, IPv4Addr: %s, Platform: %s, NodeName: %s\n", node.getIpAddressVersion(), node.getIpAddressV4(), node.getPlatformFamily(), node.getNodeName()); System.out.printf(" SNMPVersion: %s, SNMPPort: %s\n", node.getSnmpVersion(), node.getSnmpPort()); } }
実行結果は以下の通り、問題なくXMLが作成された。
LinuxとWindowsで差があったのは platformFamily フィールドのみだった。
# java MarshalNodeParts getNode starting... getNode completed. marshalling starting... marshalling completed... # ls -l *.xml -rw-r--r-- 1 root root 2918 May 3 06:01 LINUX.xml -rw-r--r-- 1 root root 2924 May 3 06:01 WINDOWS.xml
デシリアライズ+αで登録する
LinuxとWindowsで自明な項目(platformFamily)以外に差がないので、LINUX.xml を NODE.xml としてこれを読み込む。
# mv LINUX.xml NODE.xml # ls -l *.xml -rw-r--r-- 1 root root 2918 May 3 06:01 NODE.xml -rw-r--r-- 1 root root 2924 May 3 06:01 WINDOWS.xml
AddNodeLoremipsum.java としてノード loremipsum を追加するプログラムを作成。
import com.clustercontrol.ws.repository.*; import javax.xml.ws.BindingProvider; import java.util.Map; import java.io.*; import javax.xml.bind.JAXB; public class AddNodeLoremipsum { private static RepositoryEndpoint servicePort; private static void initServicePort() { // アクセス先/認証情報 final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/RepositoryEndpoint"; final String USERNAME = "hinemos"; final String PASSWORD = "hinemos"; RepositoryEndpointService service = new RepositoryEndpointService(); servicePort = service.getRepositoryEndpointPort(); // アクセス先設定/認証情報設定 BindingProvider bp = (BindingProvider)servicePort; Map<String, Object> requestContext = bp.getRequestContext(); requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT_URL); requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME); requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD); } public static void main( String[] args ) { initServicePort(); // 実行 try { final String SAMPLE_NODE_FACILITY_ID = "loremipsum"; final String SAMPLE_NODE_FACILITY_NAME = "LOREM IPSUM"; final String SAMPLE_NODE_NODENAME = "loremipsum"; final String SAMPLE_NODE_IPADDRESSV4 = "172.16.1.250"; System.out.println("loremipsum node creation starting..."); NodeInfo loremipsum = createLinuxNode(SAMPLE_NODE_FACILITY_ID, SAMPLE_NODE_FACILITY_NAME, ""); setNodeAddress(loremipsum, SAMPLE_NODE_NODENAME, 4, SAMPLE_NODE_IPADDRESSV4, ""); System.out.println("loremipsum node creation completed."); System.out.println("addNode starting..."); servicePort.addNode(loremipsum); System.out.println("addNode completed..."); } catch ( HinemosUnknown_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidRole_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidUserPass_Exception e ) { e.printStackTrace(System.out); } catch ( FacilityDuplicate_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidSetting_Exception e ) { e.printStackTrace(System.out); } catch ( IOException e ) { e.printStackTrace(System.out); } } private static NodeInfo unmarshalNodeParts( String facilityId, String facilityName, String desc, String platform ) throws IOException { final String NODE_PARTS_FILE = "NODE.xml"; InputStream is = new FileInputStream(NODE_PARTS_FILE); NodeInfo node = JAXB.unmarshal(is, NodeInfo.class); is.close(); // 固有情報消去/設定 node.setFacilityId(facilityId); node.setFacilityName(facilityName); node.setDescription(desc); node.setPlatformFamily(platform); node.setCreateDatetime(System.currentTimeMillis()); node.setModifyDatetime(node.getCreateDatetime()); node.setNodeName(""); node.setIpAddressVersion(4); node.setIpAddressV4(""); node.setIpAddressV6(""); return node; } private static NodeInfo createLinuxNode( String facilityId, String facilityName, String desc ) throws IOException { return unmarshalNodeParts(facilityId, facilityName, desc, "LINUX"); } private static NodeInfo createWindowsNode( String facilityId, String facilityName, String desc ) throws IOException { return unmarshalNodeParts(facilityId, facilityName, desc, "WINDOWS"); } private static void setNodeAddress( NodeInfo node, String nodeName, int ipAddressVersion, String ipAddressV4, String ipAddressV6 ) { node.setNodeName(nodeName); node.setIpAddressVersion(ipAddressVersion); node.setIpAddressV4(ipAddressV4); node.setIpAddressV6(ipAddressV6); } }
実行結果は以下の通り。プログラム実行後にクライアント上の更新を行うとノードが追加されている。
One more thing: getNodePropertyBySNMP によるノード情報取得を元に登録する
とくめいさんより getNodePropertyBySNMP によるノード情報取得を元に登録する方法もある旨コメントを頂きました。
この方法だとデシリアライズを使わずに、より多くの実機情報を登録できるのでいいですね。ありがとうございます!
getNodePropertyBySNMP と GUI の対応
getNodePropertyBySNMP はノード追加のダイアログ上の [Find By SNMP] に対応するサービスだ。対象ノードのSNMPと疎通して情報を埋めてくれる。
やってみよう
追加対象ノードのIPアドレスを引数で受け取り追加するプログラムを AddNodeBasedOnSnmp.java として作成。
- FacilityID, FacilityName はノード名を使う。
- OwnerRoleID は ALL_USERS 決めうちとした。
- IPアドレス以外の SNMP 設定も決めうちとした。
- getNodePropertyBySNMP サービスは存在しないノードに対して実行しても例外にはならない。ノード名すら取れない結果はおかしいと判定した。
import com.clustercontrol.ws.repository.*; import javax.xml.ws.BindingProvider; import java.util.Map; public class AddNodeBasedOnSnmp { private static RepositoryEndpoint servicePort; private static void initServicePort() { // アクセス先/認証情報 final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/RepositoryEndpoint"; final String USERNAME = "hinemos"; final String PASSWORD = "hinemos"; RepositoryEndpointService service = new RepositoryEndpointService(); servicePort = service.getRepositoryEndpointPort(); // アクセス先設定/認証情報設定 BindingProvider bp = (BindingProvider)servicePort; Map<String, Object> requestContext = bp.getRequestContext(); requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT_URL); requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME); requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD); } public static void main( String[] args ) { initServicePort(); // 実行 try { final int SNMP_PORT = 161; final String SNMP_COMMUNITY = "public"; final String SNMP_VERSION = "2c"; if ( args.length == 0 ) { System.out.println("usage: java AddNodeBasedOnSnmp <ipaddress>"); System.exit(1); } final String ipaddress = args[0]; System.out.println("getNodePropertyBySNMP starting..."); NodeInfo node = servicePort.getNodePropertyBySNMP( ipaddress, SNMP_PORT, SNMP_COMMUNITY, SNMP_VERSION); System.out.println("getNodePropertyBySNMP completed."); // ノード名も取得できないのはおかしいので排除 // (存在しないIPアドレスを指定しても getNodePropertyBySNMP 自体は失敗しない) if ( node.getNodeName().equals("") ) { System.out.println("Could not determine nodename!"); System.out.println("SNMP service seems to be disabled at the target node."); System.exit(1); } // FacilityID, FacilityName を決める(ノード名と同じ) // その他設定しないと addNode 実行時に InvalidSetting_Exception になったものを設定 node.setFacilityId(node.getNodeName()); node.setFacilityName(node.getNodeName()); node.setOwnerRoleId("ALL_USERS"); System.out.println("addNode starting..."); servicePort.addNode(node); System.out.println("addNode completed."); } catch ( HinemosUnknown_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidRole_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidUserPass_Exception e ) { e.printStackTrace(System.out); } catch ( FacilityDuplicate_Exception e ) { e.printStackTrace(System.out); } catch ( InvalidSetting_Exception e ) { e.printStackTrace(System.out); } } }
実行結果は以下の通り、SNMPが動作している 172.16.1.120 に対してうまく登録処理できた!
# java AddNodeBasedOnSnmp usage: java AddNodeBasedOnSnmp <ipaddress> # java AddNodeBasedOnSnmp 172.16.1.150 getNodePropertyBySNMP starting... getNodePropertyBySNMP completed. Could not determine nodename! SNMP service seems to be disabled at the target node. # java AddNodeBasedOnSnmp 172.16.1.120 getNodePropertyBySNMP starting... getNodePropertyBySNMP completed. addNode starting... addNode completed.