まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

自分は(慣れの要素が大きいのですが)CentOS は未だに 6.x をメインに使っています。「別に不便じゃないし~」程度にしか考えていなかったのですが、その姿勢を改めた方がいいかなあ、と思うことがあったので、その時の様子と対処がいかに大変だったかをまとめました。

Linux で比較的新しめのアプリケーションを使おうとすると、たまにこんなエラーメッセージが出ることがあります:
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.14' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `CXXABI_1.3.5' not found (required by ./xxxxx)
./xxxxx: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /opt/Xxxxx/libnode.so)

これはシステムにインストールされている libstdc++(GLIBC) ライブラリが古く、動かそうとしているアプリケーションが要求するバージョン(上の例では 3.4.14 や 3.4.15)と合わないためのエラーが発生していることを意味しています。


※実はこのエラーはこのブログエントリの最後に「Rodeo は CentOS/RHEL 7.x 上で動かすのが無難」と書いた時の話です。CentOS 6.8 で普通に Rodeo をインストールして動かすと、ここで紹介しているようなエラーに遭遇するのでした:
CentOS に Rodeo(Python IDE)をインストールする


実際にシステムに組み込まれている libstdc++ のバージョンを確認するには以下のコマンドを実行します(青字は出力結果):
# strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH

この例の場合、3.4.1 から 3.4.13 までは組み込まれているシステムであることが分かります。このシステムで上記アプリケーションを動かそうとすると 3.4.14 や 3.4.15 は組み込まれていないため、上述のようなエラーが発生してしまう、ということになるのでした。エラーの原因はこれで特定できました。


この状況を回避するには libstdc++ を必要なバージョンが組み込まれた新しいものと入れ替える必要があります。そして libstdc++ は gcc に含まれるモジュールなので、(まず gcc ビルドの前提となる glibc を更新して、)gcc をビルドして、そこから libstdc++ を取り出して既存のものと入れ替える、というちと面倒な手順を実行する必要があるのでした。要するに libstdc++ を新しくするためには gcc をビルドする必要があるのです。。

更に、ここまでは全ての Linux ディストリビューションに言える内容なのですが、実際にこの手順を行う場合、CentOS/RHEL 6.x だとものすごく面倒な手順が待ち構えているのでした。実際に行った長い道のりを以下に紹介します。



まず、gcc のビルドに必要なヘッダーファイルをインストールします。具体的には gmp-devel, mpfr-devel, libmpc-devel が必要で、かつ 64bit 環境では glibc-devel.i686 までも必要になります。このうち libmpc-devel 以外は標準の yum リポジトリからインストールできますが、libmpc-devel は EPEL から入手する必要があります。

というわけで、まずは EPEL リポジトリをダウンロード:
# yum install epel-release

続けて上記3つのヘッダファイルと 32bit 版 glibc ヘッダをダウンロードします:
# yum install gmp-devel mpfr-devel libmpc-devel
# yum install glibc-devel.i686

ヘッダファイルの準備ができた所で最初に前提となる glibc をダウンロードしてビルドします。以下の例では /usr/local/src 以下にソースコードを展開しています:
# cd /usr/local/src
# wget http://ftp.gnome.org/pub/gnome/sources/glib/2.32/glib-2.32.4.tar.xz
# tar Jxvf glib-2.32.4.tar.xz
# rm glib-2.32.4.tar.xz
# cd glib-2.32.4
# ./configure
# make
# make install

そして、ここからやっと gcc のソースコードをダウンロードしてビルドします。ちなみにここから先の手順を行うためには 5GB 弱の空きディスク容量が必要になります。また make の実行を開始してから完了するまでに数時間かかります。スペックにもよりますが、昼寝ではなく一度マジ寝して起きる頃に終わっている、というくらいの時間を見ておく必要があります (^^;:
# cd /usr/local/src
# curl -LO http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-4.8.4/gcc-4.8.4.tar.gz
# tar fxz gcc-4.8.4.tar.gz
# rm gcc-4.8.4.tar.gz
# cd gcc-4.8.4
# ./configure
# make (僕の仮想環境ではここで4~5時間かかりました・・)
# make install

・・・で、gcc のビルドが無事に完了したら libstdc++ の入れ替えを行います。まずは現在の状況を確認します:
# ls -l /usr/lib64/libstdc*

lrwxrwxrwx 1 root root     19  1月 23 12:35 2017 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.13
-rwxr-xr-x 1 root root 989840  5月 10 18:38 2016 /usr/lib64/libstdc++.so.6.0.13

ビルドした結果から、目的のライブラリをコピーして、シンボリックリンクを作り直します:
# cp /usr/local/src/gcc-4.8.4/x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.19 /usr/lib64
# cd /usr/lib64
# mv libstdc++.so.6 libstdc++.so.6.bak
# ln -s libstdc++.so.6.0.19 libstdc++.so.6
# ls -l /usr/lib64/libstdc*

lrwxrwxrwx 1 root root      19  2月 14 12:07 2017 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.19
-rwxr-xr-x 1 root root  989840  5月 10 18:38 2016 /usr/lib64/libstdc++.so.6.0.13
-rwxr-xr-x 1 root root 6468627  2月 14 12:06 2017 /usr/lib64/libstdc++.so.6.0.19
lrwxrwxrwx 1 root root      19  5月 26 11:23 2016 /usr/lib64/libstdc++.so.6.bak -> libstdc++.so.6.0.13

最後に目的のバージョン(今回であれば 3.4.14 や 3.4.15)が有効になっているかどうかを確認します:
# strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.16
GLIBCXX_3.4.17
GLIBCXX_3.4.18
GLIBCXX_3.4.19
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH

期待通りのライブラリに更新できました。これで CentOS 6.x でも Rodeo が動くようになりました。いやあ、長かった。めでたし、めでたし:
rodeo_centos6
↑今回のブログエントリは、この「CentOS 6.x 上で動く Rodeo」のスクリーンショットを撮るまでがいかに大変であったかを分かっていただきたいがために書きました。 (^^;


ここまで確認できれば glibc や gcc のソースコード一式は不要なので、消してしまっても構いません(何しろこの環境のために 5GB 前後のディスクを専有しているので・・・)
# cd /usr/local/src
# rf -rf glib-2.32.4 # rm -rf gcc-4.8.4




(参考)
http://qiita.com/dozo/items/de393588d5c267794ced

https://www.saintsouth.net/blog/update-libstdcpp-on-centos6/



 

前2回の続きです:
IBM Bluemix にカスマムサービスを追加する(1/3)
IBM Bluemix にカスマムサービスを追加する(2/3)


前回作成した REST API を実際に IBM Bluemix のカスタムサービスとして登録し、利用するまで(正確には使用を終えてカスタムサービスから削除するところまで)の手順を紹介します。なおこの手順はコマンドラインツールである cf が必要になるので、インストールしていない場合は各自の環境にあった cf をあらかじめダウンロード&インストールしておいてください:
https://github.com/cloudfoundry/cli/releases


また、今回カスタムサービスとして登録する REST API は前回紹介した app.js が https://dotnsf-operation.mybluemix.net/ にデプロイされて動いているものとします。以下の説明でのこのホスト名部分を実際にみなさんがデプロイしたホスト名に合わせて読み替えてください:
2017021401

2017021401


まず最初に、以下で登録するカスタムサービスをバインドして動作確認するためのランタイムを用意しておきます。動作確認用のアプリケーション(後述)を PHP で用意したので、IBM Bluemix 上に PHP のランタイムを1つ作成します(以下の例では teyande-php-env という名前で作成しています)、なおこの段階ではサービスは何もバインドしないでください:
2017021402


このランタイムに以下の内容の PHP アプリケーションをプッシュします:
https://github.com/dotnsf/phpEnv


上記アプリケーション(index.php)はシンプルに「ランタイムサーバー上の全環境変数とその値を表示する」というアプリケーションです。プッシュ後にサーバーにアクセスすると以下のような画面になります。この時点ではサービスを1つもバインドしていないので、環境変数 VCAP_SERVICES の値は空オブジェクトを示す "{}" となっていることが確認できるはずです:
2017021403


ここまでで準備は完了です。では前回紹介したサービスを IBM Bluemix のカスタムサービスとして登録した上でインスタンス化し、このランタイムにバインドした上で、この環境変数がどのように変わるかを確認してみます。

コマンドプロンプトかターミナルを起動し、上記の PHP ランタイムと同じデータセンター(この場合は US-SOUTH)の自分の組織に cf コマンドでログインします:
2017021404


カスタムサービスを利用するにためには Cloud Foundry の「サービスブローカー」(カスタムカタログのように理解してください)に登録しておく必要があります。まず現在のサービスブローカー一覧を確認するため "cf service-brokers" コマンドを実行して、この時点では何も登録されていないことを確認します:
2017021301
 ↑"No service brokers found"(何も登録されていません)です


ではサービスブローカーを新規に1つ作成します。以下のパラメータを付けてコマンドを実行します:
> cf create-service-broker サービスブローカー名 認証ユーザー名 認証パスワード サービスの基本URL --space-scoped
2017021302


今回、サービスブローカー名は "dotnsf-operation" とします。認証のユーザー名とパスワードは dotnsf-operation の Catalog API などが実行される時用に指定した auth_user / auth_pass の値を指定します(詳しくは前回の記事参照)。サービスの基本 URL は今回 "https://dotnsf-operation.mybluemix.net" です。そして最後にオプションの "--space-scoped"(同一組織内のユーザーが使えるサービスとするための指定)を加えています。

コマンドが成功したことを確認し、再度 "cf service-brokers" を実行してみます。先程は結果に何も含まれていませんでしたが、今度は直前に作成したばかりの "dotnsf-operation" サービスブローカーが、指定した URL で登録されていることを確認してください:
2017021303


これで dotnsf-operation API がカスタムカタログに登録された状態になりました。ただ IBM Bluemix のウェブ UI ではカスタムカタログを表示する画面が存在していないので、この時点ではまだウェブ UI には現れませんが、cf からは同様にインスタンスを作成することができるようになっています。

というわけで、続けて cf でこのサービスをインスタンス化してみます:
> cf create-service サービスブローカー名 プラン名 サービス名

サービスブローカー名は dotnsf-operation の Catalog API(GET /v2/catalog) 内で定義したもの(今回の場合は dotnsf-operation-service)を指定します。プランも同様に Catalog API 内で定義したもの(今回は "free" と "enterprise" の2つ)の中から指定できますが、今回は "free" を指定しています。最後に IBM Bluemix 内でのサービス名として "dotnsf-operation-1" と指定しています:
2017021304


ここまでの手続きが完了すると、dotnsf-operation サービスがインスタンス化され、ウェブ UI の利用中サービス一覧の中にも表示されるようになります:
2017021301


この時点でサービスを選択し、管理タブを開くと Dashboard API で定義した内容が表示されるはずです(https で Dashboard API にアクセスできることが条件です):
2017021000


ではこのサービスを最初に作成した PHP ランタイムにバインドしてみましょう。PHP ランタイムの画面に移動し、接続タブの「既存に接続」をクリックします:
2017021302


現在利用中のサービスインスタンスの一覧が表示されます。その中に "dotnsf-operation-1" という名前のサービスが含まれているはずなので、これを選択して「接続」をクリックします。そしてこの内容をランタイムの環境変数にも反映させるため「再ステージング」します:
2017021303


改めて同ランタイムの接続タブを参照すると、"dotnsf-operation-1" サービスが追加されていることを確認できます。ここで「資格情報の表示」をクリックします:
2017021304


今回の dotnsf-service の Bind API では credentials 情報として uri だけを追加していました。そのためこのサービスインスタンスの資格情報にも uri 1つだけが credentials 情報として表示されていることが確認できるはずです:
2017021305


改めてランタイムのトップ画面にアクセス(リロード)します。今回は先程と異なり dotnsf-operation-1 サービスがバインドされているので、環境変数 VCAP_SERVICES は空オブジェクトではなく、dotnsf-operation-1 の情報が表示されているはずです。credentials 情報も先程確認したものと同じものが表示されています:
2017021306



・・・というわけで、IBM Bluemix のカスタムサービスを作成して、登録して、インスタンス化して、ランタイムとバインドして環境変数に反映させる、という一連の作業ができることを確認できました!

最後に使い終わったサービスブローカーを削除する手順を紹介しておきます。まず(同サービスブローカーを使っているランタイムからのバインドを全て解除した上で)サービス一覧画面からインスタンス化されたサービスを削除します:
2017021305


サービスが削除されるとウェブ UI からは見えなくなります。が、まだサービスブローカーには登録されているので、以下の cf コマンドでサービスブローカーからも削除します:
> cf delete-service-broker サービスブローカー名
2017021306


このコマンドが成功すると、サービスブローカー一覧からも削除されます。念のため "cf service-brokers" コマンドで確認します:
2017021307




久しぶりの超大作ブログとなり、3回に分けて紹介しましたが、IBM Bluemix のカスタムサービスを作るために実装しないといけない内容や、実装後の登録方法などを紹介できたつもりです。自分が作った REST API を IBM Bluemix に統合して使いたい場合や、公式なサードパーティ API としての登録を検討する場合に必要となる実装がどんなものかを説明しました。

今回紹介した内容はあくまで「同一組織内でのみ有効なカスタムサービス」の登録方法および手順の紹介でしたが、全 IBM Bluemix ユーザー向けに実装する場合であっても同様の API 実装は必要になります。そういったエコシステムを活性化する上での役立つ情報になれば幸いです。


(参考)
 https://www.ibm.com/blogs/bluemix/2017/01/extend-bluemix-service-broker/

前回の続きです。IBM Bluemix に独自のカスタムサービスを追加する手順を紹介しています:
IBM Bluemix にカスマムサービスを追加する(1/3)

前回紹介した Node.js で実装済みの REST API を IBM Bluemix のカスタムサービスとして利用できるように改良する、その改良内容を紹介します。


前回のおさらいになりますが、改良前の API はこちらです:
https://github.com/dotnsf/dotnsfOperation/blob/master/app0.js
//. app0.js
var express = require( 'express' ),
    cfenv = require( 'cfenv' ),
    appEnv = cfenv.getAppEnv(),
    app = express();


//. Service APIs
app.get( '/', function( req, res ){
  var html = '<title>My Broker</title>';
  html += '<h1>My Broker</h1>';
  html += '<p>ドキュメントは<a target="_blank" href="./doc">こちら</a>を参照ください</p>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.get( '/:operation/:x/:y', function( req, res ){
  var operation = req.params.operation;
  var x = parseFloat( req.params.x );
  var y = parseFloat( req.params.y );
  var z = 0;

  if( operation == 'plus' ){
    z = x + y;
  }else if( operation == 'minus' ){
    z = x - y;
  }else if( operation == 'multiply' ){
    z = x * y;
  }else if( operation == 'divide' ){
    z = x / y;
  }

  res.writeHead( 200, [ { 'Content-Type': 'text/plain' } ] );
  res.write( '' + z );
  res.end();
});

app.get( '/doc', function( req, res ){
  var html = '<title>My Operation Document</title>';
  html += '<h1>My Operation Document</h1>';
  html += '<p>This is a document for my operation</p>';
  html += '<hr/>';
  html += '/plus/x/y => return ( x + y )<br/>';
  html += '/minus/x/y => return ( x - y )<br/>';
  html += '/multiply/x/y => return ( x + y )<br/>';
  html += '/divide/x/y => return ( x / y )<br/>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

var service_port = appEnv.port ? appEnv.port : 1337;
app.listen( service_port );
console.log( "server starting on " + service_port );


最終型を先に紹介しますが、これを以下のように改良します(追加箇所を赤くしています):
https://github.com/dotnsf/dotnsfOperation/blob/master/app.js
//. app.js
var express = require( 'express' ),
    basicAuth = require( 'basic-auth-connect' ),
    bodyParser = require( 'body-parser' ),
    cfenv = require( 'cfenv' ),
    uuid = require( 'uuid' ),
    appEnv = cfenv.getAppEnv(),
    app = express();

app.use( bodyParser.urlencoded( {
    extended: true
}));
app.use( bodyParser.json() );

//. API Version
const X_BROKER_API_VERSION = 2.0;
const X_BROKER_API_VERSION_NAME = 'X-Broker-Api-Version';

//. Plans
const plan0 = {
  'id': uuid.v4(),
  'name': 'free',
  'description': 'Free plan',
  'free': true
};
const plan1 = {
  'id': uuid.v4(),
  'name': 'enterprise',
  'description': 'Enterprise plan',
  'free': false
};

//. service endpoint
var service_host = 'dotnsf-operation.mybluemix.net';
var service_port = appEnv.port ? appEnv.port : 1337;
var service_base = service_host; // + ':' + service_port;
var service_instance = 'http://' + service_base + '/operation/';
var service_dashboard = 'http://' + service_base + '/operation/dashboard';

//. Service
var my_service_id = uuid.v4();
var my_service = {
  'id': my_service_id,
  'name': 'dotnsf-operation-service',
  'description': '四則演算 API',
  'bindable': true,
  'tags': [ 'private' ],
  'plans': [ plan0, plan1 ],
  'dashboard_client': {
    'id': uuid.v4(),
    'secret': 'secret-1',
    'redirect_uri': 'http://bluemix.net'
  },
  'metadata': {
    'displayName': 'My operational service',
    'longDescription': 'WebAPI で四則演算を行う',
    'providerDisplayName': 'dotnsf',
    'documenttationUrl': 'http://' + service_base + '/doc',
    'supportUrl': 'https://stackoverflow.com/questions/tagged/ibm-bluemix'
  }
};


//. Auth for /v2/*
const auth_user = 'username';
const auth_pass = 'password';
app.all( '/v2/*', basicAuth( function( username, password ){
  return username === auth_user && password === auth_pass;
}));


//. Cloud Foundry Broker APIs

//. Catalog
app.get( '/v2/catalog', function( req, res ){
  console.log( 'GET /v2/catalog' );

  var api_version = req.get( X_BROKER_API_VERSION_NAME );
  if( !api_version || X_BROKER_API_VERSION > parseFloat( api_version ) ){
    res.writeHead( 412, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Precondition failed. Missing or imcompatible ' + X_BROKER_API_VERSION_NAME + '. Expecting version ' + X_BROKER_API_VERSION + ' or later.' );
    res.end();
  }else{
    var services = {};
    services['services'] =[];
    services['services'][0] = my_service;
    res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
    res.write( JSON.stringify( services ) );
    res.end();
  }
});

//. Provision
app.put( '/v2/service_instances/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  var provision_details = req.body;
  //. {
  //    "service_id": "<service-guid>",
  //    "plan_id": "<plan-guid>",
  //    "organization_guid": "<org-guid>",
  //    "space_guid": "<space-guid>"
  //  }
  console.log( 'PUT /v2/service_instances/' + instance_id );
  console.log( provision_details );

  //. Provisioning process here.

  //. Return basic service information
  var new_service = {};
  //new_service['dashboard_url'] = service_dashboard + '/' + instance_id;
  new_service['dashboard_url'] = service_dashboard + '/' + instance_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( new_service ) );
  res.end();
});


//. Deprovision
app.delete( '/v2/service_instances/:service_id', function( req, res ){
  var service_id = req.params.service_id;

  console.log( 'DELETE /v2/service_instances/' + service_id );

  //. Deprovisioning process here.

  //. Return basic information
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( {} ) );
  res.end();
});

//. Bind
app.put( '/v2/service_instances/:service_id/service_bindings/:binding_id', function( req, res ){
  var service_id = req.params.service_id;
  var binding_id = req.params.binding_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  var binding_details = req.body;
  //. {
  //    "plan_id": "<plan-guid>",
  //    "service_id": "<service-guid>",
  //    "app_guid": "<app-guid>"
  //  }
  console.log( 'PUT /v2/service_instances/' + service_id + '/service_bindings/' + binding_id );
  console.log( binding_details );

  //. Binding process here.

  //. Return result to the Bluemix Cloud Controller
  var result = {};
  result['credentials'] = {};
  result['credentials']['uri'] = 'http://' +  service_base + '/';
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( result ) );
  res.end();
});

//. Unbind
app.delete( '/v2/service_instances/:service_id/service_bindings/:binding_id', function( req, res ){
  var service_id = req.params.service_id;
  var binding_id = req.params.binding_id;

  console.log( 'DELETE /v2/service_instances/' + service_id + '/service_bindings/' + binding_id );

  //. Unbinding process here.

  //. Return basic information
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( {} ) );
  res.end();
});



//. Service related functions
app.all( '/operation/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  console.log( req.method + ' /operation/' + instance_id );

  //. Return service information
  var service_info = {};
  service_info['greeting'] = instance_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( service_info ) );
  res.end();
});

app.get( '/operation/dashboard/:instance_id', function( req, res ){
  var instance_id = req.params.instance_id;

  console.log( req.method + ' /operation/' + instance_id );

  //. Return hard-coded HTML
  var html = '見つかっちゃった。。。(*/ω\*)';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.all( '/operation/:instance_id/:binding_id', function( req, res ){
  var instance_id = req.params.instance_id;
  var binding_id = req.params.binding_id;

  var contentType = req.get( 'Content-Type' );
  if( contentType != 'application/json' ){
    res.writeHead( 415, [ { 'Content-Type': 'text/plain' } ] );
    res.write( 'Unsupported Content-Type: expecting application/json.' );
    res.end();
  }

  //. Return service information
  var service_info = {};
  service_info['instance_id'] = instance_id;
  service_info['binding_id'] = binding_id;
  res.writeHead( 200, [ { 'Content-Type': 'application/json' } ] );
  res.write( JSON.stringify( service_info ) );
  res.end();
});



//. Service APIs
app.get( '/', function( req, res ){
  var html = '<title>My Broker</title>';
  html += '<h1>My Broker</h1>';
  html += '<p>ドキュメントは<a target="_blank" href="./doc">こちら</a>を参照ください</p>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.get( '/:operation/:x/:y', function( req, res ){
  var operation = req.params.operation;
  var x = parseFloat( req.params.x );
  var y = parseFloat( req.params.y );
  var z = 0;

  if( operation == 'plus' ){
    z = x + y;
  }else if( operation == 'minus' ){
    z = x - y;
  }else if( operation == 'multiply' ){
    z = x * y;
  }else if( operation == 'divide' ){
    z = x / y;
  }

  res.writeHead( 200, [ { 'Content-Type': 'text/plain' } ] );
  res.write( '' + z );
  res.end();
});

app.get( '/doc', function( req, res ){
  var html = '<title>My Operation Document</title>';
  html += '<h1>My Operation Document</h1>';
  html += '<p>This is a document for my operation</p>';
  html += '<hr/>';
  html += '/plus/x/y => return ( x + y )<br/>';
  html += '/minus/x/y => return ( x - y )<br/>';
  html += '/multiply/x/y => return ( x + y )<br/>';
  html += '/divide/x/y => return ( x / y )<br/>';
  res.writeHead( 200, [ { 'Content-Type': 'text/html; charset=UTF8' } ] );
  res.write( html );
  res.end();
});

app.listen( service_port );
console.log( "server starting on " + service_port );

追加した箇所をで記載しています。通常の Web API を IBM Bluemix(Cloud Foundry) サービスとして対応させる場合には少なくともこれだけの追加が必要になる、と理解してください。

具体的な追加の内容は8種類12個の Web API を追加しています。以下にそれぞれの内容を紹介します。

(1) Catalog API (GET /v2/catalog)

(コマンドライン版の)サービス一覧に表示される情報を取得する API です。リクエスト時に HTTP ヘッダ 'X-Broker-Api-Version' が設定されており、その値が実装 API バージョン(この場合は 2)以上でない場合はエラーとします。実行後の返り値についてはソースコード内の変数: services 参照していただきたいのですが、返り値のオブジェクトの中にサービス名称や説明、選択可能なプランや資料情報 URL などを含めます。


(2) Provision API (PUT /v2/service_instances/:instance_id)

サービスをプロビジョンして作成する際にコールされる API です。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。実際にはパラメータ :instance_id の値を使ってインスタンスを生成して管理するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値についてはソースコード内の変数: new_service を参照してください。


(3) Deprovision API (DELETE /v2/service_instances/:instance_id)

サービスでデプロビジョンして削除する際にコールされる API です。実際にはパラメータ :instance_id の値を使って管理中のインスタンス情報を削除するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値は空オブジェクトです。


(4) Bind API (PUT /v2/service_instances/:instance_id/service_bindings/:binding_id)

サービスのバインドを作成する際にコールされる API。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。実際にはパラメータ :instance_id , :binding_id の値を使ってバインドを生成して管理するところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値の中にランタイムの環境変数 VCAP_SERVICES に含まれる credentials 情報を含めます(つまり実行時用の id や password が必要な場合は、ここで作成して返り値に含める、という実装を行ってください)。具体的にはソースコード内の変数: result を参照してください。


(5) Unbind API (DELETE /v2/service_instances/:instance_id/service_bindings/:binding_id)

サービスをアンバインドして削除する際にコールされる API です。実際にはパラメータ :instance_id , :binding_id の値を使って管理中のバインド情報を削除する(例えば (4) で生成した username と password を無効にする、など)ところまでを実装する必要があるのですが、今回のサンプル内ではそこまでは実装していません。実行後の返り値は空オブジェクトです。


↑上記 (1) ~ (5) の API 実行時には Basic 認証をかけておきます。


(6) Instance Info APIs (PUT,GET,DELETE /operation/:instance_id)

サービスインスタンス情報を作成(PUT)/取得(GET)/削除(DELETE)するための API です。具体的な返り値はコースコード内の変数: service_info を参照してください。


(7) Bind Info APIs (PUT,GET,DELETE /operatoin/:instance_id/:binding_id)

サービスインスタンスとのバインド情報を作成(PUT)/取得(GET)/削除(DELETE)するための API です。HTTP ヘッダ 'Content-Type' の値が 'application/json' 以外の場合はエラーとします。具体的な返り値はコースコード内の変数: service_info を参照してください。


(8) Dashboard (GET /operation/dashboard/:instance_id)

サービスの管理情報やダッシュボードとして表示する画面です。IBM Bluemix のウェブ画面に表示される内容です:
2017021401

  ↑この「管理」タブの内容です

なお、この中身が正しく表示されるためには https でこの API が実行できる必要があります。具体的な返り値の例はコースコード内の変数: html を参照してください。



元のコード(app0.js)に、上記8つの API を追加実装することで IBM Bluemix のサービスとしての最低限の振る舞いが可能になります。また (1) ~ (5) の API については認証を付けることができるので、そのための実装も追加したものが新しいコード(app.js)、ということになります。この追加実装のためにいくつかのミドルウェアも追加で必要になったため、新しいコードではその部分も含めて追加しています。


そして、パッケージ情報を記述したファイル(packages.json)や、IBM Bluemix 上で実行するための manifest.ymlREADME.md までを追加した一通りのセットがこちらです:
https://github.com/dotnsf/dotnsfOperation

2017021402


IBM Bluemix 環境で動かす場合の使い方は README.md にも記載していますが、ダウンロードするか git clone した後に manifest.yml 内のアプリ名、ホスト名、ドメイン名をランタイム環境に合わせて編集します。加えて app.js 内に "dotnsf-operation.mybluemix.net" というホスト上で、"dotnsf-operatoin-service" というサービス名で動かすことを前提とした内容が記述されているので、該当箇所を実際の内容に合わせて書き換えます。また Basic 認証は username : password となっているので、ここを変更する場合はコード内の auth_user および auth_pass 変数を書き換えてください。

こうしてコードを追加した app.js を IBM Bluemix にプッシュすることでサービスが稼働することになります。なお、サービスそのものは IBM Bluemix 内で稼働させる必要はありませんが、上記 (8) の API は https 対応している必要があるので、IBM Bluemix 以外の環境で動かす場合は https 対応できる環境下で稼働させてください。


実際に dotnsf-operation.mybluemix.net 上にプッシュして動かしている様子が以下になります:
2017021402
 ↑元々の機能+αが実装された API を動かしています。

これで IBM Bluemix のサービスとして統合可能な Web API の用意ができました。次回は実際にこの API を IBM Bluemix のカスタムサービスとして登録し、ランタイムにバインドして使ってみる様子とその手順を紹介します。


(追記)
続きはこちら:
http://dotnsf.blog.jp/archives/1064290977.html

このページのトップヘ