Hatena::ブログ(Diary)

とあるSIerの憂鬱


20150503141725
2011 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 09 | 11 | 12 |
2014 | 07 | 08 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 |
2017 | 05 | 07 |
 

2017-07-04

ダイバーシティというけれど

私の会社は「ダイバーシティ」という言葉を定期的に発信してくる。
多様な人を集めることで新たな発想が生まれるのを期待しているらしい。

女性、外国人、障害をもった方、会社の中ではまだまだ少ない。そういうことをダイバーシティと言っているのだと思う。
ただ、ふっと思った。この会社は全然多様性なんて認めていない。だって昇格できる人は技術の人ではなく、管理者としてのスキルがものをいうのだから。

自分は何十人を抱えるプロマネになるイメージがわかないし、苦しいからなりたくない。
だから今の主任職まではイメージの範疇でも、その先が見えない。課長のイメージが無い。私は奴隷商人じゃないんだ!!!
行き止まりが見えてしまった感じがして、最近は今までにもまして冷めた見方になっている。

お前はエースなんだからどこでもやれる

そうかもしれない。なんとかやると思う。だからといって現場を変わりたいとは思わない。
新しいところは怖い。積み上げてきた信頼や実績がリセットされる新しい場所にいくときの徒労感をもう味わいたくない。
できるとしても、それは傷つきながらやっているんだ。折れそうな不安のなかでやっているんだ。
それをどこでもやれるなんて言うな!!!!!!!!!ふざけるな!!!!!!!!!

炎上しているプロジェクトなら言い値で入れる

初めて聞いた時に耳を疑った。
自分にない発想だ。
ビジネスとしては儲かる。けどそれは炎上しているところに人を入れるってことだ。

ゆとりの法則かピープルウェアにある「群れの速度はリーダーの速度で決まる、というポスターがある・・・群れとは君たちのことだ」というような記述を思い出さざるを得ない。

ITはビジネスツール

ITは商売道具だと言い切った。
ごめん、私はITが楽しいからここにいるんだ。そういう気持ちを失いつつあるけれど、楽しいからやっているんだ。
人生の1/3以上の時間を費やす仕事を楽しくないものにするのは不幸だ。

仕事は楽しくないもの

私は2年生に聞いてみた。少し変わりつつある自分を意識して、「仕事は楽しい?」と。
細かい仕事の指摘どうこうではなく、大事なのは「仕事は楽しいか?」だと思ったから聞いてみた。
そして「仕事は楽しいものじゃない」との答えを得た。そうなのか・・・

チェス盤の上の駒

配置を変更する様子が駒のようだ。
これまでで一番そう思う。

2017-05-20

無限成長への疑問

  • どうして去年よりかならず売上アップの予算が組まれるんだろうか。
  • なぜ予算が減ることはないのだろうか。
  • その予算を達成するために、存在しない・やりたくもない「仮の案件」を計上し、予算達成できそうな計画を立てるのだろう。

できないと思ったらできない。これはそう。だけど、そもそもなんで「売上」を上げなくちゃならない?宇宙の様に無限に膨張していくものがあるのか?という単純な感覚があって疑問を投げかける。誰かのためにそれが求められているだけではないのか?という疑問もある。

違和感

  • 単調増加する予算、別に増えない所得
  • どこまでも増えていく、ということが「永久機関が不可能」だとされることに反するように感じる
  • 誰のために売上を増やすのか?

見解

売上を伸ばすことの妥当性

今自分が感じている、「売上」を伸ばすことの妥当性、理由が2個ある。

  • 守るために攻める

三国志の蜀と同じである。蜀は魏を討伐し、漢を再興する、という大義名分のもと魏と何度も戦う(北伐)。子供の頃は「夢のために挫けずに何度も挑戦する蜀」という捉え方をしていた。しかし、あるとき、「攻めることで魏から少しでも領土を奪っておかないと、攻められた時に蜀の本土が奪われてしまう。国力が弱い蜀は攻められたらすぐマイナスになってしまう。少しでもプラスを稼ぐことで攻められた場合でも0に戻るぐらいのバッファを確保する必要がある。つねに戦いの場を自国ではなく相手の領土内にしておくことで、自国を守る。」という解釈に転換した(どこかでそういう解釈を読んだ)。

だから、単純に「売上を伸ばす」について、本当に攻めたいから出ている目標なのか、実は「攻撃は最大の防御」としての目標なのか、これは意識したい。企業も自分が滅びることは怖い、だから攻めていることがある、と思う。後ろ向きな解釈だが、これは従業員の利害と一致すると思う。

  • 株主のために成長する

私の所属する会社は「株式会社」なので株主が存在する。一般の投資家や銀行ではなく、親会社が100%の株主ではあるが、株主は株主だ。株主は利益を得るために「投資」している。リスクを負っている。

100%子会社の場合、株券自体の値上がりによる収入はそれほど見込まれず、配当による収入が目的とも思うが、株主の成長期待は自分が株を持ってみるとわかると思う。「もっともっと」なのだ。「もっと売り上げて規模が大きくなって、それに比例して利益を上げて、俺に利益をもたらしてくれ」という気持ちが単純に出てくる。今後3年で売上・利益を2倍にします、株価も2倍ぐらいにはなります、だけどそこで無理したことにより5年後に破綻します、ということだったら3年後に売り抜ける前提でそれを選択するかもしれない。ひどい自分だと思う。その企業の社会的意味など、かけらも考えていない。

「お前がその程度の意識の株主だからといって、全員がそうだと思うな」とはごもっとも。けれど、「売上アップ」を単純に「予算」として「達成しなければならないもの」と妄信しているかのような言動をしている上位層を見ていると、「それって誰のためのもの?」、「どうしてそんなに当たり前のもののように言えるの?」、と思わずにはいられない。その目標を本当に望んでいる人は別に居る。

だからどうする

そんな「売上」の目標であるが、会社に所属している以上は否定は難しい。だからどうするか。

  • 同床異夢

全然別の自分の目標を持ち、その結果の一部として、見かけ上、組織の目標を目指している・達成したようにする。自分が会社に依存せずに生きて行くために、「自分がこの売上を立てられるスキルを身につける」ための練習の場として利用する。これは同じ様な結果を形上生み出すが、意味合いは異なる。

同意出来ない目標であっても、同意しか選択肢がないのなら、本心から同意する必要はない。

  • 株主になる

大きな規模ではないが、売上アップを求める側の立場に片足をおく。ストックオプションはこの形を押し進めたもの。

  • 良い売上を目指す

増やそうと思って増やす。売ろうと思って売る。売れるところがないかと探して売る。そんな売り方が嫌なのかもしれない。アップルのipod touchを電車内で見たとき「なにこの全面ディスプレイの携帯端末!これすごい合理的!すごい便利そう!欲しい!」と思ってすぐ買った。何のCMや広告を見た訳でもない。見て、欲しいから買った。みんなそうだろう?「お願い売って!」という状態だったと思う。増やそうとしなくても結果として増えてしまう・・・こんな売上を目指す。

補足
  • やろうと思わなければできない

これは真実。だから「やりたいことならやりたいと思え」「やるための計画を立てろ」「計画通りにやって成し遂げろ」と思う。
ただ気をつけたい。これは「やりたいと思ったことを人が成し遂げる」ための手法の正しさであって、「なにをやりたいか」については何も規定していない。

「売上アップをしたいと思いその計画を立てなければ達成はない」は正しい。ただ、「正しい」は手法への正しさであって、その手法を適用した対象への同意ではない。
「手法」関数は正しいが、「手法(売上)」の呼び出しに同意するかどうかは別問題。

2015-06-13

感覚の問い合わせでしか内容を取り出せないデータベース

良い配色に関するルールを知識としてインプットしたとして、その後の使い方には2種類考えられる。

  • そのルールが私に訴えかけて色を決める。
  • 私が色を選んで、ルールを満足しているか(私の中で)問い合わせる。

f:id:incarose86:20150614005107p:image

良いと思えるものが体系的に作られるのではなく、良いと思えるものが出てくるまでいくつも試すことになる。とても時間がかかるやりかただ。いつになったらできるのか、それがわからないやりかただ。

2015-05-02

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個だけのスコープなんです、という説明されればぎりぎり納得できるが。

f:id:incarose86:20150502205024p:image

画面上ちょっと微妙な気分だったが、スタブクラスの下記の継承関係を確認したところで、プログラム的にはどう扱えばよいかは理解できた。スコープとノードはファシリティという概念でまとめられているので、ファシリティに対して(ジョブを)投げていると考えれば落ち着く(ファシリティはノードかもしれんしスコープかもしれん)。

f:id:incarose86:20150502203913p:image

前置きが長いが、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);
    }
}

実行結果は以下の通り。プログラム実行後にクライアント上の更新を行うとノードが追加されている。
f:id:incarose86:20150502212145p:image


One more thing: getNodePropertyBySNMP によるノード情報取得を元に登録する

とくめいさんより getNodePropertyBySNMP によるノード情報取得を元に登録する方法もある旨コメントを頂きました。
この方法だとデシリアライズを使わずに、より多くの実機情報を登録できるのでいいですね。ありがとうございます!

getNodePropertyBySNMP と GUI の対応

getNodePropertyBySNMP はノード追加のダイアログ上の [Find By SNMP] に対応するサービスだ。対象ノードのSNMPと疎通して情報を埋めてくれる。

f:id:incarose86:20150509195706p:image

やってみよう

追加対象ノードの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.

f:id:incarose86:20150509195707p:image

2015-05-01

HinemosのジョブネットをWebサービスAPI経由で定義登録する

HinemosのジョブネットをWebサービスAPI経由で実行する」の続編。
実行が可能になり、その過程でWebサービスAPIの利用方法も理解できた。いよいよ当初の目的の定義登録に進む。

ジョブネット定義の取得

まずはジョブネットの定義がどのように管理されているのか構造を知らなければならない。
WebサービスAPIの getJobTree と getJobFull を使用することでジョブネットの定義を引き出すことができる。

ジョブネットの階層構造は JobTreeItem クラスで表現されており、IDや名前などの各種の付加情報は JobInfo クラスで管理されている。
JobTreeItemクラスのフィールドは以下の通り。

  • JobTreeItem
    • JobTreeItem parent ・・・ 階層構造の親要素 (ただしシリアライズに不都合なので?getJobTreeの結果では null である)
    • List<JobTreeItem> children ・・・ 階層構造の下位要素
    • JobInfo data ・・・ IDや名前、コマンドなどの情報はすべてこの JobInfo クラスが持っている
    • JobDetailInfo detail ・・・ 恐らくジョブ実行時に使われるもので定義的には関係無し

なお、getJobTree サービスではこの JobTreeItem による階層構造はすべて返してくれる。しかし、data フィールドにセットされる JobInfo クラスのインスタンスにはジョブユニットID、ジョブID、名前などツリー描画のために必要な情報しか含まれない。data を完全な状態にするためには getJobFull サービスをこの JobInfo クラスのインスタンスごとに呼ぶ必要がある。

定義の取得をしてみよう

GetJobTree.java として定義を取得、大まかな出力をするプログラムを作成。

import com.clustercontrol.ws.jobmanagement.*;
import javax.xml.ws.BindingProvider;
import java.util.Map;

public class GetJobTree {
    private static JobEndpoint servicePort;

    private static void initServicePort() {
        // アクセス先/認証情報
        final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/JobEndpoint";
        final String USERNAME = "hinemos";
        final String PASSWORD = "hinemos";

        JobEndpointService service = new JobEndpointService();
        servicePort = service.getJobEndpointPort();

        // アクセス先設定/認証情報設定
        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();

        // getJobTreeサービス実行のための引数準備
        String ownerRoleId = "ALL_USERS";
        boolean treeOnly = true;

        // 実行(やたら例外多い...)
        try {
            System.out.println("getJobTree starting...");
            JobTreeItem jobtree = servicePort.getJobTree(ownerRoleId, treeOnly);
            System.out.println("getJobTree complted.");

            // JobInfo内のコマンド情報などはツリーとして取得されないため個別リクエストを追加発行して取得
            System.out.println("getJobFull starting...");
            getJobInfoFull(jobtree);
            System.out.println("getJobFull complted.");

            System.out.println("\ngetJobTree/getJobFull Output:");
            printJobTreeItem(jobtree, "  ");
        }
        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 ( JobMasterNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( NotifyNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( UserNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
    }

    private static void getJobInfoFull( JobTreeItem item )
            throws JobMasterNotFound_Exception,
                   UserNotFound_Exception,
                   HinemosUnknown_Exception,
                   InvalidUserPass_Exception,
                   InvalidRole_Exception {
        JobInfo info = item.getData();

        if ( info.getType() >= 0 ) {
            System.out.println("  getJobFull...");
            JobInfo infoFull = servicePort.getJobFull(info);
            item.setData(infoFull);
        } 

        for ( JobTreeItem i : item.getChildren() )
            getJobInfoFull(i);
    }

    private static void printJobTreeItem( JobTreeItem item, String indent ) {
        final String nextIndent = indent + "    ";

        JobInfo info = item.getData();
        JobCommandInfo cmd = info.getCommand();

        System.out.printf("%s- Unit: %s,  Type: %s,  ID: %s,  Name: %s\n", indent,
            info.getJobunitId(),
            info.getType(),
            info.getId(),
            info.getName());
        System.out.printf("%s  isPropertyFull: %s,  OwnerRoleID: %s,  Desc: %s\n", indent,
            info.isPropertyFull(),
            info.getOwnerRoleId(),
            info.getDescription());
        if ( cmd != null )
            System.out.printf("%s  facilityID: %s,  startCommand: %s\n", indent,
                cmd.getFacilityID(),
                cmd.getStartCommand());

        for ( JobTreeItem i : item.getChildren() )
            printJobTreeItem(i, nextIndent);
    }
}

実行結果は以下の通り、ジョブ定義取得は成功。なお、ジョブユニットをまとめる階層のさらに1段階上から返る模様。

# java GetJobTree
getJobTree starting...
getJobTree complted.
getJobFull starting...
  getJobFull...
  getJobFull...
  getJobFull...
  getJobFull...
getJobFull complted.

getJobTree/getJobFull Output:
  - Unit: ,  Type: -1,  ID: ,  Name: 
    isPropertyFull: false,  OwnerRoleID: ,  Desc: 
      - Unit: ,  Type: -1,  ID: ,  Name: Job
        isPropertyFull: false,  OwnerRoleID: ,  Desc: 
          - Unit: JobUnit01,  Type: 0,  ID: JobUnit01,  Name: ジョブユニット1
            isPropertyFull: true,  OwnerRoleID: ALL_USERS,  Desc: 
              - Unit: JobUnit01,  Type: 1,  ID: JobNet01,  Name: ジョブネット1
                isPropertyFull: true,  OwnerRoleID: ALL_USERS,  Desc: 
                  - Unit: JobUnit01,  Type: 2,  ID: Job01,  Name: ジョブネット1-ジョブ1
                    isPropertyFull: true,  OwnerRoleID: ALL_USERS,  Desc: ジョブ1の説明
                    facilityID: garnet-vm09,  startCommand: /tmp/job001.sh
                  - Unit: JobUnit01,  Type: 2,  ID: Job02,  Name: ジョブネット1-ジョブ2
                    isPropertyFull: true,  OwnerRoleID: ALL_USERS,  Desc: ジョブ2の説明
                    facilityID: garnet-vm09,  startCommand: /tmp/job002.sh

f:id:incarose86:20150501202029p:image


ジョブネット定義の登録準備

ジョブネットの定義を登録するためのWebサービスAPIは registerJobunit だ。自分で JobTreeItem クラスと JobInfo クラスのインスタンスをガシガシ作れば、あとはこの registerJobunit を呼べばよい。

しかし、JobInfoクラスのフィールドは意外に多い。毎回こんなの初期化したくない・・・

f:id:incarose86:20150501203600p:image

インスタンスの保存のためのシリアライズ

オブジェクトをファイルなどに保存するためのシリアライズの方式はいろいろあるようで、以下を選択肢とした。最終的には「JAXB一択」である。

  • ObjectOutputStream / ObjectInputStream ・・・ バイナリ出力
  • XStream ・・・ XML出力
  • snakeyaml ・・・ YAML出力
  • JAXB (Java Architecture for XML Binding) ・・・ XML出力

JAXB選択の理由は以下の通り。

  • wsimport の基盤になっている JAX-WS が JAXB を使っている。
  • JDK6以降は標準になっており、追加のライブラリが不要である。
  • wsimport で自動生成されるスタブクラスには Serializable がついていない。

JAX-WSとJAXBの関係はこちらのページに記載あり。ページから抜粋する。
f:id:incarose86:20150501205006p:image
JAXBを使用してシリアライズ/デシリアライズして上手く行かないなら、WebサービスAPIアクセス上も上手く行かないであろうから、ある種諦めもつく。そして、wsimportが生成したソースにアノテーションが付いているので、JAXBを使ってくれと言っているのと同じである。

ジョブユニット・ジョブネット・ジョブのシリアライズ実施

MarshalJobParts.java としてジョブユニット・ジョブネット・ジョブのそれぞれの JobTreeItem をシリアライズして XMLファイルに保存するプログラムを作成。

  • ジョブユニット・ジョブネットのシリアライズ結果に、下位の階層もついてくると部品として不適なので、シリアライズ直前に JobTreeItem の children フィールドをクリアする。
  • Job01とJob02を出力するのはJob02は前後関係の情報を持っているジョブのサンプルだから。
import com.clustercontrol.ws.jobmanagement.*;
import javax.xml.ws.BindingProvider;
import java.util.Map;

import java.io.*;
import javax.xml.bind.JAXB;

public class MarshalJobParts {
    // アクセス先/認証情報
    private static final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/JobEndpoint";
    private static final String USERNAME = "hinemos";
    private static final String PASSWORD = "hinemos";

    // ジョブ取得情報
    private static final String OWNER_ROLE_ID = "ALL_USERS";
    private static final boolean TREE_ONLY = true;

    // ジョブ部品サンプリング対象
    private static final String SAMPLE_JOBUNIT_ID = "JobUnit01";
    private static final String SAMPLE_JOBNET_ID = "JobNet01";
    private static final String SAMPLE_JOB1_ID = "Job01";
    private static final String SAMPLE_JOB2_ID = "Job02";

    private static JobEndpoint servicePort;


    public static void main( String[] args ) {
        initServicePort();

        // 実行(やたら例外多い...)
        try {
            System.out.println("getJobTree/getJobFull starting...");
            JobTreeItem root = getJobTreeFull();
            System.out.println("getJobTree/getJobFull complted.");

            System.out.println("findJobTreeItem starting...");
            JobTreeItem JobUnit = findJobTreeItem(root, SAMPLE_JOBUNIT_ID);
            JobTreeItem JobNet  = findJobTreeItem(root, SAMPLE_JOBNET_ID);
            JobTreeItem Job1    = findJobTreeItem(root, SAMPLE_JOB1_ID);
            JobTreeItem Job2    = findJobTreeItem(root, SAMPLE_JOB2_ID);
            System.out.println("findJobTreeItem completed.");

            System.out.println("marshalJobParts starting...");
            // getChildren() のリストに対して破壊的
            marshalJobParts(JobUnit, SAMPLE_JOBUNIT_ID);
            marshalJobParts(JobNet, SAMPLE_JOBNET_ID);
            marshalJobParts(Job1, SAMPLE_JOB1_ID);
            marshalJobParts(Job2, SAMPLE_JOB2_ID);
            System.out.println("marshalJobParts 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 ( JobMasterNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( NotifyNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( UserNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( IOException e ) {
            e.printStackTrace(System.out);
        }
    }

    private static void initServicePort() {
        JobEndpointService service = new JobEndpointService();
        servicePort = service.getJobEndpointPort();

        // アクセス先設定/認証情報設定
        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);
    }

    private static JobTreeItem getJobTreeFull()
            throws HinemosUnknown_Exception,
                   InvalidRole_Exception,
                   InvalidUserPass_Exception,
                   JobMasterNotFound_Exception,
                   NotifyNotFound_Exception,
                   UserNotFound_Exception {
        JobTreeItem root = servicePort.getJobTree(OWNER_ROLE_ID, TREE_ONLY);
        // JobInfo内のコマンド情報などはツリーとして取得されないため個別リクエストを追加発行して取得
        getJobInfoFull(root);
        return root;
    }

    private static void getJobInfoFull( JobTreeItem item )
            throws JobMasterNotFound_Exception,
                   UserNotFound_Exception,
                   HinemosUnknown_Exception,
                   InvalidUserPass_Exception,
                   InvalidRole_Exception {
        JobInfo info = item.getData();

        if ( info.getType() >= 0 ) {
            System.out.println("  getJobFull...");
            JobInfo infoFull = servicePort.getJobFull(info);
            item.setData(infoFull);
        } 

        for ( JobTreeItem i : item.getChildren() )
            getJobInfoFull(i);
    }

    private static JobTreeItem findJobTreeItem( JobTreeItem root, String id ) {
        JobInfo info = root.getData();

        if ( id.equals(info.getId()) ) return root;

        JobTreeItem found = null;
        for ( JobTreeItem i : root.getChildren() ) {
            found = findJobTreeItem(i, id);
            if ( found != null ) break;
        }
        return found;
    }

    private static void marshalJobParts( JobTreeItem item, String id ) throws IOException {
        System.out.println("  marshalling...");

        // 子要素は不要なので切り離す
        item.getChildren().clear();

        OutputStream os = new FileOutputStream(id + ".xml");
        JAXB.marshal(item, os);
        os.close();
    }
}

実行結果は以下の通り、XML出力は成功。

# java MarshalJobParts
getJobTree/getJobFull starting...
  getJobFull...
  getJobFull...
  getJobFull...
  getJobFull...
getJobTree/getJobFull complted.
findJobTreeItem starting...
findJobTreeItem completed.
marshalJobParts starting...
  marshalling...
  marshalling...
  marshalling...
  marshalling...
marshalJobParts completed.

# ls -l *.xml
-rw-r--r-- 1 root root  4573 May  2 05:55 Job01.xml
-rw-r--r-- 1 root root  4783 May  2 05:55 Job02.xml
-rw-r--r-- 1 root root  4158 May  2 05:55 JobNet01.xml
-rw-r--r-- 1 root root  6107 May  2 05:55 JobUnit01.xml

出力されたXMLファイルを眺めて、「デフォルト値」として使えないであろう値が埋め込まれた場所を確認。Hinemosの画面と併せてみれば何を書き換えてあげないとまずいのかはわかる。これらの値はデシリアライズ後に確実に書き換えなければならない。
f:id:incarose86:20150501221311p:image

ジョブの前後関係

Job02 は Job01 の後に実行するように条件がついている。Job02 のシリアライズ結果でこの条件に対応しているのは以下の <object> の箇所だ。

<jobTreeItem>
    <data>
        (中略)
        <waitRule>
            <calendar>0</calendar>
            <calendarEndStatus>2</calendarEndStatus>
            (中略)
            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>0</type>
                <value>0</value>
            </object>
            (後略)

この <object> に対応するのは JobObjectInfo クラスで、以下のフィールドを持つ。

  • JobObjectInfo
    • Integer type ・・・ 条件のタイプ (先行ジョブの終了状態: 0 先行ジョブの終了コード: 1 時刻: 2)
    • String jobId ・・・ 先行ジョブを条件にする場合にジョブIDが設定される
    • String jobName ・・・ jobId で示されるジョブの名前(解りやすさのためだけのはず)
    • Integer value ・・・ 先行ジョブの終了状態: 0(Normal), 1(Warning), 2(Error), 4(*) 先行ジョブの終了コード: 終了コード
    • Long time ・・・ 時刻を条件にした場合に時刻

いろいろ条件をいれてシリアライズしてみて確認(定数の意味はマネージャ側のソースを見てもよい)。
f:id:incarose86:20150501230735p:image

            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>0</type>
                <value>0</value>
            </object>
            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>0</type>
                <value>1</value>
            </object>
            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>0</type>
                <value>2</value>
            </object>
            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>0</type>
                <value>4</value>
            </object>
            <object>
                <jobId>Job01</jobId>
                <jobName>ジョブネット1-ジョブ1</jobName>
                <type>1</type>
                <value>128</value>
            </object>
            <object>
                <time>39600000</time>
                <type>2</type>
                <value>0</value>
            </object>

ジョブネット定義の登録

登録の実行

材料も情報もそろったので、いよいよ登録だ。

ジョブユニット2を追加して、以下のジョブネット「ジョブユニット2-ジョブネット1」を登録する。
f:id:incarose86:20150501234835p:image

AddJobUnit02.java として上記ジョブネットを含むジョブユニット2を追加するプログラムを作成。

  • setCommand が FacilityId = FacilityName である前提になっている。setScope では FacilityName を設定するようで、厳密にはリポジトリからノード情報を引かなければならない。
import com.clustercontrol.ws.jobmanagement.*;
import javax.xml.ws.BindingProvider;
import java.util.Map;

import java.io.*;
import javax.xml.bind.JAXB;

public class AddJobUnit02 {
    // アクセス先/認証情報
    private static final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/JobEndpoint";
    private static final String USERNAME = "hinemos";
    private static final String PASSWORD = "hinemos";

    // ジョブ取得情報
    private static final String OWNER_ROLE_ID = "ALL_USERS";
    private static final boolean TREE_ONLY = true;

    // ジョブ部品
    private static final String PARTS_FILE_JOBUNIT = "JobUnit01.xml";
    private static final String PARTS_FILE_JOBNET  = "JobNet01.xml";
    private static final String PARTS_FILE_JOB     = "Job01.xml";

    private static JobEndpoint servicePort;


    public static void main( String[] args ) {
        initServicePort();

        // 実行(やたら例外多い...)
        try {
            final String JOBUNIT_ID   = "JobUnit02";
            final String JOBUNIT_NAME = "ジョブユニット2";
            final String JOBNET_ID    = "JobNet0201";
            final String JOBNET_NAME  = "ジョブユニット2-ジョブネット1";
            final String JOB_FACILITY = "garnet-vm09";

            System.out.println("JobUnit02 creation starting...");
            // ジョブユニット・ジョブネット・ジョブ生成
            JobTreeItem jobUnit = createJobUnit(JOBUNIT_ID, JOBUNIT_NAME, "");
            JobTreeItem jobNet  = createJobNet(jobUnit, JOBNET_ID, JOBNET_NAME, "");
            JobTreeItem job1    = createJob(jobUnit, "Job020101", "ジョブネット1-ジョブ1", "");
            JobTreeItem job2    = createJob(jobUnit, "Job020102", "ジョブネット1-ジョブ2", "");
            JobTreeItem job3    = createJob(jobUnit, "Job020103", "ジョブネット1-ジョブ3", "");
            JobTreeItem job4    = createJob(jobUnit, "Job020104", "ジョブネット1-ジョブ4", "");

            // ジョブの実行エージェント、コマンド指定
            setCommand(job1, JOB_FACILITY, "/tmp/job001.sh");
            setCommand(job2, JOB_FACILITY, "/tmp/job002.sh");
            setCommand(job3, JOB_FACILITY, "/tmp/job003.sh");
            setCommand(job4, JOB_FACILITY, "/tmp/job004.sh");

            // 階層設定 
            addChild(jobUnit, jobNet);
            addChild(jobNet, job1);
            addChild(jobNet, job2);
            addChild(jobNet, job3);
            addChild(jobNet, job4);

            // ジョブ前後関係設定
            addStatusWaitRule(job1, job2, 0);
            addStatusWaitRule(job1, job3, 0);
            addStatusWaitRule(job2, job4, 0);
            System.out.println("JobUnit02 creation complted.");

            System.out.println("registerJobunit starting...");
            servicePort.registerJobunit(jobUnit);
            System.out.println("registerJobunit 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 ( JobInvalid_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( JobMasterNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( InvalidSetting_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( InvalidObjectPrivilege_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( IOException e ) {
            e.printStackTrace(System.out);
        }
    }

    private static void initServicePort() {
        JobEndpointService service = new JobEndpointService();
        servicePort = service.getJobEndpointPort();

        // アクセス先設定/認証情報設定
        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);
    }

    private static JobTreeItem unmarshalJobParts( String filename, String jobUnitID, String jobID, String jobName, String jobDesc ) throws IOException {
        InputStream is = new FileInputStream(filename);
        JobTreeItem item = JAXB.unmarshal(is, JobTreeItem.class);
        is.close();

        // 固有情報消去/設定
        item.getData().setJobunitId(jobUnitID);
        item.getData().setId(jobID);
        item.getData().setName(jobName);
        item.getData().setDescription(jobDesc);
        for ( JobNotificationsInfo n : item.getData().getNotifications() ) n.setNotifyGroupId("JOB_MST-" + jobUnitID + "-" + jobID + "-0");
        item.getData().setCreateTime(System.currentTimeMillis());
        item.getData().setUpdateTime(item.getData().getCreateTime());
        if ( item.getData().getCommand() != null ) setCommand(item, "", "");

        return item;
    }

    private static JobTreeItem createJobUnit( String jobUnitID, String jobUnitName, String jobUnitDesc ) throws IOException {
        return unmarshalJobParts(PARTS_FILE_JOBUNIT, jobUnitID, jobUnitID, jobUnitName, jobUnitDesc);
    }
    private static JobTreeItem createJobNet( JobTreeItem jobUnit, String jobNetID, String jobNetName, String jobNetDesc ) throws IOException {
        return unmarshalJobParts(PARTS_FILE_JOBNET, jobUnit.getData().getId(), jobNetID, jobNetName, jobNetDesc);
    }
    private static JobTreeItem createJob( JobTreeItem jobUnit, String jobID, String jobName, String jobDesc ) throws IOException {
        return unmarshalJobParts(PARTS_FILE_JOB, jobUnit.getData().getId(), jobID, jobName, jobDesc);
    }

    private static void setCommand( JobTreeItem item, String facility, String startCommand ) {
        item.getData().getCommand().setFacilityID(facility);
        item.getData().getCommand().setScope(facility);
        item.getData().getCommand().setStartCommand(startCommand);
    }

    private static void addChild( JobTreeItem parent, JobTreeItem child ) {
        parent.getChildren().add(child);
    }

    private static void addStatusWaitRule( JobTreeItem pred, JobTreeItem succ, int status ) {
        JobObjectInfo rule = new JobObjectInfo();
        // 0: 先行ジョブの終了状態, 1: 先行ジョブの終了コード, 2: 時刻
        rule.setType(0);
        rule.setJobId(pred.getData().getId());
        rule.setJobName(pred.getData().getName());
        // 0: Normal, 1: Warning, 2: Error, 4: *
        rule.setValue(status);
        succ.getData().getWaitRule().getObject().add(rule);
    }
}

実行結果は以下の通り、静かに終わりすぎて逆に怖い。

# java AddJobUnit02
JobUnit02 creation starting...
JobUnit02 creation complted.
registerJobunit starting...
registerJobunit completed.

クライアントからの確認と実行

プログラムから登録したジョブユニットは起動済みのクライアントにすぐ表示されるわけではない。サーバから定義を再取得させる必要があるため、再ログインするか、画面右上の [Cancel] ツールチップがでるアイコンをクリックする。定義編集していなければ [Cancel] は単なるサーバからの定義再取得として使える。

無事にジョブユニット2およびその下のジョブネットが登録された!
f:id:incarose86:20150502000453p:image

実行!ジョブ1完了後にジョブ2とジョブ3が実行中。ジョブ4はジョブ2の終了待ち。前後関係の設定もうまくいっている。
f:id:incarose86:20150502001849p:image

完了、オールグリーン!
f:id:incarose86:20150502002734p:image


随分長くかかったがようやくできた。Hinemos 5 ではあっさりコマンドライン提供されたりして涙目になるのかな。wsimportとかJAXBとかに触れたので無駄にはならないはずだけど。

改善メモ

XMLファイルから読むと IOException の処理が必要になるので、文字列として埋め込んでしまい、JAXBにStringReaderから読み込ませる。

参考サイト