diff options
Diffstat (limited to 'cinderclient/v1')
-rw-r--r-- | cinderclient/v1/__init__.py | 17 | ||||
-rw-r--r-- | cinderclient/v1/client.py | 71 | ||||
-rw-r--r-- | cinderclient/v1/contrib/__init__.py | 0 | ||||
-rw-r--r-- | cinderclient/v1/shell.py | 241 | ||||
-rw-r--r-- | cinderclient/v1/volume_snapshots.py | 88 | ||||
-rw-r--r-- | cinderclient/v1/volume_types.py | 77 | ||||
-rw-r--r-- | cinderclient/v1/volumes.py | 135 |
7 files changed, 629 insertions, 0 deletions
diff --git a/cinderclient/v1/__init__.py b/cinderclient/v1/__init__.py new file mode 100644 index 0000000..cecfacd --- /dev/null +++ b/cinderclient/v1/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient.v1.client import Client diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py new file mode 100644 index 0000000..cbee8ba --- /dev/null +++ b/cinderclient/v1/client.py @@ -0,0 +1,71 @@ +from cinderclient import client +from cinderclient.v1 import volumes +from cinderclient.v1 import volume_snapshots +from cinderclient.v1 import volume_types + + +class Client(object): + """ + Top-level object to access the OpenStack Compute API. + + Create an instance with your creds:: + + >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) + + Then call methods on its managers:: + + >>> client.servers.list() + ... + >>> client.flavors.list() + ... + + """ + + # FIXME(jesse): project_id isn't required to authenticate + def __init__(self, username, api_key, project_id, auth_url, + insecure=False, timeout=None, proxy_tenant_id=None, + proxy_token=None, region_name=None, + endpoint_type='publicURL', extensions=None, + service_type='compute', service_name=None, + volume_service_name=None): + # FIXME(comstud): Rename the api_key argument above when we + # know it's not being used as keyword argument + password = api_key + + # extensions + self.volumes = volumes.VolumeManager(self) + self.volume_snapshots = volume_snapshots.SnapshotManager(self) + self.volume_types = volume_types.VolumeTypeManager(self) + + # Add in any extensions... + if extensions: + for extension in extensions: + if extension.manager_class: + setattr(self, extension.name, + extension.manager_class(self)) + + self.client = client.HTTPClient(username, + password, + project_id, + auth_url, + insecure=insecure, + timeout=timeout, + proxy_token=proxy_token, + proxy_tenant_id=proxy_tenant_id, + region_name=region_name, + endpoint_type=endpoint_type, + service_type=service_type, + service_name=service_name, + volume_service_name=volume_service_name) + + def authenticate(self): + """ + Authenticate against the server. + + Normally this is called automatically when you first access the API, + but you can call this method to force authentication right now. + + Returns on success; raises :exc:`exceptions.Unauthorized` if the + credentials are wrong. + """ + self.client.authenticate() diff --git a/cinderclient/v1/contrib/__init__.py b/cinderclient/v1/contrib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cinderclient/v1/contrib/__init__.py diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py new file mode 100644 index 0000000..6b8b7bb --- /dev/null +++ b/cinderclient/v1/shell.py @@ -0,0 +1,241 @@ +# Copyright 2010 Jacob Kaplan-Moss + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys +import time + +from cinderclient import utils + + +def _poll_for_status(poll_fn, obj_id, action, final_ok_states, + poll_period=5, show_progress=True): + """Block while an action is being performed, periodically printing + progress. + """ + def print_progress(progress): + if show_progress: + msg = ('\rInstance %(action)s... %(progress)s%% complete' + % dict(action=action, progress=progress)) + else: + msg = '\rInstance %(action)s...' % dict(action=action) + + sys.stdout.write(msg) + sys.stdout.flush() + + print + while True: + obj = poll_fn(obj_id) + status = obj.status.lower() + progress = getattr(obj, 'progress', None) or 0 + if status in final_ok_states: + print_progress(100) + print "\nFinished" + break + elif status == "error": + print "\nError %(action)s instance" % locals() + break + else: + print_progress(progress) + time.sleep(poll_period) + + +def _find_volume(cs, volume): + """Get a volume by ID.""" + return utils.find_resource(cs.volumes, volume) + + +def _find_volume_snapshot(cs, snapshot): + """Get a volume snapshot by ID.""" + return utils.find_resource(cs.volume_snapshots, snapshot) + + +def _print_volume(cs, volume): + utils.print_dict(volume._info) + + +def _print_volume_snapshot(cs, snapshot): + utils.print_dict(snapshot._info) + + +def _translate_volume_keys(collection): + convert = [('displayName', 'display_name'), ('volumeType', 'volume_type')] + for item in collection: + keys = item.__dict__.keys() + for from_key, to_key in convert: + if from_key in keys and to_key not in keys: + setattr(item, to_key, item._info[from_key]) + + +def _translate_volume_snapshot_keys(collection): + convert = [('displayName', 'display_name'), ('volumeId', 'volume_id')] + for item in collection: + keys = item.__dict__.keys() + for from_key, to_key in convert: + if from_key in keys and to_key not in keys: + setattr(item, to_key, item._info[from_key]) + + +@utils.service_type('volume') +def do_list(cs, args): + """List all the volumes.""" + volumes = cs.volumes.list() + _translate_volume_keys(volumes) + + # Create a list of servers to which the volume is attached + for vol in volumes: + servers = [s.get('server_id') for s in vol.attachments] + setattr(vol, 'attached_to', ','.join(map(str, servers))) + utils.print_list(volumes, ['ID', 'Status', 'Display Name', + 'Size', 'Volume Type', 'Attached to']) + + +@utils.arg('volume', metavar='<volume>', help='ID of the volume.') +@utils.service_type('volume') +def do_show(cs, args): + """Show details about a volume.""" + volume = _find_volume(cs, args.volume) + _print_volume(cs, volume) + + +@utils.arg('size', + metavar='<size>', + type=int, + help='Size of volume in GB') +@utils.arg('--snapshot_id', + metavar='<snapshot_id>', + help='Optional snapshot id to create the volume from. (Default=None)', + default=None) +@utils.arg('--display_name', metavar='<display_name>', + help='Optional volume name. (Default=None)', + default=None) +@utils.arg('--display_description', metavar='<display_description>', + help='Optional volume description. (Default=None)', + default=None) +@utils.arg('--volume_type', + metavar='<volume_type>', + help='Optional volume type. (Default=None)', + default=None) +@utils.service_type('volume') +def do_create(cs, args): + """Add a new volume.""" + cs.volumes.create(args.size, + args.snapshot_id, + args.display_name, + args.display_description, + args.volume_type) + + +@utils.arg('volume', metavar='<volume>', help='ID of the volume to delete.') +@utils.service_type('volume') +def do_delete(cs, args): + """Remove a volume.""" + volume = _find_volume(cs, args.volume) + volume.delete() + + +@utils.service_type('volume') +def do_snapshot_list(cs, args): + """List all the snapshots.""" + snapshots = cs.volume_snapshots.list() + _translate_volume_snapshot_keys(snapshots) + utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name', + 'Size']) + + +@utils.arg('snapshot', metavar='<snapshot>', help='ID of the snapshot.') +@utils.service_type('volume') +def do_snapshot_show(cs, args): + """Show details about a snapshot.""" + snapshot = _find_volume_snapshot(cs, args.snapshot) + _print_volume_snapshot(cs, snapshot) + + +@utils.arg('volume_id', + metavar='<volume_id>', + help='ID of the volume to snapshot') +@utils.arg('--force', + metavar='<True|False>', + help='Optional flag to indicate whether to snapshot a volume even if its ' + 'attached to an instance. (Default=False)', + default=False) +@utils.arg('--display_name', metavar='<display_name>', + help='Optional snapshot name. (Default=None)', + default=None) +@utils.arg('--display_description', metavar='<display_description>', + help='Optional snapshot description. (Default=None)', + default=None) +@utils.service_type('volume') +def do_snapshot_create(cs, args): + """Add a new snapshot.""" + cs.volume_snapshots.create(args.volume_id, + args.force, + args.display_name, + args.display_description) + + +@utils.arg('snapshot_id', + metavar='<snapshot_id>', + help='ID of the snapshot to delete.') +@utils.service_type('volume') +def do_snapshot_delete(cs, args): + """Remove a snapshot.""" + snapshot = _find_volume_snapshot(cs, args.snapshot_id) + snapshot.delete() + + +def _print_volume_type_list(vtypes): + utils.print_list(vtypes, ['ID', 'Name']) + + +@utils.service_type('volume') +def do_type_list(cs, args): + """Print a list of available 'volume types'.""" + vtypes = cs.volume_types.list() + _print_volume_type_list(vtypes) + + +@utils.arg('name', + metavar='<name>', + help="Name of the new flavor") +@utils.service_type('volume') +def do_type_create(cs, args): + """Create a new volume type.""" + vtype = cs.volume_types.create(args.name) + _print_volume_type_list([vtype]) + + +@utils.arg('id', + metavar='<id>', + help="Unique ID of the volume type to delete") +@utils.service_type('volume') +def do_type_delete(cs, args): + """Delete a specific flavor""" + cs.volume_types.delete(args.id) + + +def do_endpoints(cs, args): + """Discover endpoints that get returned from the authenticate services""" + catalog = cs.client.service_catalog.catalog + for e in catalog['access']['serviceCatalog']: + utils.print_dict(e['endpoints'][0], e['name']) + + +def do_credentials(cs, args): + """Show user credentials returned from auth""" + catalog = cs.client.service_catalog.catalog + utils.print_dict(catalog['access']['user'], "User Credentials") + utils.print_dict(catalog['access']['token'], "Token") diff --git a/cinderclient/v1/volume_snapshots.py b/cinderclient/v1/volume_snapshots.py new file mode 100644 index 0000000..fa6c4b4 --- /dev/null +++ b/cinderclient/v1/volume_snapshots.py @@ -0,0 +1,88 @@ +# Copyright 2011 Denali Systems, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Volume snapshot interface (1.1 extension). +""" + +from cinderclient import base + + +class Snapshot(base.Resource): + """ + A Snapshot is a point-in-time snapshot of an openstack volume. + """ + def __repr__(self): + return "<Snapshot: %s>" % self.id + + def delete(self): + """ + Delete this snapshot. + """ + self.manager.delete(self) + + +class SnapshotManager(base.ManagerWithFind): + """ + Manage :class:`Snapshot` resources. + """ + resource_class = Snapshot + + def 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` + """ + body = {'snapshot': {'volume_id': volume_id, + 'force': force, + 'display_name': display_name, + 'display_description': display_description}} + return self._create('/snapshots', body, 'snapshot') + + def get(self, snapshot_id): + """ + Get a snapshot. + + :param snapshot_id: The ID of the snapshot to get. + :rtype: :class:`Snapshot` + """ + return self._get("/snapshots/%s" % snapshot_id, "snapshot") + + def list(self, detailed=True): + """ + Get a list of all snapshots. + + :rtype: list of :class:`Snapshot` + """ + if detailed is True: + return self._list("/snapshots/detail", "snapshots") + else: + return self._list("/snapshots", "snapshots") + + def delete(self, snapshot): + """ + Delete a snapshot. + + :param snapshot: The :class:`Snapshot` to delete. + """ + self._delete("/snapshots/%s" % base.getid(snapshot)) diff --git a/cinderclient/v1/volume_types.py b/cinderclient/v1/volume_types.py new file mode 100644 index 0000000..e6d644d --- /dev/null +++ b/cinderclient/v1/volume_types.py @@ -0,0 +1,77 @@ +# Copyright (c) 2011 Rackspace US, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Volume Type interface. +""" + +from cinderclient import base + + +class VolumeType(base.Resource): + """ + A Volume Type is the type of volume to be created + """ + def __repr__(self): + return "<Volume Type: %s>" % self.name + + +class VolumeTypeManager(base.ManagerWithFind): + """ + Manage :class:`VolumeType` resources. + """ + resource_class = VolumeType + + def list(self): + """ + Get a list of all volume types. + + :rtype: list of :class:`VolumeType`. + """ + return self._list("/types", "volume_types") + + def get(self, volume_type): + """ + Get a specific volume type. + + :param volume_type: The ID of the :class:`VolumeType` to get. + :rtype: :class:`VolumeType` + """ + return self._get("/types/%s" % base.getid(volume_type), "volume_type") + + def delete(self, volume_type): + """ + Delete a specific volume_type. + + :param volume_type: The ID of the :class:`VolumeType` to get. + """ + self._delete("/types/%s" % base.getid(volume_type)) + + def create(self, name): + """ + Create a volume type. + + :param name: Descriptive name of the volume type + :rtype: :class:`VolumeType` + """ + + body = { + "volume_type": { + "name": name, + } + } + + return self._create("/types", body, "volume_type") diff --git a/cinderclient/v1/volumes.py b/cinderclient/v1/volumes.py new file mode 100644 index 0000000..d465724 --- /dev/null +++ b/cinderclient/v1/volumes.py @@ -0,0 +1,135 @@ +# Copyright 2011 Denali Systems, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Volume interface (1.1 extension). +""" + +from cinderclient import base + + +class Volume(base.Resource): + """ + A volume is an extra block level storage to the OpenStack instances. + """ + def __repr__(self): + return "<Volume: %s>" % self.id + + def delete(self): + """ + Delete this volume. + """ + self.manager.delete(self) + + +class VolumeManager(base.ManagerWithFind): + """ + Manage :class:`Volume` resources. + """ + resource_class = Volume + + def create(self, size, snapshot_id=None, + display_name=None, display_description=None, + volume_type=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` + """ + body = {'volume': {'size': size, + 'snapshot_id': snapshot_id, + 'display_name': display_name, + 'display_description': display_description, + 'volume_type': volume_type}} + return self._create('/volumes', body, 'volume') + + def get(self, volume_id): + """ + Get a volume. + + :param volume_id: The ID of the volume to delete. + :rtype: :class:`Volume` + """ + return self._get("/volumes/%s" % volume_id, "volume") + + def list(self, detailed=True): + """ + Get a list of all volumes. + + :rtype: list of :class:`Volume` + """ + if detailed is True: + return self._list("/volumes/detail", "volumes") + else: + return self._list("/volumes", "volumes") + + def delete(self, volume): + """ + Delete a volume. + + :param volume: The :class:`Volume` to delete. + """ + self._delete("/volumes/%s" % base.getid(volume)) + + def create_server_volume(self, server_id, volume_id, device): + """ + Attach a volume identified by the volume ID to the given server ID + + :param server_id: The ID of the server + :param volume_id: The ID of the volume to attach. + :param device: The device name + :rtype: :class:`Volume` + """ + body = {'volumeAttachment': {'volumeId': volume_id, + 'device': device}} + return self._create("/servers/%s/os-volume_attachments" % server_id, + body, "volumeAttachment") + + def get_server_volume(self, server_id, attachment_id): + """ + Get the volume identified by the attachment ID, that is attached to + the given server ID + + :param server_id: The ID of the server + :param attachment_id: The ID of the attachment + :rtype: :class:`Volume` + """ + return self._get("/servers/%s/os-volume_attachments/%s" % (server_id, + attachment_id,), "volumeAttachment") + + def get_server_volumes(self, server_id): + """ + Get a list of all the attached volumes for the given server ID + + :param server_id: The ID of the server + :rtype: list of :class:`Volume` + """ + return self._list("/servers/%s/os-volume_attachments" % server_id, + "volumeAttachments") + + def delete_server_volume(self, server_id, attachment_id): + """ + Detach a volume identified by the attachment ID from the given server + + :param server_id: The ID of the server + :param attachment_id: The ID of the attachment + """ + self._delete("/servers/%s/os-volume_attachments/%s" % + (server_id, attachment_id,)) |