HinemosのノードをWebサービスAPI経由で定義登録する

HinemosのジョブネットをWebサービスAPI経由で実行する」および「HinemosのジョブネットをWebサービスAPI経由で定義登録する」の派生。
Hinemosのインストールを ANSIBLE で自動化し、ジョブ定義登録も自動化できるようになった。ところがノードの登録が自動化できていなかった。

対応の道筋はジョブの場合と全く同じで以下の通りにする。使う要素はこれまで同様なので細かい説明は割愛する。

  1. wsimport でスタブクラスを生成する。
  2. 既存の定義を取得してみてみる。
  3. デフォルトの定義情報をシリアライズで保存する。
  4. シリアライズ+αで登録する。

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

デフォルトの定義情報をシリアライズで保存する

上記の一覧にある LINUXWINDOWSシリアライズする。一応差がプラットフォームのところだけか確認する。

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が作成された。
LinuxWindowsで差があったのは 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

シリアライズ+αで登録する

LinuxWindowsで自明な項目(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.