クリスマスはOpenStackをPythonワンライナーでキメる!

みなさんメリークリスマス。
おぷ☆すた関係者のみなさんの中にはクリスマス中止のお知らせが届いている方が多いかとは思いますが、2012年のJOSUG AdventCalendarネタの最後を飾るべくPythonワンライナーでOpenStack-APIを叩く方法を書いてみます。

■keystone認証とOpenStack APIについて

REST-APIを利用してOpenStackを操作するためにはkeystoneからトークンをもらわなくてはなりません。
その仕組みについて、以下のスライドにまとめてみました。

OpenStack Study#9 JOSUG

python-keystoneclientが提供するモジュールを利用せずにPythonなどから利用したいと考えている奇特なひとがいたら参考までにどうぞ:)

■とりあえずワンライナーでふつうに書いてみる

必要となる情報を環境変数から取得し、keystoneで認証を受けて許可されたendpointへの接続情報を得るプログラムをワンライナーで書いてみます。

  • 環境変数設定
    • ユーザID "foo"
    • パスワード "bar"
    • テナント名 "baz"
    • keystoneサーバIPアドレス "172.16.100.16"
    • keystoneサーバポート "5000"
export OS_USERNAME=foo
export OS_PASSWORD=bar
export OS_TENANT_NAME=baz
export OS_AUTH_URL="http://172.16.100.16:5000/v2.0"

ふつうに書いて、文を";"で繋くことによって1行にしてみた。
まぁふつうなので、あまり勝った気はしないし、おもしろくない。

python -c 'import re,os;from sys import stdout as s;from httplib import HTTPConnection as c;import json as j;e=os.environ;u=re.match(r"^(http|https)://(.*)(/.*)$",os.environ["OS_AUTH_URL"]).groups();x=c(u[1]);x.request("POST",u[2]+"/tokens","{\"auth\":{\"tenantName\":\"%s\",\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"}}}"%(e["OS_TENANT_NAME"],e["OS_USERNAME"],e["OS_PASSWORD"]),{"Content-Type":"application/json"});s.write(j.dumps(j.load(x.getresponse()),indent=2));x.close()'
  • とりあえずの結果
{
  "access": {
    "token": {
      "expires": "2012-12-24T08:30:33Z", 
      "id": "37b71bf29a9244d8a4214776ec5d467f", 
      "tenant": {
        "id": "30ecf00d44f743499397b0b6ee3d7a19", 
        "enabled": true, 
        "description": "Default Tenant", 
        "name": "baz"
      }
    }, 
    "serviceCatalog": [
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19", 
            "internalURL": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19", 
            "id": "56c68763275145fe9403b70851973df9"
          }
        ], 
        "type": "compute", 
        "name": "nova"
      }, 
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:9292/v1", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:9292/v1", 
            "internalURL": "http://172.16.100.16:9292/v1", 
            "id": "0db60374f7d44b168c5c7169f3fa8c86"
          }
        ], 
        "type": "image", 
        "name": "glance"
      }, 
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:8776/v1/30ecf00d44f743499397b0b6ee3d7a19", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:8776/v1/30ecf00d44f743499397b0b6ee3d7a19", 
            "internalURL": "http://172.16.100.16:8776/v1/30ecf00d44f743499397b0b6ee3d7a19", 
            "id": "e4a933ca12d344e3bd762b8a2a8ca7d7"
          }
        ], 
        "type": "volume", 
        "name": "volume"
      }, 
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:8773/services/Admin", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:8773/services/Cloud", 
            "internalURL": "http://172.16.100.16:8773/services/Cloud", 
            "id": "e66ed5a7256145f191e66c4dfdb77626"
          }
        ], 
        "type": "ec2", 
        "name": "ec2"
      }, 
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:8888/v1", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:8888/v1/AUTH_30ecf00d44f743499397b0b6ee3d7a19", 
            "internalURL": "http://172.16.100.16:8888/v1/AUTH_30ecf00d44f743499397b0b6ee3d7a19", 
            "id": "7ef0dbf8462a4c0dbb25920cf6282f72"
          }
        ], 
        "type": "object-store", 
        "name": "swift"
      }, 
      {
        "endpoints_links": [], 
        "endpoints": [
          {
            "adminURL": "http://172.16.100.16:35357/v2.0", 
            "region": "RegionOne", 
            "publicURL": "http://172.16.100.16:5000/v2.0", 
            "internalURL": "http://172.16.100.16:5000/v2.0", 
            "id": "38c31701d9bb4a6aa5d021424e2c90b8"
          }
        ], 
        "type": "identity", 
        "name": "keystone"
      }
    ], 
    "user": {
      "username": "foo", 
      "roles_links": [], 
      "id": "cb8ad9145ff44b858ec4cf9c8848b305", 
      "roles": [
        {
          "name": "memberRole"
        }
      ], 
      "name": "foo"
    }, 
    "metadata": {
      "is_admin": 0, 
      "roles": [
        "8fb8ac9f2f2849588e02c4d1bef99a07"
      ]
    }
  }
}

■もう少しワンライナーらしく

";"で繋いじゃワンライナーらしくないな...これじゃ元木さんにダメ出しされる。
そこで、
リスト内包表記lambdaglobals.__setitem__()やら"and"や"or"の評価を使って";"を可能な限り使わないように書き換えると、こんな感じ。

  • importのところは、まぁとりあえず";"をつかってもいいかな。
  • 代入文(=)を使用すると次の文との間を";"で繋ぐことになってワンライナーっぽくならないのでglobals().__setitem__()を使う。
  • globals().__setitem__()をタプル()にするとFalseを返すので、これを利用してorで次のタプルと繋ぐ。
  • 繰り替えされる処理は lambda で関数化

というあたりがポイントです。

■endpointリストを取得する

かろうじて700文字を切った。

  • コード(699文字)
python -c 'import re,os;from sys import stdout as s;from httplib import HTTPConnection as c;import json as j;(globals().__setitem__("e",os.environ))or(globals().__setitem__("r",r"^(http|https)://(.*)(/.*)$"))or(globals().__setitem__("f0",lambda a,b:re.match(a,b).groups()))or(globals().__setitem__("f1",lambda b:c(b)))or(globals().__setitem__("p",f1(f0(r,e["OS_AUTH_URL"])[1])))or(p.request("POST",f0(r,e["OS_AUTH_URL"])[2]+"/tokens","{\"auth\":{\"tenantName\":\"%s\",\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"}}}"%(e["OS_TENANT_NAME"],e["OS_USERNAME"],e["OS_PASSWORD"]),{"Content-Type":"application/json"}))or(s.write(j.dumps(j.load(p.getresponse()),indent=2)))or(p.close())'
仮想マシンのリストを取得する

なんかもう1000文字越えちゃった。3日もしたら忘れちゃうのでデバッグ不可能。

  • コード(1158文字)
python -c 'import re,os;from sys import stdout as s;from httplib import HTTPConnection as c;import json as j;(globals().__setitem__("e",os.environ))or(globals().__setitem__("r0",r"^(http|https)://(.*)(/.*)$"))or(globals().__setitem__("r1",r"^(http|https)://(.*:\d+)(/.*)$"))or(globals().__setitem__("f0",lambda a,b:re.match(a,b).groups()))or(globals().__setitem__("f1",lambda b:c(b)))or(globals().__setitem__("p",f1(f0(r0,e["OS_AUTH_URL"])[1])))or(p.request("POST",f0(r0,e["OS_AUTH_URL"])[2]+"/tokens","{\"auth\":{\"tenantName\":\"%s\",\"passwordCredentials\":{\"username\":\"%s\",\"password\":\"%s\"}}}"%(e["OS_TENANT_NAME"],e["OS_USERNAME"],e["OS_PASSWORD"]),{"Content-Type":"application/json"}))or(globals().__setitem__("z",j.load(p.getresponse())))or(globals().__setitem__("t",z["access"]["token"]["id"]))or(globals().__setitem__("u",f0(r1,[z["endpoints"][0]["publicURL"] for z in z["access"]["serviceCatalog"] if z["type"]=="compute"][0])))or(p.close())or(globals().__setitem__("p",f1(u[1])))or(p.request("GET",u[2]+"/servers","",{"Content-Type":"application/json","X-Auth-Token":t}))or(s.write(j.dumps(j.load(p.getresponse()),indent=2)))or(p.close())'
  • 結果

なんか仮想マシンのリストが取得できてるっぽい。

{
  "servers": [
    {
      "id": "e852c933-9dcd-470f-a74c-712fc5a81976", 
      "links": [
        {
          "href": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19/servers/e852c933-9dcd-470f-a74c-712fc5a81976", 
          "rel": "self"
        }, 
        {
          "href": "http://172.16.100.16:8774/30ecf00d44f743499397b0b6ee3d7a19/servers/e852c933-9dcd-470f-a74c-712fc5a81976", 
          "rel": "bookmark"
        }
      ], 
      "name": "vm02"
    }, 
    {
      "id": "92d8c492-84f4-41b8-9e4a-87612a439c67", 
      "links": [
        {
          "href": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19/servers/92d8c492-84f4-41b8-9e4a-87612a439c67", 
          "rel": "self"
        }, 
        {
          "href": "http://172.16.100.16:8774/30ecf00d44f743499397b0b6ee3d7a19/servers/92d8c492-84f4-41b8-9e4a-87612a439c67", 
          "rel": "bookmark"
        }
      ], 
      "name": "vm01"
    }, 
    {
      "id": "98c7449c-acf2-4cfc-bdc1-55ee1e153c74", 
      "links": [
        {
          "href": "http://172.16.100.16:8774/v2/30ecf00d44f743499397b0b6ee3d7a19/servers/98c7449c-acf2-4cfc-bdc1-55ee1e153c74", 
          "rel": "self"
        }, 
        {
          "href": "http://172.16.100.16:8774/30ecf00d44f743499397b0b6ee3d7a19/servers/98c7449c-acf2-4cfc-bdc1-55ee1e153c74", 
          "rel": "bookmark"
        }
      ], 
      "name": "vm00"
    }
  ]
}

という感じで、AdventCalendarの最終日を、こんな誰の役にも立たないワンライナーで締めくくるのもおぷ☆すたらしくていいかなぁ。
あ、lambda やリスト内包表記を使いこなす必要があるので、pythonの勉強をするにはちょっとは役に立つかも。