クリスマスの夜はOpenStackをPythonワンライナーでキメる(第1夜)

みなさんめりーくりすます!

この記事は、OpenStack Advent Calendar 2014の12/25の記事です。

日本OpenStackユーザ会のAdvent Calendarでは、第1回からクリスマス(またはイヴ)の夜はOpenStackをワンライナーで操作するという伝統芸をお送りしています。 2014年は4部くらいの構成でお送りする予定で、今日は、その第1夜です(今日は、まだワンライナーじゃないよ)

※決して2013年のワンライナーを解読するのに時間がかかって、締切に間に合わなそうだから、苦しまぎれに4回構成にしたってわけではありません

目標

今回の目標は、ワンライナー仮想マシンのリストを取得するまでです。毎年こんなんですが、、、

PythonのプログラムからOpenStackを操作する方法

ぼくも、正直普段は1の方法で利用しています。2の方法をとるのは、毎年このAdvent Calendarを書くときだけです(^^;

方法1. OpenStackのPythonクライアントライブラリを利用する

方法2. 直接REST APIを叩く

REST APIを利用するためのドキュメント

以下のドキュメント(本当によく書いてあるなぁ)を参考に実装していきます。

普通に書いてみる

python-novaclientを利用しない時点で、すでにあまり普通ではないのですが、まぁ普通に書いてみます。

novaが管理する仮想マシンのリストを取得する手順

OpenStackのREST APIを利用します。基本的にPythonが標準で提供しているライブラリのみで乗り切ります。

1. keystoneに認証してもらってトークンIDとサービスカタログを取得する

サービスカタログには、コンポーネントのエンドポイントURLが - POSTメソッド - URL: $OS_AUTH_URL/tokens - ヘッダ

Content-Type: application/json
Accept: application/json
  • リクエスト(JSON形式)
{
    "auth": {
        "tenantName": $OS_TENANT_NAME,
        "passwordCredentials": {
            "username": $OS_USERNAME,
            "password": $OS_PASSWORD
        }
    }
}
  • レスポンス
{
    "access": {
        "token": {
            ... 省略 ...
            "id": "aaaaa-bbbbb-ccccc-dddd",  <-取得したトークンのID
            "tenant": {
                ... 省略 ...
                "id": "fc394f2ab2df4114bde39905f800dc57", <-取得したテナントID
                "name": $OS_TENANT_NAME
            }
        },
    ... 省略 ...
   }
}

2. keystoneからテナントIDを取得する

Content-Type: application/json
Accept: application/json
X-Auth-Token: トークンID  <-1.で取得したトークンID
  • レスポンス(JSON形式)
{
    "tenants": [
        {
            "id": テナントID,
            "name": "tenant-A",
            ...省略...
        },
        {
            "id": テナントID,
            "name": "tenant-B",
            ...省略...
        }
    ],
    "tenants_links": []
}

3. リージョン名とテナントIDからエンドポイントURLを特定する

$OS_REGION_NAMEと2.で取得したテナントIDを利用して1.で取得したサービスカタログから、nova APIのエンドポイントURLを特定します。

4. novaから仮想マシンのリストを取得する

  • GETメソッド
  • URL: novaAPIのエンドポイントURL/servers
  • ヘッダ
Content-Type: application/json
Accept: application/json
X-Auth-Token: トークンID  <-1.で取得したトークンID
  • レスポンス(JSON形式)
だーーーーっとJSON形式で構造化された仮想マシン情報

ソースコード(nova_list.py)

python-novaclientを使わないと、おそろしくめんどくさい...

#!/usr/bin/env python

import json
import re
import urllib
import urllib2
from os import environ as env


def throw_post(url, body, header):
    request = urllib2.Request(url=url,
                              data=json.dumps(body),
                              headers=header)
    return json.loads(urllib2.urlopen(request).read())


def throw_get(url, header):
    request = urllib2.Request(url=url, headers=header)
    return json.loads(urllib2.urlopen(request).read())


def main():
    os_auth_url = env.get('OS_AUTH_URL').rstrip('/')
    os_user = env.get('OS_USERNAME')
    os_passwd = env.get('OS_PASSWORD')
    os_region = env.get('OS_REGION_NAME')
    os_tenant = env.get('OS_TENANT_NAME')

    header = {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
    }

    #
    # get auth-token and catalog info
    #
    auth_header = header.copy()
    api_tokens_url = os_auth_url + '/tokens'
    auth_body = dict(auth=dict(tenantName=os_tenant,
                               passwordCredentials=dict(username=os_user,
                                                        password=os_passwd)))
    auth_response = throw_post(api_tokens_url, auth_body, auth_header)
    token_info = auth_response['access']['token']
    catalog_info = auth_response['access']['serviceCatalog']

    #
    # get tenant-id
    #
    tenant_header = header.copy()
    api_tenants_url = os_auth_url + '/tenants'
    tenant_header['X-Auth-Token'] = token_info['id']
    tenant_response = throw_get(api_tenants_url, tenant_header)
    for i in tenant_response['tenants']:
        if i['name'] == os_tenant:
            tenant_id = i['id']

    #
    # get endpoint-url
    #
    for i in catalog_info:
        if i['type'] == 'compute':
            for j in i['endpoints']:
                if j['region'] == os_region and j['tenantId'] == tenant_id:
                    os_nova_url = j['publicURL']

    #
    # get instance list
    #
    servers_header = header.copy()
    servers_header['X-Auth-Token'] = token_info['id']
    api_servers_url = os_nova_url + '/servers'
    servers_response = throw_get(api_servers_url, servers_header)
    for i in servers_response['servers']:
        print '%s %s' % (i['id'], i['name'])


if __name__ == '__main__':
    # usage: python nova_list.py
    # description: get instance-list such as "nova list"
    main()


#
# [EOF]
#

実行してみる

ちゃんとリストは取得できますね。

$ python nova_list.py
4f0a63bb-f79e-4850-ad5f-3156d677940b test-vm00
a4ca06c9-aff5-4363-b5aa-7a85eb18500c test-vm01
6e7c2192-253c-4b6d-94b4-f1ada8a993d8 test-vm02
213299ca-1c13-4b5e-8ce6-bbb30758f340 test-vm03

明日からが本番だぜ!

明日からは、この普通のコードをワンライナーに直して行く予定です。 あんまり期待しないでまっててね。今年のOpenStackのアドカレは、まだまだおわらないぜ!