何の話かというと
OpenStackのPython用Client Libraryは、pydoc程度しかドキュメントがなくて困っていたので、pydocから見える情報をベースにして(勘と経験で)利用方法の参考になりそうなサンプルコードを作ってみました。
それぞれのクライアントは、「Clientオブジェクトを取得して、それにくっついたManagerから各コンポーネントのオブジェクトを作って・・・、」という流れは共通していますが、微妙に細かいところで仕様の不統一があります。引数でコンポーネントを指定する際に、オブジェクトを渡す場合とIDを渡す場合があったり、list()で返ってくるのが、オブジェクトのリストだったり、Iteratorだったりします。
前提環境
Fedora 18 + RDO(Grizzly)の環境でテストしています。
# rpm -qa | grep -E "python-.*client" python-glanceclient-0.9.0-3.fc19.noarch python-cinderclient-1.0.4-1.fc19.noarch python-novaclient-2.13.0-1.fc18.noarch python-swiftclient-1.4.0-1.fc19.noarch python-quantumclient-2.2.1-3.fc19.noarch python-keystoneclient-0.2.3-7.fc19.noarch # rpm -qa | grep openstack openstack-nova-novncproxy-2013.1.3-2.fc19.noarch openstack-nova-console-2013.1.3-2.fc19.noarch openstack-utils-2013.2-1.fc19.1.noarch openstack-nova-compute-2013.1.3-2.fc19.noarch openstack-quantum-openvswitch-2013.1.3-1.fc19.noarch openstack-keystone-2013.1.3-1.fc19.noarch openstack-glance-2013.1.3-1.fc19.noarch openstack-cinder-2013.1.3-1.fc19.noarch openstack-nova-common-2013.1.3-2.fc19.noarch openstack-nova-scheduler-2013.1.3-2.fc19.noarch openstack-quantum-2013.1.3-1.fc19.noarch openstack-dashboard-2013.1.3-2.fc19.noarch openstack-nova-conductor-2013.1.3-2.fc19.noarch openstack-packstack-2013.1.1-0.20.dev642.fc19.noarch openstack-nova-api-2013.1.3-2.fc19.noarch openstack-nova-cert-2013.1.3-2.fc19.noarch python-django-openstack-auth-1.0.7-1.fc18.noarch
ここでは、Glance/Cinder/NovaのClient Libraryについて説明します。その他はまたの機会に。
Clientオブジェクトの取得
Client libraryを使用する際は、はじめに各モジュール(glance/cinder/nova)のClientオブジェクトを取得します。次は各Clientオブジェクトを取得するおまじないです。「keystoenrc_XXX」で、いつもの環境変数を設定した状態で実行してください。
clients.py
#!/usr/bin/python # Boilerplate for preparing client objects import os import keystoneclient.v2_0.client as kclient import novaclient.v1_1.client as nclient import glanceclient.v1.client as gclient import cinderclient.v1.client as cclient _keystone_creds = {} _keystone_creds['username'] = os.environ['OS_USERNAME'] _keystone_creds['password'] = os.environ['OS_PASSWORD'] _keystone_creds['auth_url'] = os.environ['OS_AUTH_URL'] _keystone_creds['tenant_name'] = os.environ['OS_TENANT_NAME'] _nova_creds = {} _nova_creds['username'] = os.environ['OS_USERNAME'] _nova_creds['api_key'] = os.environ['OS_PASSWORD'] _nova_creds['auth_url'] = os.environ['OS_AUTH_URL'] _nova_creds['project_id'] = os.environ['OS_TENANT_NAME'] keystone = kclient.Client(**_keystone_creds) nova = nclient.Client(**_nova_creds) cinder = cclient.Client(**_nova_creds) _glance_ep = keystone.service_catalog.url_for( service_type='image', endpoint_type='publicURL') glance = gclient.Client(_glance_ep, token=keystone.auth_token)
ここで取得されるClientオブジェクトは、そのメンバとして、自身が管理する各種コンポーネントのManagerオブジェクトを持っています。Managerオブジェクトのメソッドで、個々のコンポーネントを表すオブジェクトを取得して利用します。主要なManagerオブジェクトは次のとおりです。
glance: Glance Client
glance.images: Image Manager(imageオブジェクトを取り扱う)
cinder: Cinder Client
cinder.volumes: Volume Manager(volumeオブジェクトを取り扱う)
cinder.volume_snapshots: Snapshot Manager(snapshotオブジェクトを取り扱う)
nova: Nova Client
nova.servers: Server Manager(serverオブジェクトを取り扱う)
nova.floating_ips: Floating IP Manager(floating_ipオブジェクトを取り扱う)
nova.security_groups: Security Group Manager(security_groupオブジェクトを取り扱う)
nova.security_group_rules: Security Group Rules Manager(security_group_ruleオブジェクトを取り扱う)
nova.flavors: Flavor Manager(flavorオブジェクトを取り扱う)
nova.keypairs: Keypair Manager (keypairオブジェクトを取り扱う)
Nova Clientでは、nova.imagesとnova.networksのManagerを使って、imageとnetworkも扱えるようになっています。本来は、CinderとNeutronのClientを使うべきですが、インスタンス起動時にイメージやネットワークのIDを検索するなど、簡易的に利用するのに便利です。
Glance Client
まずは、比較的シンプルなGlance Clientから説明します。glance.images(Image Manager)の主要なメソッドを「# pydoc glanceclient.v1.images」から抜き出すと次のようになります。日本語のコメントは私が追加しています。
| create(self, **kwargs) | Create an image # 作成したimageオブジェクトを返す。引数はこの後のサンプルコードを参照。 | | list(self, **kwargs) # filtersにマッチしたimageオブジェクトのIteratorを返す。 | Get a list of images. | :param limit: maximum number of images to return | :param filters: dict of direct comparison filters that mimics the | structure of an image object # imageオブジェクトのアトリビュートを指定する。 | | delete(self, image) | Delete an image. # imageオブジェクトを引数に取る。 | | update(self, image, **kwargs) # imageオブジェクトを引数に取って、アトリビュートを更新します。 | Update an image
同じく、imageオブジェクトの主要メソッドです。
| delete(self) | | update(self, **fields) # アトリビュートを更新します。
最後に、imageオブジェクトのアトリビュート一覧です。「image._info」でぶっこ抜いたサンプルです。
{ u'checksum': u'd972013792949d0d3ba628fbe8685bce', u'container_format': u'bare', u'created_at': u'2013-12-15T06:03:46', u'deleted': False, u'deleted_at': None, u'disk_format': u'qcow2', u'id': u'7d7188d1-4c38-459a-965a-ed03efc6c30e', u'is_public': True, u'min_disk': 0, u'min_ram': 0, u'name': u'cirros', u'owner': u'd0a8267eaf2241d680cb360e68e63218', u'properties': { }, u'protected': False, u'size': 13147648, u'status': u'active', u'updated_at': u'2013-12-15T06:03:47'}
それでは、これらを利用するサンプルコードです。先の「clients.py」と同じディレクトリに置いて実行して下さい。
glance.py
#!/usr/bin/python from clients import nova, glance, cinder import pprint pp = pprint.PrettyPrinter(indent=4) print "Delete sample images if exist." images = glance.images.list(filters={'name':"sample-1"}) for image in images: image.delete() images = glance.images.list(filters={'name':"sample-2"}) for image in images: image.delete() print "Upload sample images (sample-1, sample-2)." with open('/tmp/cirros-0.3.1-x86_64-disk.img') as imagefile: image01 = glance.images.create( name="sample-1", is_public=True, disk_format='qcow2', container_format='bare', data=imagefile) with open('/tmp/cirros-0.3.1-x86_64-disk.img') as imagefile: image02 = glance.images.create( name="sample-2", is_public=True, disk_format='qcow2', container_format='bare', data=imagefile) print "Update an attribute through an image object." image01.update(min_ram=512) print "Update an attribute through the image manager." glance.images.update(image02.id, min_ram=512) print "List all images" for image in glance.images.list(): print "----" print "Attributes of %s" % image.name pp.pprint(image._info) print "" print "Delete an image through an image object." image01.delete() print "Delete an image through the image manager." glance.images.delete(image02.id) print "List all images, again" for image in glance.images.list(): print "----" print "Attributes of %s" % image.name pp.pprint(image._info)
実行例はこちらです。この環境では、「Fedora19」のイメージが最初からあります。冒頭のUserWarningは、既知の問題っぽいのでとりあえず無視してください。
# wget http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img -O /tmp/cirros-0.3.1-x86_64-disk.img # cat ~/keystonerc_demo export OS_USERNAME=demo_user export OS_TENANT_NAME=demo export OS_PASSWORD=passw0rd export OS_AUTH_URL=http://172.16.1.11:5000/v2.0/ export PS1='[\u@\h \W(keystone_demo)]\$ ' # . ~/keystonerc_demo # glance image-list +--------------------------------------+----------+-------------+------------------+-----------+--------+ | ID | Name | Disk Format | Container Format | Size | Status | +--------------------------------------+----------+-------------+------------------+-----------+--------+ | 9f454833-8132-4e2c-a60e-a4fdeae8baa1 | Fedora19 | qcow2 | bare | 237371392 | active | +--------------------------------------+----------+-------------+------------------+-----------+--------+ # ./glance.py /usr/lib/python2.7/site-packages/novaclient/client.py:16: UserWarning: Module backports was already imported from /usr/lib64/python2.7/site-packages/backports/__init__.pyc, but /usr/lib/python2.7/site-packages is being added to sys.path import pkg_resources Delete sample images if exist. Upload sample images (sample-1, sample-2). Update an attribute through an image object. Update an attribute through the image manager. List all images ---- Attributes of sample-2 { u'checksum': u'd972013792949d0d3ba628fbe8685bce', u'container_format': u'bare', u'created_at': u'2013-12-15T12:03:08', u'deleted': False, u'deleted_at': None, u'disk_format': u'qcow2', u'id': u'1013a667-90d4-45f5-86db-8e666b07874f', u'is_public': True, u'min_disk': 0, u'min_ram': 512, u'name': u'sample-2', u'owner': u'd0a8267eaf2241d680cb360e68e63218', u'properties': { }, u'protected': False, u'size': 13147648, u'status': u'active', u'updated_at': u'2013-12-15T12:03:09'} ---- Attributes of sample-1 { u'checksum': u'd972013792949d0d3ba628fbe8685bce', u'container_format': u'bare', u'created_at': u'2013-12-15T12:03:07', u'deleted': False, u'deleted_at': None, u'disk_format': u'qcow2', u'id': u'b22dab45-8d5f-405a-bb8e-e7637eb63d51', u'is_public': True, u'min_disk': 0, u'min_ram': 512, u'name': u'sample-1', u'owner': u'd0a8267eaf2241d680cb360e68e63218', u'properties': { }, u'protected': False, u'size': 13147648, u'status': u'active', u'updated_at': u'2013-12-15T12:03:08'} ---- Attributes of Fedora19 { u'checksum': u'9ff360edd3b3f1fc035205f63a58ec3e', u'container_format': u'bare', u'created_at': u'2013-12-14T10:12:14', u'deleted': False, u'deleted_at': None, u'disk_format': u'qcow2', u'id': u'9f454833-8132-4e2c-a60e-a4fdeae8baa1', u'is_public': True, u'min_disk': 0, u'min_ram': 1024, u'name': u'Fedora19', u'owner': u'b6155b861f974df1a5f05e641abcd7b5', u'properties': { }, u'protected': False, u'size': 237371392, u'status': u'active', u'updated_at': u'2013-12-15T05:09:54'} Delete an image through an image object. Delete an image through the image manager. List all images, again ---- Attributes of Fedora19 { u'checksum': u'9ff360edd3b3f1fc035205f63a58ec3e', u'container_format': u'bare', u'created_at': u'2013-12-14T10:12:14', u'deleted': False, u'deleted_at': None, u'disk_format': u'qcow2', u'id': u'9f454833-8132-4e2c-a60e-a4fdeae8baa1', u'is_public': True, u'min_disk': 0, u'min_ram': 1024, u'name': u'Fedora19', u'owner': u'b6155b861f974df1a5f05e641abcd7b5', u'properties': { }, u'protected': False, u'size': 237371392, u'status': u'active', u'updated_at': u'2013-12-15T05:09:54'}
Cinder Client
次はCinder Clientです。cinder.volumes(Volume Manager)とcinder.volume_snapshots(Snapshot Manager)の2つのManagerがあります。まず、Volume Managerについて、主要なメソッドを「# pydoc cinderclient.v1.volumes」から抜き出します。
| create(self, size, snapshot_id=None, source_volid=None, display_name=None, display_description=None, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None) | Create a volume. | | :param size: Size of volume in GB | :param snapshot_id: ID of the snapshot | :param display_name: Name of the volume | :param display_description: Description of the volume | :param volume_type: Type of volume | :rtype: :class:`Volume` | :param user_id: User id derived from context | :param project_id: Project id derived from context | :param availability_zone: Availability Zone to use | :param metadata: Optional metadata to set on volume creation | :param imageRef: reference to an image stored in glance | :param source_volid: ID of source volume to clone from | | delete(self, volume) | Delete a volume. | | get(self, volume_id) | Get a volume. | | list(self, detailed=True, search_opts=None) | Get a list of all volumes. | | find(self, **kwargs) | Find a single item with attributes matching ``**kwargs``. | | This isn't very efficient: it loads the entire list then filters on | the Python side. | | findall(self, **kwargs) | Find all items with attributes matching ``**kwargs``.
同じく、volumeオブジェクトの主要メソッドです。
| delete(self) | Delete this volume. | | update(self, **kwargs) | Update the display_name or display_description for this volume.
最後に、volumeオブジェクトのアトリビュート一覧です。「volume._info」でぶっこ抜いたサンプルです。
{ u'attachments': [], u'availability_zone': u'nova', u'bootable': u'false', u'created_at': u'2013-12-15T11:13:56.000000', u'display_description': u'test volume', u'display_name': u'volume01', u'id': u'a4cd89bf-253d-41ab-8920-78f3a7f101bd', u'metadata': { }, u'size': 1, u'snapshot_id': None, u'source_volid': None, u'status': u'creating', u'volume_type': u'None'}
それでは、Volume Managerを使ったサンプルコードと実行例です。
cinder.py
#!/usr/bin/python from clients import nova, glance, cinder import time import pprint pp = pprint.PrettyPrinter(indent=4) print "Delete sample volumes if exist." volumes = cinder.volumes.findall(display_name='sample-1') for volume in volumes: volume.delete() volumes = cinder.volumes.findall(display_name='sample-2') for volume in volumes: volume.delete() print "Create sample volumes (sample-1, sample-2)." volume01 = cinder.volumes.create(size=1, display_name='sample-1', display_description='test volume No.1') volume02 = cinder.volumes.create(size=1, display_name='sample-2') while (volume01.status != 'available'): print "Wait sample-1 to be available." time.sleep(1) volume01 = cinder.volumes.get(volume01.id) while (volume02.status != 'available'): print "Wait sample-2 to be available." time.sleep(1) volume02 = cinder.volumes.get(volume02.id) print "Update an attribute." volume02.update(display_description='test volume No.2') print "List all volumes" for volume in cinder.volumes.list(): print "----" print "Attributes of %s" % volume.display_name pp.pprint(volume._info) print "" print "Delete a volume through an volume object." volume01.delete() print "Delete a image through the volume manager." cinder.volumes.delete(volume02) while (volume01): try: cinder.volumes.get(volume01.id) print "Wait sample-1 to be deleted." time.sleep(1) except: volume01=None while (volume02): try: cinder.volumes.get(volume02.id) print "Wait sample-2 to be deleted." time.sleep(1) except: volume02=None print "List all volumes, again" for volume in cinder.volumes.list(): print "----" print "Attributes of %s" % volume.display_name pp.pprint(volume._info)
# ./cinder.py /usr/lib/python2.7/site-packages/novaclient/client.py:16: UserWarning: Module backports was already imported from /usr/lib64/python2.7/site-packages/backports/__init__.pyc, but /usr/lib/python2.7/site-packages is being added to sys.path import pkg_resources Delete sample volumes if exist. Create sample volumes (sample-1, sample-2). Wait sample-1 to be available. Wait sample-1 to be available. Wait sample-2 to be available. Update an attribute. List all volumes ---- Attributes of sample-2 { u'attachments': [], u'availability_zone': u'nova', u'bootable': u'false', u'created_at': u'2013-12-15T12:28:44.000000', u'display_description': u'test volume No.2', u'display_name': u'sample-2', u'id': u'feb9fae3-6de7-4b51-9541-b692797bf7f8', u'metadata': { }, u'size': 1, u'snapshot_id': None, u'source_volid': None, u'status': u'available', u'volume_type': u'None'} ---- Attributes of sample-1 { u'attachments': [], u'availability_zone': u'nova', u'bootable': u'false', u'created_at': u'2013-12-15T12:28:44.000000', u'display_description': u'test volume No.1', u'display_name': u'sample-1', u'id': u'89e0810d-4710-4143-8457-6c57d9a27cf3', u'metadata': { }, u'size': 1, u'snapshot_id': None, u'source_volid': None, u'status': u'available', u'volume_type': u'None'} Delete a volume through an volume object. Delete a image through the volume manager. Wait sample-1 to be deleted. Wait sample-1 to be deleted. List all volumes, again
続いて、Snapshot Managerです。「# pydoc cinderclient.v1.volume_snapshots」から抽出した主要メソッド。
| create(self, volume_id, force=False, display_name=None, display_description=None) | Create a snapshot of the given volume. | | :param volume_id: The ID of the volume to snapshot. | :param force: If force is True, create a snapshot even if the volume is | attached to an instance. Default is False. | :param display_name: Name of the snapshot | :param display_description: Description of the snapshot | :rtype: :class:`Snapshot` | | delete(self, snapshot) | Delete a snapshot. | | get(self, snapshot_id) | Get a snapshot. | | list(self, detailed=True, search_opts=None) | Get a list of all snapshots. | | update(self, snapshot, **kwargs) | Update the display_name or display_description for a snapshot. | | find(self, **kwargs) | Find a single item with attributes matching ``**kwargs``. | | findall(self, **kwargs) | Find all items with attributes matching ``**kwargs``.
こちらは、snapshotオブジェクトの主要メソッド。
| delete(self) | Delete this snapshot. | | update(self, **kwargs) | Update the display_name or display_description for this snapshot.
そして、snapshotオブジェクトのアトリビュート一覧。volume_idからベースボリュームのIDが分かります。
{ u'created_at': u'2013-12-16T02:43:44.000000', u'display_description': u'test snapshot No.1', u'display_name': u'snaphsot_sample-1', u'id': u'e1c43356-eabd-4cd3-bb3e-e9a9049b4eda', u'metadata': { }, u'os-extended-snapshot-attributes:progress': u'100%', u'os-extended-snapshot-attributes:project_id': u'd0a8267eaf2241d680cb360e68e63218', u'size': 1, u'status': u'available', u'volume_id': u'4041d739-e2cf-48ae-8733-67b66bc07eb7'}
以上を踏まえたサンプルコードと実行例です。
snapshot.py
#!/usr/bin/python from clients import nova, glance, cinder import time import pprint pp = pprint.PrettyPrinter(indent=4) print "Create a sample volume (sample-1)." volume01 = cinder.volumes.create(size=1, display_name='sample-1', display_description='test volume No.1') while (volume01.status != 'available'): print "Wait the sample volume to be available." time.sleep(1) volume01 = cinder.volumes.get(volume01.id) print "Create snapshot from the sample." snapshot01 = cinder.volume_snapshots.create(volume01.id, display_name='snaphsot_sample-1') while (snapshot01.status != 'available'): print "Wait the snapshot to be available." time.sleep(1) snapshot01 = cinder.volume_snapshots.get(snapshot01.id) print "Update an attribute of the snapshot through a snapshot object." snapshot01.update(display_description='test snapshot No.1') print "List all snapshots" for snapshot in cinder.volume_snapshots.list(): print "----" print "Attributes of snapshot %s" % snapshot.display_name pp.pprint(snapshot._info) base_volume = cinder.volumes.get(snapshot.volume_id) print "Attributes of base volume %s" % base_volume.display_name pp.pprint(base_volume._info) print "" print "Update an attribute of the snapshot through the manager." cinder.volume_snapshots.update(snapshot01, display_name='to_be_deleted') print "Find a snapshot with display_name='to_be_deleted'" for snapshot in cinder.volume_snapshots.findall(display_name='to_be_deleted'): print "Delete the snapshot and its base volume." base_volume = cinder.volumes.get(snapshot.volume_id) snapshot.delete() while (snapshot): try: cinder.volume_snapshots.get(snapshot.id) print "Wait the snapshot to be deleted." time.sleep(1) except: snapshot=None base_volume.delete()
# ./snapshot.py /usr/lib/python2.7/site-packages/novaclient/client.py:16: UserWarning: Module backports was already imported from /usr/lib64/python2.7/site-packages/backports/__init__.pyc, but /usr/lib/python2.7/site-packages is being added to sys.path import pkg_resources Create a sample volume (sample-1). Wait the sample volume to be available. Wait the sample volume to be available. Create snapshot from the sample. Wait the snapshot to be available. Update an attribute of the snapshot through a snapshot object. List all snapshots ---- Attributes of snapshot snaphsot_sample-1 { u'created_at': u'2013-12-16T02:43:44.000000', u'display_description': u'test snapshot No.1', u'display_name': u'snaphsot_sample-1', u'id': u'e1c43356-eabd-4cd3-bb3e-e9a9049b4eda', u'metadata': { }, u'os-extended-snapshot-attributes:progress': u'100%', u'os-extended-snapshot-attributes:project_id': u'd0a8267eaf2241d680cb360e68e63218', u'size': 1, u'status': u'available', u'volume_id': u'4041d739-e2cf-48ae-8733-67b66bc07eb7'} Attributes of base volume sample-1 { u'attachments': [], u'availability_zone': u'nova', u'bootable': u'false', u'created_at': u'2013-12-16T02:43:41.000000', u'display_description': u'test volume No.1', u'display_name': u'sample-1', u'id': u'4041d739-e2cf-48ae-8733-67b66bc07eb7', u'metadata': { }, u'size': 1, u'snapshot_id': None, u'source_volid': None, u'status': u'available', u'volume_type': u'None'} Update an attribute of the snapshot through the manager. Find a snapshot with display_name='to_be_deleted' Delete the snapshot and its base volume. Wait the snapshot to be deleted.
Nova Client
そしていよいよ、大物のNovaです。
が、あまりにもメソッドが多くて書き出すと大変なので、また、そろそろ使い方の雰囲気も分かってきたと思いますので、下記のpydocを参照ください。
# pydoc novaclient.v1_1.servers # pydoc novaclient.v1_1.flavors # pydoc novaclient.v1_1.floating_ips # pydoc novaclient.v1_1.security_groups # pydoc novaclient.v1_1.security_group_rules # pydoc novaclient.v1_1.keypairs # pydoc novaclient.v1_1.images # pydoc novaclient.v1_1.networks
ここでは、サンプルコードで実例を示しておきます。下記は、インスタンスを起動した後、ブロックボリュームを作成してアタッチして、さらにフローティングIPを割り当てています。イメージ「cirros」と仮想ネットワーク「private01」と鍵ペア「mykey」が事前に用意されている前提です。
launch_vm.py
#!/usr/bin/python from clients import nova, glance, cinder import time image = nova.images.find(name='cirros') flavor = nova.flavors.find(name='m1.tiny') net = nova.networks.find(label='private01') instance = nova.servers.create( name="vm01", image=image, flavor=flavor, key_name="mykey", nics=[{'net-id':net.id}]) volume = cinder.volumes.create(size=1, display_name='volume01') while instance.status == 'BUILD': print "Waiting status to be active." time.sleep(10) instance = nova.servers.get(instance.id) while volume.status == 'creating': time.sleep(1) volume = cinder.volumes.get(volume.id) nova.volumes.create_server_volume(instance.id, volume.id, '/dev/vdb') for floating_ip in nova.floating_ips.list(): if floating_ip.instance_id == None: instance.add_floating_ip(floating_ip) print "Floating IP: %s" % floating_ip.ip break
仮想マシンインスタンスを起動する際に、カスタマイズスクリプト(Userdata)を送り込むことも可能です。次のように、引数「userdata」にファイルディスクリプタを渡します。
with open('/tmp/userdata.txt') as userdata_file: instance = nova.servers.create( name="vm01", image=image, flavor=flavor, key_name="mykey", security_groups=['default','group01'], nics=[{'net-id':net.id}], userdata=userdata_file)