summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorscottda <scott.dangelo@hpe.com>2016-03-31 09:20:29 -0600
committerscottda <scott.dangelo@hpe.com>2016-04-18 10:49:51 -0600
commit27e6f6f7f85ef0e5fcc37b96e67c870b84880760 (patch)
tree1e433f8e2ec54e208441eca98eaf2dde0c97561b
parent6e0b5f451c045484001696494ca5d9e89dbf6a40 (diff)
downloadpython-cinderclient-27e6f6f7f85ef0e5fcc37b96e67c870b84880760.tar.gz
Add /v3 endpoint support for cinderclient
Add support for Cinder API /v3 endpoint. A couple of unit tests for /v3 endpoint were added to v3/test_shell.py to ensure that the v3 shell works, and to also test that modules work with: from cinderclient.v2.availability_zones import * syntax. Change-Id: I6ae0ada221bebb4ab1850d9c99b10fcbb585201f Implements: https://blueprints.launchpad.net/python-cinderclient/+spec/add-v3-endpoint-support
-rw-r--r--cinderclient/client.py5
-rw-r--r--cinderclient/service_catalog.py8
-rw-r--r--cinderclient/shell.py18
-rw-r--r--cinderclient/tests/unit/v3/__init__.py0
-rw-r--r--cinderclient/tests/unit/v3/fakes.py36
-rw-r--r--cinderclient/tests/unit/v3/test_shell.py73
-rw-r--r--cinderclient/utils.py8
-rw-r--r--cinderclient/v2/availability_zones.py25
-rw-r--r--cinderclient/v2/capabilities.py22
-rw-r--r--cinderclient/v2/cgsnapshots.py109
-rw-r--r--cinderclient/v2/consistencygroups.py145
-rw-r--r--cinderclient/v2/limits.py77
-rw-r--r--cinderclient/v2/pools.py45
-rw-r--r--cinderclient/v2/qos_specs.py136
-rw-r--r--cinderclient/v2/quota_classes.py31
-rw-r--r--cinderclient/v2/quotas.py41
-rw-r--r--cinderclient/v2/services.py60
-rw-r--r--cinderclient/v2/shell.py2596
-rw-r--r--cinderclient/v2/volume_backups.py107
-rw-r--r--cinderclient/v2/volume_backups_restore.py25
-rw-r--r--cinderclient/v2/volume_encryption_types.py85
-rw-r--r--cinderclient/v2/volume_snapshots.py190
-rw-r--r--cinderclient/v2/volume_transfers.py84
-rw-r--r--cinderclient/v2/volume_type_access.py37
-rw-r--r--cinderclient/v2/volume_types.py134
-rw-r--r--cinderclient/v2/volumes.py587
-rw-r--r--cinderclient/v3/__init__.py17
-rw-r--r--cinderclient/v3/availability_zones.py42
-rw-r--r--cinderclient/v3/capabilities.py39
-rw-r--r--cinderclient/v3/cgsnapshots.py126
-rw-r--r--cinderclient/v3/client.py131
-rw-r--r--cinderclient/v3/consistencygroups.py162
-rw-r--r--cinderclient/v3/contrib/__init__.py0
-rw-r--r--cinderclient/v3/contrib/list_extensions.py47
-rw-r--r--cinderclient/v3/limits.py91
-rw-r--r--cinderclient/v3/pools.py62
-rw-r--r--cinderclient/v3/qos_specs.py156
-rw-r--r--cinderclient/v3/quota_classes.py46
-rw-r--r--cinderclient/v3/quotas.py56
-rw-r--r--cinderclient/v3/services.py79
-rw-r--r--cinderclient/v3/shell.py2655
-rw-r--r--cinderclient/v3/volume_backups.py124
-rw-r--r--cinderclient/v3/volume_backups_restore.py43
-rw-r--r--cinderclient/v3/volume_encryption_types.py104
-rw-r--r--cinderclient/v3/volume_snapshots.py205
-rw-r--r--cinderclient/v3/volume_transfers.py101
-rw-r--r--cinderclient/v3/volume_type_access.py53
-rw-r--r--cinderclient/v3/volume_types.py151
-rw-r--r--cinderclient/v3/volumes.py604
49 files changed, 5255 insertions, 4523 deletions
diff --git a/cinderclient/client.py b/cinderclient/client.py
index af8f6fa..622ac71 100644
--- a/cinderclient/client.py
+++ b/cinderclient/client.py
@@ -66,11 +66,11 @@ if not hasattr(urlparse, 'parse_qsl'):
import cgi
urlparse.parse_qsl = cgi.parse_qsl
-_VALID_VERSIONS = ['v1', 'v2']
+_VALID_VERSIONS = ['v1', 'v2', 'v3']
# tell keystoneclient that we can ignore the /v1|v2/{project_id} component of
# the service catalog when doing discovery lookups
-for svc in ('volume', 'volumev2'):
+for svc in ('volume', 'volumev2', 'volumev3'):
discover.add_catalog_discover_hack(svc, re.compile('/v[12]/\w+/?$'), '/')
@@ -580,6 +580,7 @@ def get_client_class(version):
version_map = {
'1': 'cinderclient.v1.client.Client',
'2': 'cinderclient.v2.client.Client',
+ '3': 'cinderclient.v3.client.Client',
}
try:
client_path = version_map[str(version)]
diff --git a/cinderclient/service_catalog.py b/cinderclient/service_catalog.py
index ce78b47..0227750 100644
--- a/cinderclient/service_catalog.py
+++ b/cinderclient/service_catalog.py
@@ -57,16 +57,18 @@ class ServiceCatalog(object):
# enabled and the service_type is set to 'volume', go ahead and
# accept that.
skip_service_type_check = False
- if service_type == 'volumev2' and service['type'] == 'volume':
+ if (service_type in ('volumev2', 'volumev3') and
+ service['type'] == 'volume'):
version = service['endpoints'][0]['publicURL'].split('/')[3]
- if version == 'v2':
+ if version in ('v2', 'v3'):
skip_service_type_check = True
if (not skip_service_type_check
and service.get("type") != service_type):
continue
- if (volume_service_name and service_type in ('volume', 'volumev2')
+ if (volume_service_name and service_type in
+ ('volume', 'volumev2', 'volumev3')
and service.get('name') != volume_service_name):
continue
diff --git a/cinderclient/shell.py b/cinderclient/shell.py
index 9a7ef33..67f1fb8 100644
--- a/cinderclient/shell.py
+++ b/cinderclient/shell.py
@@ -32,8 +32,6 @@ from cinderclient import exceptions as exc
from cinderclient import utils
import cinderclient.auth_plugin
from cinderclient._i18n import _
-from cinderclient.v1 import shell as shell_v1
-from cinderclient.v2 import shell as shell_v2
from keystoneclient import discover
from keystoneclient import session
@@ -54,6 +52,9 @@ _i18n.enable_lazy()
DEFAULT_OS_VOLUME_API_VERSION = "2"
DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL'
DEFAULT_CINDER_SERVICE_TYPE = 'volumev2'
+V1_SHELL = 'cinderclient.v1.shell'
+V2_SHELL = 'cinderclient.v2.shell'
+V3_SHELL = 'cinderclient.v3.shell'
logging.basicConfig()
logger = logging.getLogger(__name__)
@@ -392,13 +393,12 @@ class OpenStackCinderShell(object):
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
- try:
- actions_module = {
- '1.1': shell_v1,
- '2': shell_v2,
- }[version]
- except KeyError:
- actions_module = shell_v1
+ if version == '2':
+ actions_module = importutils.import_module(V2_SHELL)
+ elif version == '3':
+ actions_module = importutils.import_module(V3_SHELL)
+ else:
+ actions_module = importutils.import_module(V1_SHELL)
self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self)
diff --git a/cinderclient/tests/unit/v3/__init__.py b/cinderclient/tests/unit/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cinderclient/tests/unit/v3/__init__.py
diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py
new file mode 100644
index 0000000..679012a
--- /dev/null
+++ b/cinderclient/tests/unit/v3/fakes.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2013 OpenStack Foundation
+#
+# 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.tests.unit import fakes
+from cinderclient.v3 import client
+from cinderclient.tests.unit.v2 import fakes as fake_v2
+
+
+class FakeClient(fakes.FakeClient, client.Client):
+
+ def __init__(self, *args, **kwargs):
+ client.Client.__init__(self, 'username', 'password',
+ 'project_id', 'auth_url',
+ extensions=kwargs.get('extensions'))
+ self.client = FakeHTTPClient(**kwargs)
+
+ def get_volume_api_version_from_endpoint(self):
+ return self.client.get_volume_api_version_from_endpoint()
+
+
+class FakeHTTPClient(fake_v2.FakeHTTPClient):
+
+ def __init__(self, **kwargs):
+ super(FakeHTTPClient, self).__init__()
+ self.management_url = 'http://10.0.2.15:8776/v3/fake'
diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py
new file mode 100644
index 0000000..5925fec
--- /dev/null
+++ b/cinderclient/tests/unit/v3/test_shell.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 fixtures
+import mock
+from requests_mock.contrib import fixture as requests_mock_fixture
+
+from cinderclient import client
+from cinderclient import shell
+from cinderclient.tests.unit import utils
+from cinderclient.tests.unit.v3 import fakes
+from cinderclient.tests.unit.fixture_data import keystone_client
+
+
+class ShellTest(utils.TestCase):
+
+ FAKE_ENV = {
+ 'CINDER_USERNAME': 'username',
+ 'CINDER_PASSWORD': 'password',
+ 'CINDER_PROJECT_ID': 'project_id',
+ 'OS_VOLUME_API_VERSION': '3',
+ 'CINDER_URL': keystone_client.BASE_URL,
+ }
+
+ # Patch os.environ to avoid required auth info.
+ def setUp(self):
+ """Run before each test."""
+ super(ShellTest, self).setUp()
+ for var in self.FAKE_ENV:
+ self.useFixture(fixtures.EnvironmentVariable(var,
+ self.FAKE_ENV[var]))
+
+ self.shell = shell.OpenStackCinderShell()
+
+ # HACK(bcwaldon): replace this when we start using stubs
+ self.old_get_client_class = client.get_client_class
+ client.get_client_class = lambda *_: fakes.FakeClient
+
+ self.requests = self.useFixture(requests_mock_fixture.Fixture())
+ self.requests.register_uri(
+ 'GET', keystone_client.BASE_URL,
+ text=keystone_client.keystone_request_callback)
+
+ self.cs = mock.Mock()
+
+ def run_command(self, cmd):
+ self.shell.main(cmd.split())
+
+ def assert_called(self, method, url, body=None,
+ partial_body=None, **kwargs):
+ return self.shell.cs.assert_called(method, url, body,
+ partial_body, **kwargs)
+
+ def test_list(self):
+ self.run_command('list')
+ # NOTE(jdg): we default to detail currently
+ self.assert_called('GET', '/volumes/detail')
+
+ def test_list_availability_zone(self):
+ self.run_command('availability-zone-list')
+ self.assert_called('GET', '/os-availability-zone')
diff --git a/cinderclient/utils.py b/cinderclient/utils.py
index ff339d0..0b28272 100644
--- a/cinderclient/utils.py
+++ b/cinderclient/utils.py
@@ -18,6 +18,7 @@ from __future__ import print_function
import os
import pkg_resources
import sys
+import types
import uuid
import six
@@ -268,3 +269,10 @@ def _load_entry_point(ep_name, name=None):
return ep.load()
except (ImportError, pkg_resources.UnknownExtra, AttributeError):
continue
+
+
+def retype_method(old_type, new_type, namespace):
+ for attr in namespace.values():
+ if (isinstance(attr, types.FunctionType) and
+ getattr(attr, 'service_type', None) == old_type):
+ setattr(attr, 'service_type', new_type)
diff --git a/cinderclient/v2/availability_zones.py b/cinderclient/v2/availability_zones.py
index aec2279..e70d382 100644
--- a/cinderclient/v2/availability_zones.py
+++ b/cinderclient/v2/availability_zones.py
@@ -13,30 +13,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
"""Availability Zone interface (v2 extension)"""
-from cinderclient import base
-
-
-class AvailabilityZone(base.Resource):
- NAME_ATTR = 'display_name'
-
- def __repr__(self):
- return "<AvailabilityZone: %s>" % self.zoneName
-
-
-class AvailabilityZoneManager(base.ManagerWithFind):
- """Manage :class:`AvailabilityZone` resources."""
- resource_class = AvailabilityZone
-
- def list(self, detailed=False):
- """Lists all availability zones.
+from cinderclient.v3.availability_zones import * # flake8: noqa
- :rtype: list of :class:`AvailabilityZone`
- """
- if detailed is True:
- return self._list("/os-availability-zone/detail",
- "availabilityZoneInfo")
- else:
- return self._list("/os-availability-zone", "availabilityZoneInfo")
diff --git a/cinderclient/v2/capabilities.py b/cinderclient/v2/capabilities.py
index a30c155..61d7717 100644
--- a/cinderclient/v2/capabilities.py
+++ b/cinderclient/v2/capabilities.py
@@ -15,25 +15,5 @@
"""Capabilities interface (v2 extension)"""
+from cinderclient.v3.capabilities import * # flake8: noqa
-from cinderclient import base
-
-
-class Capabilities(base.Resource):
- NAME_ATTR = 'name'
-
- def __repr__(self):
- return "<Capabilities: %s>" % self.name
-
-
-class CapabilitiesManager(base.Manager):
- """Manage :class:`Capabilities` resources."""
- resource_class = Capabilities
-
- def get(self, host):
- """Show backend volume stats and properties.
-
- :param host: Specified backend to obtain volume stats and properties.
- :rtype: :class:`Capabilities`
- """
- return self._get('/capabilities/%s' % host, None)
diff --git a/cinderclient/v2/cgsnapshots.py b/cinderclient/v2/cgsnapshots.py
index f41434a..d911836 100644
--- a/cinderclient/v2/cgsnapshots.py
+++ b/cinderclient/v2/cgsnapshots.py
@@ -15,112 +15,5 @@
"""cgsnapshot interface (v2 extension)."""
-import six
-try:
- from urllib import urlencode
-except ImportError:
- from urllib.parse import urlencode
+from cinderclient.v3.cgsnapshots import * # flake8: noqa
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
-
-
-class Cgsnapshot(base.Resource):
- """A cgsnapshot is snapshot of a consistency group."""
- def __repr__(self):
- return "<cgsnapshot: %s>" % self.id
-
- def delete(self):
- """Delete this cgsnapshot."""
- return self.manager.delete(self)
-
- def update(self, **kwargs):
- """Update the name or description for this cgsnapshot."""
- return self.manager.update(self, **kwargs)
-
-
-class CgsnapshotManager(base.ManagerWithFind):
- """Manage :class:`Cgsnapshot` resources."""
- resource_class = Cgsnapshot
-
- def create(self, consistencygroup_id, name=None, description=None,
- user_id=None,
- project_id=None):
- """Creates a cgsnapshot.
-
- :param consistencygroup: Name or uuid of a consistencygroup
- :param name: Name of the cgsnapshot
- :param description: Description of the cgsnapshot
- :param user_id: User id derived from context
- :param project_id: Project id derived from context
- :rtype: :class:`Cgsnapshot`
- """
-
- body = {'cgsnapshot': {'consistencygroup_id': consistencygroup_id,
- 'name': name,
- 'description': description,
- 'user_id': user_id,
- 'project_id': project_id,
- 'status': "creating",
- }}
-
- return self._create('/cgsnapshots', body, 'cgsnapshot')
-
- def get(self, cgsnapshot_id):
- """Get a cgsnapshot.
-
- :param cgsnapshot_id: The ID of the cgsnapshot to get.
- :rtype: :class:`Cgsnapshot`
- """
- return self._get("/cgsnapshots/%s" % cgsnapshot_id, "cgsnapshot")
-
- def list(self, detailed=True, search_opts=None):
- """Lists all cgsnapshots.
-
- :rtype: list of :class:`Cgsnapshot`
- """
- if search_opts is None:
- search_opts = {}
-
- qparams = {}
-
- for opt, val in six.iteritems(search_opts):
- if val:
- qparams[opt] = val
-
- query_string = "?%s" % urlencode(qparams) if qparams else ""
-
- detail = ""
- if detailed:
- detail = "/detail"
-
- return self._list("/cgsnapshots%s%s" % (detail, query_string),
- "cgsnapshots")
-
- def delete(self, cgsnapshot):
- """Delete a cgsnapshot.
-
- :param cgsnapshot: The :class:`Cgsnapshot` to delete.
- """
- return self._delete("/cgsnapshots/%s" % base.getid(cgsnapshot))
-
- def update(self, cgsnapshot, **kwargs):
- """Update the name or description for a cgsnapshot.
-
- :param cgsnapshot: The :class:`Cgsnapshot` to update.
- """
- if not kwargs:
- return
-
- body = {"cgsnapshot": kwargs}
-
- return self._update("/cgsnapshots/%s" % base.getid(cgsnapshot), body)
-
- def _action(self, action, cgsnapshot, info=None, **kwargs):
- """Perform a cgsnapshot "action."
- """
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/cgsnapshots/%s/action' % base.getid(cgsnapshot)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v2/consistencygroups.py b/cinderclient/v2/consistencygroups.py
index 80d903b..fcee881 100644
--- a/cinderclient/v2/consistencygroups.py
+++ b/cinderclient/v2/consistencygroups.py
@@ -15,148 +15,5 @@
"""Consistencygroup interface (v2 extension)."""
-import six
-try:
- from urllib import urlencode
-except ImportError:
- from urllib.parse import urlencode
+from cinderclient.v3.consistencygroups import * # flake8: noqa
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
-
-
-class Consistencygroup(base.Resource):
- """A Consistencygroup of volumes."""
- def __repr__(self):
- return "<Consistencygroup: %s>" % self.id
-
- def delete(self, force='False'):
- """Delete this consistencygroup."""
- return self.manager.delete(self, force)
-
- def update(self, **kwargs):
- """Update the name or description for this consistencygroup."""
- return self.manager.update(self, **kwargs)
-
-
-class ConsistencygroupManager(base.ManagerWithFind):
- """Manage :class:`Consistencygroup` resources."""
- resource_class = Consistencygroup
-
- def create(self, volume_types, name=None,
- description=None, user_id=None,
- project_id=None, availability_zone=None):
- """Creates a consistencygroup.
-
- :param name: Name of the ConsistencyGroup
- :param description: Description of the ConsistencyGroup
- :param volume_types: Types of volume
- :param user_id: User id derived from context
- :param project_id: Project id derived from context
- :param availability_zone: Availability Zone to use
- :rtype: :class:`Consistencygroup`
- """
-
- body = {'consistencygroup': {'name': name,
- 'description': description,
- 'volume_types': volume_types,
- 'user_id': user_id,
- 'project_id': project_id,
- 'availability_zone': availability_zone,
- 'status': "creating",
- }}
-
- return self._create('/consistencygroups', body, 'consistencygroup')
-
- def create_from_src(self, cgsnapshot_id, source_cgid, name=None,
- description=None, user_id=None,
- project_id=None):
- """Creates a consistencygroup from a cgsnapshot or a source CG.
-
- :param cgsnapshot_id: UUID of a CGSnapshot
- :param source_cgid: UUID of a source CG
- :param name: Name of the ConsistencyGroup
- :param description: Description of the ConsistencyGroup
- :param user_id: User id derived from context
- :param project_id: Project id derived from context
- :rtype: A dictionary containing Consistencygroup metadata
- """
- body = {'consistencygroup-from-src': {'name': name,
- 'description': description,
- 'cgsnapshot_id': cgsnapshot_id,
- 'source_cgid': source_cgid,
- 'user_id': user_id,
- 'project_id': project_id,
- 'status': "creating",
- }}
-
- self.run_hooks('modify_body_for_update', body,
- 'consistencygroup-from-src')
- resp, body = self.api.client.post(
- "/consistencygroups/create_from_src", body=body)
- return common_base.DictWithMeta(body['consistencygroup'], resp)
-
- def get(self, group_id):
- """Get a consistencygroup.
-
- :param group_id: The ID of the consistencygroup to get.
- :rtype: :class:`Consistencygroup`
- """
- return self._get("/consistencygroups/%s" % group_id,
- "consistencygroup")
-
- def list(self, detailed=True, search_opts=None):
- """Lists all consistencygroups.
-
- :rtype: list of :class:`Consistencygroup`
- """
- if search_opts is None:
- search_opts = {}
-
- qparams = {}
-
- for opt, val in six.iteritems(search_opts):
- if val:
- qparams[opt] = val
-
- query_string = "?%s" % urlencode(qparams) if qparams else ""
-
- detail = ""
- if detailed:
- detail = "/detail"
-
- return self._list("/consistencygroups%s%s" % (detail, query_string),
- "consistencygroups")
-
- def delete(self, consistencygroup, force=False):
- """Delete a consistencygroup.
-
- :param Consistencygroup: The :class:`Consistencygroup` to delete.
- """
- body = {'consistencygroup': {'force': force}}
- self.run_hooks('modify_body_for_action', body, 'consistencygroup')
- url = '/consistencygroups/%s/delete' % base.getid(consistencygroup)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
-
- def update(self, consistencygroup, **kwargs):
- """Update the name or description for a consistencygroup.
-
- :param Consistencygroup: The :class:`Consistencygroup` to update.
- """
- if not kwargs:
- return
-
- body = {"consistencygroup": kwargs}
-
- return self._update("/consistencygroups/%s" %
- base.getid(consistencygroup), body)
-
- def _action(self, action, consistencygroup, info=None, **kwargs):
- """Perform a consistencygroup "action."
- """
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/consistencygroups/%s/action' % base.getid(consistencygroup)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v2/limits.py b/cinderclient/v2/limits.py
index 512a58d..2a20684 100644
--- a/cinderclient/v2/limits.py
+++ b/cinderclient/v2/limits.py
@@ -12,80 +12,7 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Limits interface (v2 extension)"""
-from cinderclient import base
+from cinderclient.v3.limits import * # flake8: noqa
-
-class Limits(base.Resource):
- """A collection of RateLimit and AbsoluteLimit objects."""
-
- def __repr__(self):
- return "<Limits>"
-
- @property
- def absolute(self):
- for (name, value) in list(self._info['absolute'].items()):
- yield AbsoluteLimit(name, value)
-
- @property
- def rate(self):
- for group in self._info['rate']:
- uri = group['uri']
- regex = group['regex']
- for rate in group['limit']:
- yield RateLimit(rate['verb'], uri, regex, rate['value'],
- rate['remaining'], rate['unit'],
- rate['next-available'])
-
-
-class RateLimit(object):
- """Data model that represents a flattened view of a single rate limit."""
-
- def __init__(self, verb, uri, regex, value, remain,
- unit, next_available):
- self.verb = verb
- self.uri = uri
- self.regex = regex
- self.value = value
- self.remain = remain
- self.unit = unit
- self.next_available = next_available
-
- def __eq__(self, other):
- return self.uri == other.uri \
- and self.regex == other.regex \
- and self.value == other.value \
- and self.verb == other.verb \
- and self.remain == other.remain \
- and self.unit == other.unit \
- and self.next_available == other.next_available
-
- def __repr__(self):
- return "<RateLimit: method=%s uri=%s>" % (self.verb, self.uri)
-
-
-class AbsoluteLimit(object):
- """Data model that represents a single absolute limit."""
-
- def __init__(self, name, value):
- self.name = name
- self.value = value
-
- def __eq__(self, other):
- return self.value == other.value and self.name == other.name
-
- def __repr__(self):
- return "<AbsoluteLimit: name=%s>" % (self.name)
-
-
-class LimitsManager(base.Manager):
- """Manager object used to interact with limits resource."""
-
- resource_class = Limits
-
- def get(self):
- """Get a specific extension.
-
- :rtype: :class:`Limits`
- """
- return self._get("/limits", "limits")
diff --git a/cinderclient/v2/pools.py b/cinderclient/v2/pools.py
index 608e057..5f3a72c 100644
--- a/cinderclient/v2/pools.py
+++ b/cinderclient/v2/pools.py
@@ -15,48 +15,5 @@
"""Pools interface (v2 extension)"""
-import six
+from cinderclient.v3.pools import * # flake8: noqa
-from cinderclient import base
-
-
-class Pool(base.Resource):
- NAME_ATTR = 'name'
-
- def __repr__(self):
- return "<Pool: %s>" % self.name
-
-
-class PoolManager(base.Manager):
- """Manage :class:`Pool` resources."""
- resource_class = Pool
-
- def list(self, detailed=False):
- """Lists all
-
- :rtype: list of :class:`Pool`
- """
- if detailed is True:
- pools = self._list("/scheduler-stats/get_pools?detail=True",
- "pools")
- # Other than the name, all of the pool data is buried below in
- # a 'capabilities' dictionary. In order to be consistent with the
- # get-pools command line, these elements are moved up a level to
- # be attributes of the pool itself.
- for pool in pools:
- if hasattr(pool, 'capabilities'):
- for k, v in six.iteritems(pool.capabilities):
- setattr(pool, k, v)
-
- # Remove the capabilities dictionary since all of its
- # elements have been copied up to the containing pool
- del pool.capabilities
- return pools
- else:
- pools = self._list("/scheduler-stats/get_pools", "pools")
-
- # avoid cluttering the basic pool list with capabilities dict
- for pool in pools:
- if hasattr(pool, 'capabilities'):
- del pool.capabilities
- return pools
diff --git a/cinderclient/v2/qos_specs.py b/cinderclient/v2/qos_specs.py
index 84b8e0a..8b419b4 100644
--- a/cinderclient/v2/qos_specs.py
+++ b/cinderclient/v2/qos_specs.py
@@ -14,143 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""
QoS Specs interface.
"""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
-
-
-class QoSSpecs(base.Resource):
- """QoS specs entity represents quality-of-service parameters/requirements.
-
- A QoS specs is a set of parameters or requirements for quality-of-service
- purpose, which can be associated with volume types (for now). In future,
- QoS specs may be extended to be associated other entities, such as single
- volume.
- """
- def __repr__(self):
- return "<QoSSpecs: %s>" % self.name
-
- def delete(self):
- return self.manager.delete(self)
-
-
-class QoSSpecsManager(base.ManagerWithFind):
- """
- Manage :class:`QoSSpecs` resources.
- """
- resource_class = QoSSpecs
-
- def list(self, search_opts=None):
- """Get a list of all qos specs.
-
- :rtype: list of :class:`QoSSpecs`.
- """
- return self._list("/qos-specs", "qos_specs")
-
- def get(self, qos_specs):
- """Get a specific qos specs.
-
- :param qos_specs: The ID of the :class:`QoSSpecs` to get.
- :rtype: :class:`QoSSpecs`
- """
- return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs")
-
- def delete(self, qos_specs, force=False):
- """Delete a specific qos specs.
-
- :param qos_specs: The ID of the :class:`QoSSpecs` to be removed.
- :param force: Flag that indicates whether to delete target qos specs
- if it was in-use.
- """
- return self._delete("/qos-specs/%s?force=%s" %
- (base.getid(qos_specs), force))
-
- def create(self, name, specs):
- """Create a qos specs.
-
- :param name: Descriptive name of the qos specs, must be unique
- :param specs: A dict of key/value pairs to be set
- :rtype: :class:`QoSSpecs`
- """
-
- body = {
- "qos_specs": {
- "name": name,
- }
- }
-
- body["qos_specs"].update(specs)
- return self._create("/qos-specs", body, "qos_specs")
-
- def set_keys(self, qos_specs, specs):
- """Add/Update keys in qos specs.
-
- :param qos_specs: The ID of qos specs
- :param specs: A dict of key/value pairs to be set
- :rtype: :class:`QoSSpecs`
- """
-
- body = {
- "qos_specs": {}
- }
-
- body["qos_specs"].update(specs)
- return self._update("/qos-specs/%s" % qos_specs, body)
-
- def unset_keys(self, qos_specs, specs):
- """Remove keys from a qos specs.
-
- :param qos_specs: The ID of qos specs
- :param specs: A list of key to be unset
- :rtype: :class:`QoSSpecs`
- """
-
- body = {'keys': specs}
-
- return self._update("/qos-specs/%s/delete_keys" % qos_specs,
- body)
-
- def get_associations(self, qos_specs):
- """Get associated entities of a qos specs.
-
- :param qos_specs: The id of the :class: `QoSSpecs`
- :return: a list of entities that associated with specific qos specs.
- """
- return self._list("/qos-specs/%s/associations" % base.getid(qos_specs),
- "qos_associations")
-
- def associate(self, qos_specs, vol_type_id):
- """Associate a volume type with specific qos specs.
-
- :param qos_specs: The qos specs to be associated with
- :param vol_type_id: The volume type id to be associated with
- """
- resp, body = self.api.client.get(
- "/qos-specs/%s/associate?vol_type_id=%s" %
- (base.getid(qos_specs), vol_type_id))
- return common_base.TupleWithMeta((resp, body), resp)
-
- def disassociate(self, qos_specs, vol_type_id):
- """Disassociate qos specs from volume type.
-
- :param qos_specs: The qos specs to be associated with
- :param vol_type_id: The volume type id to be associated with
- """
- resp, body = self.api.client.get(
- "/qos-specs/%s/disassociate?vol_type_id=%s" %
- (base.getid(qos_specs), vol_type_id))
- return common_base.TupleWithMeta((resp, body), resp)
-
- def disassociate_all(self, qos_specs):
- """Disassociate all entities from specific qos specs.
+from cinderclient.v3.qos_specs import * # flake8: noqa
- :param qos_specs: The qos specs to be associated with
- """
- resp, body = self.api.client.get(
- "/qos-specs/%s/disassociate_all" %
- base.getid(qos_specs))
- return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v2/quota_classes.py b/cinderclient/v2/quota_classes.py
index 0e5fb5b..28065b8 100644
--- a/cinderclient/v2/quota_classes.py
+++ b/cinderclient/v2/quota_classes.py
@@ -13,34 +13,5 @@
# License for the specific language governing permissions and limitations
# under the License.
-from cinderclient import base
+from cinderclient.v3.quota_classes import * # flake8: noqa
-
-class QuotaClassSet(base.Resource):
-
- @property
- def id(self):
- """Needed by base.Resource to self-refresh and be indexed."""
- return self.class_name
-
- def update(self, *args, **kwargs):
- return self.manager.update(self.class_name, *args, **kwargs)
-
-
-class QuotaClassSetManager(base.Manager):
- resource_class = QuotaClassSet
-
- def get(self, class_name):
- return self._get("/os-quota-class-sets/%s" % (class_name),
- "quota_class_set")
-
- def update(self, class_name, **updates):
- body = {'quota_class_set': {'class_name': class_name}}
-
- for update in updates:
- body['quota_class_set'][update] = updates[update]
-
- result = self._update('/os-quota-class-sets/%s' % (class_name), body)
- return self.resource_class(self,
- result['quota_class_set'], loaded=True,
- resp=result.request_ids)
diff --git a/cinderclient/v2/quotas.py b/cinderclient/v2/quotas.py
index bebf32a..83fb271 100644
--- a/cinderclient/v2/quotas.py
+++ b/cinderclient/v2/quotas.py
@@ -13,44 +13,5 @@
# License for the specific language governing permissions and limitations
# under the License.
-from cinderclient import base
+from cinderclient.v3.quotas import * # flake8: noqa
-
-class QuotaSet(base.Resource):
-
- @property
- def id(self):
- """Needed by base.Resource to self-refresh and be indexed."""
- return self.tenant_id
-
- def update(self, *args, **kwargs):
- return self.manager.update(self.tenant_id, *args, **kwargs)
-
-
-class QuotaSetManager(base.Manager):
- resource_class = QuotaSet
-
- def get(self, tenant_id, usage=False):
- if hasattr(tenant_id, 'tenant_id'):
- tenant_id = tenant_id.tenant_id
- return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage),
- "quota_set")
-
- def update(self, tenant_id, **updates):
- body = {'quota_set': {'tenant_id': tenant_id}}
-
- for update in updates:
- body['quota_set'][update] = updates[update]
-
- result = self._update('/os-quota-sets/%s' % (tenant_id), body)
- return self.resource_class(self, result['quota_set'], loaded=True,
- resp=result.request_ids)
-
- def defaults(self, tenant_id):
- return self._get('/os-quota-sets/%s/defaults' % tenant_id,
- 'quota_set')
-
- def delete(self, tenant_id):
- if hasattr(tenant_id, 'tenant_id'):
- tenant_id = tenant_id.tenant_id
- return self._delete("/os-quota-sets/%s" % tenant_id)
diff --git a/cinderclient/v2/services.py b/cinderclient/v2/services.py
index 8cefc34..b49faca 100644
--- a/cinderclient/v2/services.py
+++ b/cinderclient/v2/services.py
@@ -16,64 +16,6 @@
"""
service interface
"""
-from cinderclient import base
+from cinderclient.v3.services import * # flake8: noqa
-class Service(base.Resource):
-
- def __repr__(self):
- return "<Service: %s>" % self.service
-
-
-class ServiceManager(base.ManagerWithFind):
- resource_class = Service
-
- def list(self, host=None, binary=None):
- """
- Describes service list for host.
-
- :param host: destination host name.
- :param binary: service binary.
- """
- url = "/os-services"
- filters = []
- if host:
- filters.append("host=%s" % host)
- if binary:
- filters.append("binary=%s" % binary)
- if filters:
- url = "%s?%s" % (url, "&".join(filters))
- return self._list(url, "services")
-
- def enable(self, host, binary):
- """Enable the service specified by hostname and binary."""
- body = {"host": host, "binary": binary}
- result = self._update("/os-services/enable", body)
- return self.resource_class(self, result, resp=result.request_ids)
-
- def disable(self, host, binary):
- """Disable the service specified by hostname and binary."""
- body = {"host": host, "binary": binary}
- result = self._update("/os-services/disable", body)
- return self.resource_class(self, result, resp=result.request_ids)
-
- def disable_log_reason(self, host, binary, reason):
- """Disable the service with reason."""
- body = {"host": host, "binary": binary, "disabled_reason": reason}
- result = self._update("/os-services/disable-log-reason", body)
- return self.resource_class(self, result, resp=result.request_ids)
-
- def freeze_host(self, host):
- """Freeze the service specified by hostname."""
- body = {"host": host}
- return self._update("/os-services/freeze", body)
-
- def thaw_host(self, host):
- """Thaw the service specified by hostname."""
- body = {"host": host}
- return self._update("/os-services/thaw", body)
-
- def failover_host(self, host, backend_id):
- """Failover a replicated backend by hostname."""
- body = {"host": host, "backend_id": backend_id}
- return self._update("/os-services/failover_host", body)
diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py
index 19398c0..1e0ddb3 100644
--- a/cinderclient/v2/shell.py
+++ b/cinderclient/v2/shell.py
@@ -14,1698 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import print_function
-
-import argparse
-import copy
-import os
-import sys
-import time
-
-import six
-
-from cinderclient import base
-from cinderclient import exceptions
+from cinderclient.v3.shell import * # flake8: noqa
from cinderclient import utils
-from cinderclient.v2 import availability_zones
-from oslo_utils import strutils
-
-
-def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
- poll_period=5, show_progress=True):
- """Blocks while an action occurs. Periodically shows 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" % {'action': action})
- break
- else:
- print_progress(progress)
- time.sleep(poll_period)
-
-
-def _find_volume_snapshot(cs, snapshot):
- """Gets a volume snapshot by name or ID."""
- return utils.find_resource(cs.volume_snapshots, snapshot)
-
-
-def _find_vtype(cs, vtype):
- """Gets a volume type by name or ID."""
- return utils.find_resource(cs.volume_types, vtype)
-
-
-def _find_backup(cs, backup):
- """Gets a backup by name or ID."""
- return utils.find_resource(cs.backups, backup)
-
-
-def _find_consistencygroup(cs, consistencygroup):
- """Gets a consistencygroup by name or ID."""
- return utils.find_resource(cs.consistencygroups, consistencygroup)
-
-
-def _find_cgsnapshot(cs, cgsnapshot):
- """Gets a cgsnapshot by name or ID."""
- return utils.find_resource(cs.cgsnapshots, cgsnapshot)
-
-
-def _find_transfer(cs, transfer):
- """Gets a transfer by name or ID."""
- return utils.find_resource(cs.transfers, transfer)
-
-
-def _find_qos_specs(cs, qos_specs):
- """Gets a qos specs by ID."""
- return utils.find_resource(cs.qos_specs, qos_specs)
-
-
-def _print_volume_snapshot(snapshot):
- utils.print_dict(snapshot._info)
-
-
-def _print_volume_image(image):
- utils.print_dict(image[1]['os-volume_upload_image'])
-
-
-def _translate_keys(collection, convert):
- for item in collection:
- keys = item.__dict__
- 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_keys(collection):
- convert = [('volumeType', 'volume_type'),
- ('os-vol-tenant-attr:tenant_id', 'tenant_id')]
- _translate_keys(collection, convert)
-
-
-def _translate_volume_snapshot_keys(collection):
- convert = [('volumeId', 'volume_id')]
- _translate_keys(collection, convert)
-
-
-def _translate_availability_zone_keys(collection):
- convert = [('zoneName', 'name'), ('zoneState', 'status')]
- _translate_keys(collection, convert)
-
-
-def _extract_metadata(args):
- metadata = {}
- for metadatum in args.metadata:
- # unset doesn't require a val, so we have the if/else
- if '=' in metadatum:
- (key, value) = metadatum.split('=', 1)
- else:
- key = metadatum
- value = None
-
- metadata[key] = value
- return metadata
-
-
-@utils.arg('--all-tenants',
- dest='all_tenants',
- metavar='<0|1>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.arg('--all_tenants',
- nargs='?',
- type=int,
- const=1,
- help=argparse.SUPPRESS)
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Filters results by a name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.arg('--status',
- metavar='<status>',
- default=None,
- help='Filters results by a status. Default=None.')
-@utils.arg('--bootable',
- metavar='<True|true|False|false>',
- const=True,
- nargs='?',
- choices=['True', 'true', 'False', 'false'],
- help='Filters results by bootable status. Default=None.')
-@utils.arg('--migration_status',
- metavar='<migration_status>',
- default=None,
- help='Filters results by a migration status. Default=None. '
- 'Admin only.')
-@utils.arg('--metadata',
- type=str,
- nargs='*',
- metavar='<key=value>',
- default=None,
- help='Filters results by a metadata key and value pair. '
- 'Default=None.')
-@utils.arg('--marker',
- metavar='<marker>',
- default=None,
- help='Begin returning volumes that appear later in the volume '
- 'list than that represented by this volume id. '
- 'Default=None.')
-@utils.arg('--limit',
- metavar='<limit>',
- default=None,
- help='Maximum number of volumes to return. Default=None.')
-@utils.arg('--fields',
- default=None,
- metavar='<fields>',
- help='Comma-separated list of fields to display. '
- 'Use the show command to see which fields are available. '
- 'Unavailable/non-existent fields will be ignored. '
- 'Default=None.')
-@utils.arg('--sort_key',
- metavar='<sort_key>',
- default=None,
- help=argparse.SUPPRESS)
-@utils.arg('--sort_dir',
- metavar='<sort_dir>',
- default=None,
- help=argparse.SUPPRESS)
-@utils.arg('--sort',
- metavar='<key>[:<direction>]',
- default=None,
- help=(('Comma-separated list of sort keys and directions in the '
- 'form of <key>[:<asc|desc>]. '
- 'Valid keys: %s. '
- 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
-@utils.arg('--tenant',
- type=str,
- dest='tenant',
- nargs='?',
- metavar='<tenant>',
- help='Display information from single tenant (Admin only).')
-@utils.service_type('volumev2')
-def do_list(cs, args):
- """Lists all volumes."""
- # NOTE(thingee): Backwards-compatibility with v1 args
- if args.display_name is not None:
- args.name = args.display_name
-
- all_tenants = 1 if args.tenant else \
- int(os.environ.get("ALL_TENANTS", args.all_tenants))
- search_opts = {
- 'all_tenants': all_tenants,
- 'project_id': args.tenant,
- 'name': args.name,
- 'status': args.status,
- 'bootable': args.bootable,
- 'migration_status': args.migration_status,
- 'metadata': _extract_metadata(args) if args.metadata else None,
- }
-
- # If unavailable/non-existent fields are specified, these fields will
- # be removed from key_list at the print_list() during key validation.
- field_titles = []
- if args.fields:
- for field_title in args.fields.split(','):
- field_titles.append(field_title)
-
- # --sort_key and --sort_dir deprecated in kilo and is not supported
- # with --sort
- if args.sort and (args.sort_key or args.sort_dir):
- raise exceptions.CommandError(
- 'The --sort_key and --sort_dir arguments are deprecated and are '
- 'not supported with --sort.')
-
- volumes = cs.volumes.list(search_opts=search_opts, marker=args.marker,
- limit=args.limit, sort_key=args.sort_key,
- sort_dir=args.sort_dir, sort=args.sort)
- _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)))
-
- if field_titles:
- key_list = ['ID'] + field_titles
- else:
- key_list = ['ID', 'Status', 'Name', 'Size', 'Volume Type',
- 'Bootable', 'Attached to']
- # If all_tenants is specified, print
- # Tenant ID as well.
- if search_opts['all_tenants']:
- key_list.insert(1, 'Tenant ID')
-
- if args.sort_key or args.sort_dir or args.sort:
- sortby_index = None
- else:
- sortby_index = 0
- utils.print_list(volumes, key_list, exclude_unavailable=True,
- sortby_index=sortby_index)
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume.')
-@utils.service_type('volumev2')
-def do_show(cs, args):
- """Shows volume details."""
- info = dict()
- volume = utils.find_volume(cs, args.volume)
- info.update(volume._info)
-
- info.pop('links', None)
- utils.print_dict(info,
- formatters=['metadata', 'volume_image_metadata'])
-
-
-class CheckSizeArgForCreate(argparse.Action):
- def __call__(self, parser, args, values, option_string=None):
- if ((args.snapshot_id or args.source_volid or args.source_replica)
- is None and values is None):
- parser.error('Size is a required parameter if snapshot '
- 'or source volume is not specified.')
- setattr(args, self.dest, values)
-
-
-@utils.arg('size',
- metavar='<size>',
- nargs='?',
- type=int,
- action=CheckSizeArgForCreate,
- help='Size of volume, in GiBs. (Required unless '
- 'snapshot-id/source-volid is specified).')
-@utils.arg('--consisgroup-id',
- metavar='<consistencygroup-id>',
- default=None,
- help='ID of a consistency group where the new volume belongs to. '
- 'Default=None.')
-@utils.arg('--snapshot-id',
- metavar='<snapshot-id>',
- default=None,
- help='Creates volume from snapshot ID. Default=None.')
-@utils.arg('--snapshot_id',
- help=argparse.SUPPRESS)
-@utils.arg('--source-volid',
- metavar='<source-volid>',
- default=None,
- help='Creates volume from volume ID. Default=None.')
-@utils.arg('--source_volid',
- help=argparse.SUPPRESS)
-@utils.arg('--source-replica',
- metavar='<source-replica>',
- default=None,
- help='Creates volume from replicated volume ID. Default=None.')
-@utils.arg('--image-id',
- metavar='<image-id>',
- default=None,
- help='Creates volume from image ID. Default=None.')
-@utils.arg('--image_id',
- help=argparse.SUPPRESS)
-@utils.arg('--image',
- metavar='<image>',
- default=None,
- help='Creates a volume from image (ID or name). Default=None.')
-@utils.arg('--image_ref',
- help=argparse.SUPPRESS)
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Volume name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.arg('--display_name',
- help=argparse.SUPPRESS)
-@utils.arg('--description',
- metavar='<description>',
- default=None,
- help='Volume description. Default=None.')
-@utils.arg('--display-description',
- help=argparse.SUPPRESS)
-@utils.arg('--display_description',
- help=argparse.SUPPRESS)
-@utils.arg('--volume-type',
- metavar='<volume-type>',
- default=None,
- help='Volume type. Default=None.')
-@utils.arg('--volume_type',
- help=argparse.SUPPRESS)
-@utils.arg('--availability-zone',
- metavar='<availability-zone>',
- default=None,
- help='Availability zone for volume. Default=None.')
-@utils.arg('--availability_zone',
- help=argparse.SUPPRESS)
-@utils.arg('--metadata',
- type=str,
- nargs='*',
- metavar='<key=value>',
- default=None,
- help='Metadata key and value pairs. Default=None.')
-@utils.arg('--hint',
- metavar='<key=value>',
- dest='scheduler_hints',
- action='append',
- default=[],
- help='Scheduler hint, like in nova.')
-@utils.arg('--allow-multiattach',
- dest='multiattach',
- action="store_true",
- help=('Allow volume to be attached more than once.'
- ' Default=False'),
- default=False)
-@utils.service_type('volumev2')
-def do_create(cs, args):
- """Creates a volume."""
- # NOTE(thingee): Backwards-compatibility with v1 args
- if args.display_name is not None:
- args.name = args.display_name
-
- if args.display_description is not None:
- args.description = args.display_description
-
- volume_metadata = None
- if args.metadata is not None:
- volume_metadata = _extract_metadata(args)
-
- # NOTE(N.S.): take this piece from novaclient
- hints = {}
- if args.scheduler_hints:
- for hint in args.scheduler_hints:
- key, _sep, value = hint.partition('=')
- # NOTE(vish): multiple copies of same hint will
- # result in a list of values
- if key in hints:
- if isinstance(hints[key], six.string_types):
- hints[key] = [hints[key]]
- hints[key] += [value]
- else:
- hints[key] = value
- # NOTE(N.S.): end of taken piece
-
- # Keep backward compatibility with image_id, favoring explicit ID
- image_ref = args.image_id or args.image or args.image_ref
-
- volume = cs.volumes.create(args.size,
- args.consisgroup_id,
- args.snapshot_id,
- args.source_volid,
- args.name,
- args.description,
- args.volume_type,
- availability_zone=args.availability_zone,
- imageRef=image_ref,
- metadata=volume_metadata,
- scheduler_hints=hints,
- source_replica=args.source_replica,
- multiattach=args.multiattach)
-
- info = dict()
- volume = cs.volumes.get(volume.id)
- info.update(volume._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('--cascade',
- metavar='<cascade>',
- default=False,
- const=True,
- nargs='?',
- help='Remove any snapshots along with volume. Default=False.')
-@utils.arg('volume',
- metavar='<volume>', nargs='+',
- help='Name or ID of volume or volumes to delete.')
-@utils.service_type('volumev2')
-def do_delete(cs, args):
- """Removes one or more volumes."""
- failure_count = 0
- for volume in args.volume:
- try:
- utils.find_volume(cs, volume).delete(cascade=args.cascade)
- print("Request to delete volume %s has been accepted." % (volume))
- except Exception as e:
- failure_count += 1
- print("Delete for volume %s failed: %s" % (volume, e))
- if failure_count == len(args.volume):
- raise exceptions.CommandError("Unable to delete any of the specified "
- "volumes.")
-
-
-@utils.arg('volume',
- metavar='<volume>', nargs='+',
- help='Name or ID of volume or volumes to delete.')
-@utils.service_type('volumev2')
-def do_force_delete(cs, args):
- """Attempts force-delete of volume, regardless of state."""
- failure_count = 0
- for volume in args.volume:
- try:
- utils.find_volume(cs, volume).force_delete()
- except Exception as e:
- failure_count += 1
- print("Delete for volume %s failed: %s" % (volume, e))
- if failure_count == len(args.volume):
- raise exceptions.CommandError("Unable to force delete any of the "
- "specified volumes.")
-
-
-@utils.arg('volume', metavar='<volume>', nargs='+',
- help='Name or ID of volume to modify.')
-@utils.arg('--state', metavar='<state>', default='available',
- help=('The state to assign to the volume. Valid values are '
- '"available", "error", "creating", "deleting", "in-use", '
- '"attaching", "detaching", "error_deleting" and '
- '"maintenance". '
- 'NOTE: This command simply changes the state of the '
- 'Volume in the DataBase with no regard to actual status, '
- 'exercise caution when using. Default=available.'))
-@utils.arg('--attach-status', metavar='<attach-status>', default=None,
- help=('The attach status to assign to the volume in the DataBase, '
- 'with no regard to the actual status. Valid values are '
- '"attached" and "detached". Default=None, that means the '
- 'status is unchanged.'))
-@utils.arg('--reset-migration-status',
- action='store_true',
- help=('Clears the migration status of the volume in the DataBase '
- 'that indicates the volume is source or destination of '
- 'volume migration, with no regard to the actual status.'))
-@utils.service_type('volumev2')
-def do_reset_state(cs, args):
- """Explicitly updates the volume state in the Cinder database.
-
- Note that this does not affect whether the volume is actually attached to
- the Nova compute host or instance and can result in an unusable volume.
- Being a database change only, this has no impact on the true state of the
- volume and may not match the actual state. This can render a volume
- unusable in the case of change to the 'available' state.
- """
- failure_flag = False
- migration_status = 'none' if args.reset_migration_status else None
-
- for volume in args.volume:
- try:
- utils.find_volume(cs, volume).reset_state(args.state,
- args.attach_status,
- migration_status)
- except Exception as e:
- failure_flag = True
- msg = "Reset state for volume %s failed: %s" % (volume, e)
- print(msg)
-
- if failure_flag:
- msg = "Unable to reset the state for the specified volume(s)."
- raise exceptions.CommandError(msg)
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume to rename.')
-@utils.arg('name',
- nargs='?',
- metavar='<name>',
- help='New name for volume.')
-@utils.arg('--description', metavar='<description>',
- help='Volume description. Default=None.',
- default=None)
-@utils.arg('--display-description',
- help=argparse.SUPPRESS)
-@utils.arg('--display_description',
- help=argparse.SUPPRESS)
-@utils.service_type('volumev2')
-def do_rename(cs, args):
- """Renames a volume."""
- kwargs = {}
-
- if args.name is not None:
- kwargs['name'] = args.name
- if args.display_description is not None:
- kwargs['description'] = args.display_description
- elif args.description is not None:
- kwargs['description'] = args.description
-
- if not any(kwargs):
- msg = 'Must supply either name or description.'
- raise exceptions.ClientException(code=1, message=msg)
-
- utils.find_volume(cs, args.volume).update(**kwargs)
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume for which to update metadata.')
-@utils.arg('action',
- metavar='<action>',
- choices=['set', 'unset'],
- help='The action. Valid values are "set" or "unset."')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='Metadata key and value pair to set or unset. '
- 'For unset, specify only the key.')
-@utils.service_type('volumev2')
-def do_metadata(cs, args):
- """Sets or deletes volume metadata."""
- volume = utils.find_volume(cs, args.volume)
- metadata = _extract_metadata(args)
-
- if args.action == 'set':
- cs.volumes.set_metadata(volume, metadata)
- elif args.action == 'unset':
- # NOTE(zul): Make sure py2/py3 sorting is the same
- cs.volumes.delete_metadata(volume, sorted(metadata.keys(),
- reverse=True))
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume for which to update metadata.')
-@utils.arg('action',
- metavar='<action>',
- choices=['set', 'unset'],
- help="The action. Valid values are 'set' or 'unset.'")
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='Metadata key and value pair to set or unset. '
- 'For unset, specify only the key.')
-@utils.service_type('volumev2')
-def do_image_metadata(cs, args):
- """Sets or deletes volume image metadata."""
- volume = utils.find_volume(cs, args.volume)
- metadata = _extract_metadata(args)
-
- if args.action == 'set':
- cs.volumes.set_image_metadata(volume, metadata)
- elif args.action == 'unset':
- cs.volumes.delete_image_metadata(volume, sorted(metadata.keys(),
- reverse=True))
-
-
-@utils.arg('--all-tenants',
- dest='all_tenants',
- metavar='<0|1>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.arg('--all_tenants',
- nargs='?',
- type=int,
- const=1,
- help=argparse.SUPPRESS)
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Filters results by a name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.arg('--display_name',
- help=argparse.SUPPRESS)
-@utils.arg('--status',
- metavar='<status>',
- default=None,
- help='Filters results by a status. Default=None.')
-@utils.arg('--volume-id',
- metavar='<volume-id>',
- default=None,
- help='Filters results by a volume ID. Default=None.')
-@utils.arg('--volume_id',
- help=argparse.SUPPRESS)
-@utils.arg('--marker',
- metavar='<marker>',
- default=None,
- help='Begin returning snapshots that appear later in the snapshot '
- 'list than that represented by this id. '
- 'Default=None.')
-@utils.arg('--limit',
- metavar='<limit>',
- default=None,
- help='Maximum number of snapshots to return. Default=None.')
-@utils.arg('--sort',
- metavar='<key>[:<direction>]',
- default=None,
- help=(('Comma-separated list of sort keys and directions in the '
- 'form of <key>[:<asc|desc>]. '
- 'Valid keys: %s. '
- 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
-@utils.arg('--tenant',
- type=str,
- dest='tenant',
- nargs='?',
- metavar='<tenant>',
- help='Display information from single tenant (Admin only).')
-@utils.service_type('volumev2')
-def do_snapshot_list(cs, args):
- """Lists all snapshots."""
- all_tenants = (1 if args.tenant else
- int(os.environ.get("ALL_TENANTS", args.all_tenants)))
-
- if args.display_name is not None:
- args.name = args.display_name
-
- search_opts = {
- 'all_tenants': all_tenants,
- 'display_name': args.name,
- 'status': args.status,
- 'volume_id': args.volume_id,
- 'project_id': args.tenant,
- }
-
- snapshots = cs.volume_snapshots.list(search_opts=search_opts,
- marker=args.marker,
- limit=args.limit,
- sort=args.sort)
- _translate_volume_snapshot_keys(snapshots)
- if args.sort:
- sortby_index = None
- else:
- sortby_index = 0
-
- utils.print_list(snapshots,
- ['ID', 'Volume ID', 'Status', 'Name', 'Size'],
- sortby_index=sortby_index)
-
-
-@utils.arg('snapshot',
- metavar='<snapshot>',
- help='Name or ID of snapshot.')
-@utils.service_type('volumev2')
-def do_snapshot_show(cs, args):
- """Shows snapshot details."""
- snapshot = _find_volume_snapshot(cs, args.snapshot)
- _print_volume_snapshot(snapshot)
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume to snapshot.')
-@utils.arg('--force',
- metavar='<True|False>',
- const=True,
- nargs='?',
- default=False,
- help='Allows or disallows snapshot of '
- 'a volume when the volume is attached to an instance. '
- 'If set to True, ignores the current status of the '
- 'volume when attempting to snapshot it rather '
- 'than forcing it to be available. '
- 'Default=False.')
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Snapshot name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.arg('--display_name',
- help=argparse.SUPPRESS)
-@utils.arg('--description',
- metavar='<description>',
- default=None,
- help='Snapshot description. Default=None.')
-@utils.arg('--display-description',
- help=argparse.SUPPRESS)
-@utils.arg('--display_description',
- help=argparse.SUPPRESS)
-@utils.arg('--metadata',
- type=str,
- nargs='*',
- metavar='<key=value>',
- default=None,
- help='Snapshot metadata key and value pairs. Default=None.')
-@utils.service_type('volumev2')
-def do_snapshot_create(cs, args):
- """Creates a snapshot."""
- if args.display_name is not None:
- args.name = args.display_name
-
- if args.display_description is not None:
- args.description = args.display_description
-
- snapshot_metadata = None
- if args.metadata is not None:
- snapshot_metadata = _extract_metadata(args)
-
- volume = utils.find_volume(cs, args.volume)
- snapshot = cs.volume_snapshots.create(volume.id,
- args.force,
- args.name,
- args.description,
- metadata=snapshot_metadata)
- _print_volume_snapshot(snapshot)
-
-
-@utils.arg('snapshot',
- metavar='<snapshot>', nargs='+',
- help='Name or ID of the snapshot(s) to delete.')
-@utils.service_type('volumev2')
-def do_snapshot_delete(cs, args):
- """Removes one or more snapshots."""
- failure_count = 0
- for snapshot in args.snapshot:
- try:
- _find_volume_snapshot(cs, snapshot).delete()
- except Exception as e:
- failure_count += 1
- print("Delete for snapshot %s failed: %s" % (snapshot, e))
- if failure_count == len(args.snapshot):
- raise exceptions.CommandError("Unable to delete any of the specified "
- "snapshots.")
-
-
-@utils.arg('snapshot', metavar='<snapshot>',
- help='Name or ID of snapshot.')
-@utils.arg('name', nargs='?', metavar='<name>',
- help='New name for snapshot.')
-@utils.arg('--description', metavar='<description>',
- default=None,
- help='Snapshot description. Default=None.')
-@utils.arg('--display-description',
- help=argparse.SUPPRESS)
-@utils.arg('--display_description',
- help=argparse.SUPPRESS)
-@utils.service_type('volumev2')
-def do_snapshot_rename(cs, args):
- """Renames a snapshot."""
- kwargs = {}
-
- if args.name is not None:
- kwargs['name'] = args.name
-
- if args.description is not None:
- kwargs['description'] = args.description
- elif args.display_description is not None:
- kwargs['description'] = args.display_description
-
- if not any(kwargs):
- msg = 'Must supply either name or description.'
- raise exceptions.ClientException(code=1, message=msg)
-
- _find_volume_snapshot(cs, args.snapshot).update(**kwargs)
-
-
-@utils.arg('snapshot', metavar='<snapshot>', nargs='+',
- help='Name or ID of snapshot to modify.')
-@utils.arg('--state', metavar='<state>',
- default='available',
- help=('The state to assign to the snapshot. Valid values are '
- '"available", "error", "creating", "deleting", and '
- '"error_deleting". NOTE: This command simply changes '
- 'the state of the Snapshot in the DataBase with no regard '
- 'to actual status, exercise caution when using. '
- 'Default=available.'))
-@utils.service_type('volumev2')
-def do_snapshot_reset_state(cs, args):
- """Explicitly updates the snapshot state."""
- failure_count = 0
-
- single = (len(args.snapshot) == 1)
-
- for snapshot in args.snapshot:
- try:
- _find_volume_snapshot(cs, snapshot).reset_state(args.state)
- except Exception as e:
- failure_count += 1
- msg = "Reset state for snapshot %s failed: %s" % (snapshot, e)
- if not single:
- print(msg)
-
- if failure_count == len(args.snapshot):
- if not single:
- msg = ("Unable to reset the state for any of the specified "
- "snapshots.")
- raise exceptions.CommandError(msg)
-
-
-def _print_volume_type_list(vtypes):
- utils.print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public'])
-
-
-@utils.service_type('volumev2')
-def do_type_list(cs, args):
- """Lists available 'volume types'. (Admin only will see private types)"""
- vtypes = cs.volume_types.list()
- _print_volume_type_list(vtypes)
-
-
-@utils.service_type('volumev2')
-def do_type_default(cs, args):
- """List the default volume type."""
- vtype = cs.volume_types.default()
- _print_volume_type_list([vtype])
-
-
-@utils.arg('volume_type',
- metavar='<volume_type>',
- help='Name or ID of the volume type.')
-@utils.service_type('volumev2')
-def do_type_show(cs, args):
- """Show volume type details."""
- vtype = _find_vtype(cs, args.volume_type)
- info = dict()
- info.update(vtype._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('id',
- metavar='<id>',
- help='ID of the volume type.')
-@utils.arg('--name',
- metavar='<name>',
- help='Name of the volume type.')
-@utils.arg('--description',
- metavar='<description>',
- help='Description of the volume type.')
-@utils.arg('--is-public',
- metavar='<is-public>',
- help='Make type accessible to the public or not.')
-@utils.service_type('volumev2')
-def do_type_update(cs, args):
- """Updates volume type name, description, and/or is_public."""
- is_public = strutils.bool_from_string(args.is_public)
- vtype = cs.volume_types.update(args.id, args.name, args.description,
- is_public)
- _print_volume_type_list([vtype])
-
-
-@utils.service_type('volumev2')
-def do_extra_specs_list(cs, args):
- """Lists current volume types and extra specs."""
- vtypes = cs.volume_types.list()
- utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'])
-
-
-@utils.arg('name',
- metavar='<name>',
- help='Name of new volume type.')
-@utils.arg('--description',
- metavar='<description>',
- help='Description of new volume type.')
-@utils.arg('--is-public',
- metavar='<is-public>',
- default=True,
- help='Make type accessible to the public (default true).')
-@utils.service_type('volumev2')
-def do_type_create(cs, args):
- """Creates a volume type."""
- is_public = strutils.bool_from_string(args.is_public)
- vtype = cs.volume_types.create(args.name, args.description, is_public)
- _print_volume_type_list([vtype])
-
-
-@utils.arg('vol_type',
- metavar='<vol_type>', nargs='+',
- help='Name or ID of volume type or types to delete.')
-@utils.service_type('volumev2')
-def do_type_delete(cs, args):
- """Deletes volume type or types."""
- failure_count = 0
- for vol_type in args.vol_type:
- try:
- vtype = _find_volume_type(cs, vol_type)
- cs.volume_types.delete(vtype)
- print("Request to delete volume type %s has been accepted."
- % (vol_type))
- except Exception as e:
- failure_count += 1
- print("Delete for volume type %s failed: %s" % (vol_type, e))
- if failure_count == len(args.vol_type):
- raise exceptions.CommandError("Unable to delete any of the "
- "specified types.")
-
-
-@utils.arg('vtype',
- metavar='<vtype>',
- help='Name or ID of volume type.')
-@utils.arg('action',
- metavar='<action>',
- choices=['set', 'unset'],
- help='The action. Valid values are "set" or "unset."')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='The extra specs key and value pair to set or unset. '
- 'For unset, specify only the key.')
-@utils.service_type('volumev2')
-def do_type_key(cs, args):
- """Sets or unsets extra_spec for a volume type."""
- vtype = _find_volume_type(cs, args.vtype)
- keypair = _extract_metadata(args)
-
- if args.action == 'set':
- vtype.set_keys(keypair)
- elif args.action == 'unset':
- vtype.unset_keys(list(keypair))
-
-
-@utils.arg('--volume-type', metavar='<volume_type>', required=True,
- help='Filter results by volume type name or ID.')
-@utils.service_type('volumev2')
-def do_type_access_list(cs, args):
- """Print access information about the given volume type."""
- volume_type = _find_volume_type(cs, args.volume_type)
- if volume_type.is_public:
- raise exceptions.CommandError("Failed to get access list "
- "for public volume type.")
- access_list = cs.volume_type_access.list(volume_type)
-
- columns = ['Volume_type_ID', 'Project_ID']
- utils.print_list(access_list, columns)
-
-
-@utils.arg('--volume-type', metavar='<volume_type>', required=True,
- help='Volume type name or ID to add access for the given project.')
-@utils.arg('--project-id', metavar='<project_id>', required=True,
- help='Project ID to add volume type access for.')
-@utils.service_type('volumev2')
-def do_type_access_add(cs, args):
- """Adds volume type access for the given project."""
- vtype = _find_volume_type(cs, args.volume_type)
- cs.volume_type_access.add_project_access(vtype, args.project_id)
-
-
-@utils.arg('--volume-type', metavar='<volume_type>', required=True,
- help=('Volume type name or ID to remove access '
- 'for the given project.'))
-@utils.arg('--project-id', metavar='<project_id>', required=True,
- help='Project ID to remove volume type access for.')
-@utils.service_type('volumev2')
-def do_type_access_remove(cs, args):
- """Removes volume type access for the given project."""
- vtype = _find_volume_type(cs, args.volume_type)
- cs.volume_type_access.remove_project_access(
- vtype, args.project_id)
-
-
-@utils.service_type('volumev2')
-def do_endpoints(cs, args):
- """Discovers endpoints registered by authentication service."""
- catalog = cs.client.service_catalog.catalog
- for e in catalog['serviceCatalog']:
- utils.print_dict(e['endpoints'][0], e['name'])
-
-
-@utils.service_type('volumev2')
-def do_credentials(cs, args):
- """Shows user credentials returned from auth."""
- catalog = cs.client.service_catalog.catalog
-
- # formatters defines field to be converted from unicode to string
- utils.print_dict(catalog['user'], "User Credentials",
- formatters=['domain', 'roles'])
- utils.print_dict(catalog['token'], "Token",
- formatters=['audit_ids', 'tenant'])
-
-_quota_resources = ['volumes', 'snapshots', 'gigabytes',
- 'backups', 'backup_gigabytes',
- 'consistencygroups', 'per_volume_gigabytes']
-_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
-
-
-def _quota_show(quotas):
- quota_dict = {}
- for resource in quotas._info:
- good_name = False
- for name in _quota_resources:
- if resource.startswith(name):
- good_name = True
- if not good_name:
- continue
- quota_dict[resource] = getattr(quotas, resource, None)
- utils.print_dict(quota_dict)
-
-
-def _quota_usage_show(quotas):
- quota_list = []
- for resource in quotas._info.keys():
- good_name = False
- for name in _quota_resources:
- if resource.startswith(name):
- good_name = True
- if not good_name:
- continue
- quota_info = getattr(quotas, resource, None)
- quota_info['Type'] = resource
- quota_info = dict((k.capitalize(), v) for k, v in quota_info.items())
- quota_list.append(quota_info)
- utils.print_list(quota_list, _quota_infos)
-
-
-def _quota_update(manager, identifier, args):
- updates = {}
- for resource in _quota_resources:
- val = getattr(args, resource, None)
- if val is not None:
- if args.volume_type:
- resource = resource + '_%s' % args.volume_type
- updates[resource] = val
-
- if updates:
- _quota_show(manager.update(identifier, **updates))
-
-
-@utils.arg('tenant',
- metavar='<tenant_id>',
- help='ID of tenant for which to list quotas.')
-@utils.service_type('volumev2')
-def do_quota_show(cs, args):
- """Lists quotas for a tenant."""
-
- _quota_show(cs.quotas.get(args.tenant))
-
-
-@utils.arg('tenant', metavar='<tenant_id>',
- help='ID of tenant for which to list quota usage.')
-@utils.service_type('volumev2')
-def do_quota_usage(cs, args):
- """Lists quota usage for a tenant."""
-
- _quota_usage_show(cs.quotas.get(args.tenant, usage=True))
-
-
-@utils.arg('tenant',
- metavar='<tenant_id>',
- help='ID of tenant for which to list quota defaults.')
-@utils.service_type('volumev2')
-def do_quota_defaults(cs, args):
- """Lists default quotas for a tenant."""
-
- _quota_show(cs.quotas.defaults(args.tenant))
-
-
-@utils.arg('tenant',
- metavar='<tenant_id>',
- help='ID of tenant for which to set quotas.')
-@utils.arg('--volumes',
- metavar='<volumes>',
- type=int, default=None,
- help='The new "volumes" quota value. Default=None.')
-@utils.arg('--snapshots',
- metavar='<snapshots>',
- type=int, default=None,
- help='The new "snapshots" quota value. Default=None.')
-@utils.arg('--gigabytes',
- metavar='<gigabytes>',
- type=int, default=None,
- help='The new "gigabytes" quota value. Default=None.')
-@utils.arg('--backups',
- metavar='<backups>',
- type=int, default=None,
- help='The new "backups" quota value. Default=None.')
-@utils.arg('--backup-gigabytes',
- metavar='<backup_gigabytes>',
- type=int, default=None,
- help='The new "backup_gigabytes" quota value. Default=None.')
-@utils.arg('--consistencygroups',
- metavar='<consistencygroups>',
- type=int, default=None,
- help='The new "consistencygroups" quota value. Default=None.')
-@utils.arg('--volume-type',
- metavar='<volume_type_name>',
- default=None,
- help='Volume type. Default=None.')
-@utils.arg('--per-volume-gigabytes',
- metavar='<per_volume_gigabytes>',
- type=int, default=None,
- help='Set max volume size limit. Default=None.')
-@utils.service_type('volumev2')
-def do_quota_update(cs, args):
- """Updates quotas for a tenant."""
-
- _quota_update(cs.quotas, args.tenant, args)
-
-
-@utils.arg('tenant', metavar='<tenant_id>',
- help='UUID of tenant to delete the quotas for.')
-@utils.service_type('volumev2')
-def do_quota_delete(cs, args):
- """Delete the quotas for a tenant."""
-
- cs.quotas.delete(args.tenant)
-
-
-@utils.arg('class_name',
- metavar='<class>',
- help='Name of quota class for which to list quotas.')
-@utils.service_type('volumev2')
-def do_quota_class_show(cs, args):
- """Lists quotas for a quota class."""
-
- _quota_show(cs.quota_classes.get(args.class_name))
-
-
-@utils.arg('class_name',
- metavar='<class_name>',
- help='Name of quota class for which to set quotas.')
-@utils.arg('--volumes',
- metavar='<volumes>',
- type=int, default=None,
- help='The new "volumes" quota value. Default=None.')
-@utils.arg('--snapshots',
- metavar='<snapshots>',
- type=int, default=None,
- help='The new "snapshots" quota value. Default=None.')
-@utils.arg('--gigabytes',
- metavar='<gigabytes>',
- type=int, default=None,
- help='The new "gigabytes" quota value. Default=None.')
-@utils.arg('--volume-type',
- metavar='<volume_type_name>',
- default=None,
- help='Volume type. Default=None.')
-@utils.service_type('volumev2')
-def do_quota_class_update(cs, args):
- """Updates quotas for a quota class."""
-
- _quota_update(cs.quota_classes, args.class_name, args)
-
-
-@utils.service_type('volumev2')
-def do_absolute_limits(cs, args):
- """Lists absolute limits for a user."""
- limits = cs.limits.get().absolute
- columns = ['Name', 'Value']
- utils.print_list(limits, columns)
-
-
-@utils.service_type('volumev2')
-def do_rate_limits(cs, args):
- """Lists rate limits for a user."""
- limits = cs.limits.get().rate
- columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
- utils.print_list(limits, columns)
-
-
-def _find_volume_type(cs, vtype):
- """Gets a volume type by name or ID."""
- return utils.find_resource(cs.volume_types, vtype)
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Name or ID of volume to snapshot.')
-@utils.arg('--force',
- metavar='<True|False>',
- const=True,
- nargs='?',
- default=False,
- help='Enables or disables upload of '
- 'a volume that is attached to an instance. '
- 'Default=False.')
-@utils.arg('--container-format',
- metavar='<container-format>',
- default='bare',
- help='Container format type. '
- 'Default is bare.')
-@utils.arg('--container_format',
- help=argparse.SUPPRESS)
-@utils.arg('--disk-format',
- metavar='<disk-format>',
- default='raw',
- help='Disk format type. '
- 'Default is raw.')
-@utils.arg('--disk_format',
- help=argparse.SUPPRESS)
-@utils.arg('image_name',
- metavar='<image-name>',
- help='The new image name.')
-@utils.arg('--image_name',
- help=argparse.SUPPRESS)
-@utils.service_type('volumev2')
-def do_upload_to_image(cs, args):
- """Uploads volume to Image Service as an image."""
- volume = utils.find_volume(cs, args.volume)
- _print_volume_image(volume.upload_to_image(args.force,
- args.image_name,
- args.container_format,
- args.disk_format))
-
-
-@utils.arg('volume', metavar='<volume>', help='ID of volume to migrate.')
-@utils.arg('host', metavar='<host>', help='Destination host. Takes the form: '
- 'host@backend-name#pool')
-@utils.arg('--force-host-copy', metavar='<True|False>',
- choices=['True', 'False'],
- required=False,
- const=True,
- nargs='?',
- default=False,
- help='Enables or disables generic host-based '
- 'force-migration, which bypasses driver '
- 'optimizations. Default=False.')
-@utils.arg('--lock-volume', metavar='<True|False>',
- choices=['True', 'False'],
- required=False,
- const=True,
- nargs='?',
- default=False,
- help='Enables or disables the termination of volume migration '
- 'caused by other commands. This option applies to the '
- 'available volume. True means it locks the volume '
- 'state and does not allow the migration to be aborted. The '
- 'volume status will be in maintenance during the '
- 'migration. False means it allows the volume migration '
- 'to be aborted. The volume status is still in the original '
- 'status. Default=False.')
-@utils.service_type('volumev2')
-def do_migrate(cs, args):
- """Migrates volume to a new host."""
- volume = utils.find_volume(cs, args.volume)
- try:
- volume.migrate_volume(args.host, args.force_host_copy,
- args.lock_volume)
- print("Request to migrate volume %s has been accepted." % (volume))
- except Exception as e:
- print("Migration for volume %s failed: %s." % (volume,
- six.text_type(e)))
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of volume for which to modify type.')
-@utils.arg('new_type', metavar='<volume-type>', help='New volume type.')
-@utils.arg('--migration-policy', metavar='<never|on-demand>', required=False,
- choices=['never', 'on-demand'], default='never',
- help='Migration policy during retype of volume.')
-@utils.service_type('volumev2')
-def do_retype(cs, args):
- """Changes the volume type for a volume."""
- volume = utils.find_volume(cs, args.volume)
- volume.retype(args.new_type, args.migration_policy)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of volume to backup.')
-@utils.arg('--container', metavar='<container>',
- default=None,
- help='Backup container name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.arg('--name', metavar='<name>',
- default=None,
- help='Backup name. Default=None.')
-@utils.arg('--display-description',
- help=argparse.SUPPRESS)
-@utils.arg('--description',
- metavar='<description>',
- default=None,
- help='Backup description. Default=None.')
-@utils.arg('--incremental',
- action='store_true',
- help='Incremental backup. Default=False.',
- default=False)
-@utils.arg('--force',
- action='store_true',
- help='Allows or disallows backup of a volume '
- 'when the volume is attached to an instance. '
- 'If set to True, backs up the volume whether '
- 'its status is "available" or "in-use". The backup '
- 'of an "in-use" volume means your data is crash '
- 'consistent. Default=False.',
- default=False)
-@utils.arg('--snapshot-id',
- metavar='<snapshot-id>',
- default=None,
- help='ID of snapshot to backup. Default=None.')
-@utils.service_type('volumev2')
-def do_backup_create(cs, args):
- """Creates a volume backup."""
- if args.display_name is not None:
- args.name = args.display_name
-
- if args.display_description is not None:
- args.description = args.display_description
-
- volume = utils.find_volume(cs, args.volume)
- backup = cs.backups.create(volume.id,
- args.container,
- args.name,
- args.description,
- args.incremental,
- args.force,
- args.snapshot_id)
-
- info = {"volume_id": volume.id}
- info.update(backup._info)
-
- if 'links' in info:
- info.pop('links')
-
- utils.print_dict(info)
+utils.retype_method('volumev3', 'volumev2', globals())
-@utils.arg('backup', metavar='<backup>', help='Name or ID of backup.')
-@utils.service_type('volumev2')
-def do_backup_show(cs, args):
- """Shows backup details."""
- backup = _find_backup(cs, args.backup)
- info = dict()
- info.update(backup._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('--all-tenants',
- metavar='<all_tenants>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.arg('--all_tenants',
- nargs='?',
- type=int,
- const=1,
- help=argparse.SUPPRESS)
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Filters results by a name. Default=None.')
-@utils.arg('--status',
- metavar='<status>',
- default=None,
- help='Filters results by a status. Default=None.')
-@utils.arg('--volume-id',
- metavar='<volume-id>',
- default=None,
- help='Filters results by a volume ID. Default=None.')
-@utils.arg('--volume_id',
- help=argparse.SUPPRESS)
-@utils.arg('--marker',
- metavar='<marker>',
- default=None,
- help='Begin returning backups that appear later in the backup '
- 'list than that represented by this id. '
- 'Default=None.')
-@utils.arg('--limit',
- metavar='<limit>',
- default=None,
- help='Maximum number of backups to return. Default=None.')
-@utils.arg('--sort',
- metavar='<key>[:<direction>]',
- default=None,
- help=(('Comma-separated list of sort keys and directions in the '
- 'form of <key>[:<asc|desc>]. '
- 'Valid keys: %s. '
- 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
-@utils.service_type('volumev2')
-def do_backup_list(cs, args):
- """Lists all backups."""
-
- search_opts = {
- 'all_tenants': args.all_tenants,
- 'name': args.name,
- 'status': args.status,
- 'volume_id': args.volume_id,
- }
-
- backups = cs.backups.list(search_opts=search_opts,
- marker=args.marker,
- limit=args.limit,
- sort=args.sort)
- _translate_volume_snapshot_keys(backups)
- columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count',
- 'Container']
- if args.sort:
- sortby_index = None
- else:
- sortby_index = 0
- utils.print_list(backups, columns, sortby_index=sortby_index)
-
-
-@utils.arg('backup', metavar='<backup>', nargs='+',
- help='Name or ID of backup(s) to delete.')
-@utils.service_type('volumev2')
-def do_backup_delete(cs, args):
- """Removes one or more backups."""
- failure_count = 0
- for backup in args.backup:
- try:
- _find_backup(cs, backup).delete()
- print("Request to delete backup %s has been accepted." % (backup))
- except Exception as e:
- failure_count += 1
- print("Delete for backup %s failed: %s" % (backup, e))
- if failure_count == len(args.backup):
- raise exceptions.CommandError("Unable to delete any of the specified "
- "backups.")
-
-
-@utils.arg('backup', metavar='<backup>',
- help='ID of backup to restore.')
-@utils.arg('--volume-id', metavar='<volume>',
- default=None,
- help=argparse.SUPPRESS)
-@utils.arg('--volume', metavar='<volume>',
- default=None,
- help='Name or ID of volume to which to restore. '
- 'Default=None.')
-@utils.service_type('volumev2')
-def do_backup_restore(cs, args):
- """Restores a backup."""
- vol = args.volume or args.volume_id
- if vol:
- volume_id = utils.find_volume(cs, vol).id
- else:
- volume_id = None
-
- restore = cs.restores.restore(args.backup, volume_id)
-
- info = {"backup_id": args.backup}
- info.update(restore._info)
-
- info.pop('links', None)
-
- utils.print_dict(info)
-
-
-@utils.arg('backup', metavar='<backup>',
- help='ID of the backup to export.')
-@utils.service_type('volumev2')
-def do_backup_export(cs, args):
- """Export backup metadata record."""
- info = cs.backups.export_record(args.backup)
- utils.print_dict(info)
-
-
-@utils.arg('backup_service', metavar='<backup_service>',
- help='Backup service to use for importing the backup.')
-@utils.arg('backup_url', metavar='<backup_url>',
- help='Backup URL for importing the backup metadata.')
-@utils.service_type('volumev2')
-def do_backup_import(cs, args):
- """Import backup metadata record."""
- info = cs.backups.import_record(args.backup_service, args.backup_url)
- info.pop('links', None)
-
- utils.print_dict(info)
-
-
-@utils.arg('backup', metavar='<backup>', nargs='+',
- help='Name or ID of the backup to modify.')
-@utils.arg('--state', metavar='<state>',
- default='available',
- help='The state to assign to the backup. Valid values are '
- '"available", "error". Default=available.')
-@utils.service_type('volumev2')
-def do_backup_reset_state(cs, args):
- """Explicitly updates the backup state."""
- failure_count = 0
-
- single = (len(args.backup) == 1)
-
- for backup in args.backup:
- try:
- _find_backup(cs, backup).reset_state(args.state)
- except Exception as e:
- failure_count += 1
- msg = "Reset state for backup %s failed: %s" % (backup, e)
- if not single:
- print(msg)
-
- if failure_count == len(args.backup):
- if not single:
- msg = ("Unable to reset the state for any of the specified "
- "backups.")
- raise exceptions.CommandError(msg)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of volume to transfer.')
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Transfer name. Default=None.')
-@utils.arg('--display-name',
- help=argparse.SUPPRESS)
-@utils.service_type('volumev2')
-def do_transfer_create(cs, args):
- """Creates a volume transfer."""
- if args.display_name is not None:
- args.name = args.display_name
-
- volume = utils.find_volume(cs, args.volume)
- transfer = cs.transfers.create(volume.id,
- args.name)
- info = dict()
- info.update(transfer._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('transfer', metavar='<transfer>',
- help='Name or ID of transfer to delete.')
-@utils.service_type('volumev2')
-def do_transfer_delete(cs, args):
- """Undoes a transfer."""
- transfer = _find_transfer(cs, args.transfer)
- transfer.delete()
-
-
-@utils.arg('transfer', metavar='<transfer>',
- help='ID of transfer to accept.')
-@utils.arg('auth_key', metavar='<auth_key>',
- help='Authentication key of transfer to accept.')
-@utils.service_type('volumev2')
-def do_transfer_accept(cs, args):
- """Accepts a volume transfer."""
- transfer = cs.transfers.accept(args.transfer, args.auth_key)
- info = dict()
- info.update(transfer._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('--all-tenants',
- dest='all_tenants',
- metavar='<0|1>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.arg('--all_tenants',
- nargs='?',
- type=int,
- const=1,
- help=argparse.SUPPRESS)
-@utils.service_type('volumev2')
-def do_transfer_list(cs, args):
- """Lists all transfers."""
- all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
- search_opts = {
- 'all_tenants': all_tenants,
- }
- transfers = cs.transfers.list(search_opts=search_opts)
- columns = ['ID', 'Volume ID', 'Name']
- utils.print_list(transfers, columns)
-
-
-@utils.arg('transfer', metavar='<transfer>',
- help='Name or ID of transfer to accept.')
-@utils.service_type('volumev2')
-def do_transfer_show(cs, args):
- """Shows transfer details."""
- transfer = _find_transfer(cs, args.transfer)
- info = dict()
- info.update(transfer._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of volume to extend.')
-@utils.arg('new_size',
- metavar='<new_size>',
- type=int,
- help='New size of volume, in GiBs.')
-@utils.service_type('volumev2')
-def do_extend(cs, args):
- """Attempts to extend size of an existing volume."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.extend(volume, args.new_size)
-
-
-@utils.arg('--host', metavar='<hostname>', default=None,
- help='Host name. Default=None.')
-@utils.arg('--binary', metavar='<binary>', default=None,
- help='Service binary. Default=None.')
-@utils.arg('--withreplication',
- metavar='<True|False>',
- const=True,
- nargs='?',
- default=False,
- help='Enables or disables display of '
- 'Replication info for c-vol services. Default=False.')
-@utils.service_type('volumev2')
-def do_service_list(cs, args):
- """Lists all services. Filter by host and service binary."""
- replication = strutils.bool_from_string(args.withreplication)
- result = cs.services.list(host=args.host, binary=args.binary)
- columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"]
- if replication:
- columns.extend(["Replication Status", "Active Backend ID", "Frozen"])
- # NOTE(jay-lau-513): we check if the response has disabled_reason
- # so as not to add the column when the extended ext is not enabled.
- if result and hasattr(result[0], 'disabled_reason'):
- columns.append("Disabled Reason")
- utils.print_list(result, columns)
-
-
-@utils.arg('host', metavar='<hostname>', help='Host name.')
-@utils.arg('binary', metavar='<binary>', help='Service binary.')
-@utils.service_type('volumev2')
-def do_service_enable(cs, args):
- """Enables the service."""
- result = cs.services.enable(args.host, args.binary)
- columns = ["Host", "Binary", "Status"]
- utils.print_list([result], columns)
-
-
-@utils.arg('host', metavar='<hostname>', help='Host name.')
-@utils.arg('binary', metavar='<binary>', help='Service binary.')
-@utils.arg('--reason', metavar='<reason>',
- help='Reason for disabling service.')
-@utils.service_type('volumev2')
-def do_service_disable(cs, args):
- """Disables the service."""
- columns = ["Host", "Binary", "Status"]
- if args.reason:
- columns.append('Disabled Reason')
- result = cs.services.disable_log_reason(args.host, args.binary,
- args.reason)
- else:
- result = cs.services.disable(args.host, args.binary)
- utils.print_list([result], columns)
-
+# Below is shameless hack for unit tests
+# TODO remove after deciding if unit tests are moved to /v3/ dir
def _treeizeAvailabilityZone(zone):
"""Builds a tree view for availability zones."""
@@ -1749,906 +64,3 @@ def _treeizeAvailabilityZone(zone):
return result
-@utils.service_type('volumev2')
-def do_availability_zone_list(cs, _args):
- """Lists all availability zones."""
- try:
- availability_zones = cs.availability_zones.list()
- except exceptions.Forbidden as e: # policy doesn't allow probably
- try:
- availability_zones = cs.availability_zones.list(detailed=False)
- except Exception:
- raise e
-
- result = []
- for zone in availability_zones:
- result += _treeizeAvailabilityZone(zone)
- _translate_availability_zone_keys(result)
- utils.print_list(result, ['Name', 'Status'])
-
-
-def _print_volume_encryption_type_list(encryption_types):
- """
- Lists volume encryption types.
-
- :param encryption_types: a list of :class: VolumeEncryptionType instances
- """
- utils.print_list(encryption_types, ['Volume Type ID', 'Provider',
- 'Cipher', 'Key Size',
- 'Control Location'])
-
-
-@utils.service_type('volumev2')
-def do_encryption_type_list(cs, args):
- """Shows encryption type details for volume types. Admin only."""
- result = cs.volume_encryption_types.list()
- utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher',
- 'Key Size', 'Control Location'])
-
-
-@utils.arg('volume_type',
- metavar='<volume_type>',
- type=str,
- help='Name or ID of volume type.')
-@utils.service_type('volumev2')
-def do_encryption_type_show(cs, args):
- """Shows encryption type details for a volume type. Admin only."""
- volume_type = _find_volume_type(cs, args.volume_type)
-
- result = cs.volume_encryption_types.get(volume_type)
-
- # Display result or an empty table if no result
- if hasattr(result, 'volume_type_id'):
- _print_volume_encryption_type_list([result])
- else:
- _print_volume_encryption_type_list([])
-
-
-@utils.arg('volume_type',
- metavar='<volume_type>',
- type=str,
- help='Name or ID of volume type.')
-@utils.arg('provider',
- metavar='<provider>',
- type=str,
- help='The class that provides encryption support. '
- 'For example, LuksEncryptor.')
-@utils.arg('--cipher',
- metavar='<cipher>',
- type=str,
- required=False,
- default=None,
- help='The encryption algorithm or mode. '
- 'For example, aes-xts-plain64. Default=None.')
-@utils.arg('--key_size',
- metavar='<key_size>',
- type=int,
- required=False,
- default=None,
- help='Size of encryption key, in bits. '
- 'For example, 128 or 256. Default=None.')
-@utils.arg('--control_location',
- metavar='<control_location>',
- choices=['front-end', 'back-end'],
- type=str,
- required=False,
- default='front-end',
- help='Notional service where encryption is performed. '
- 'Valid values are "front-end" or "back-end." '
- 'For example, front-end=Nova. Default is "front-end."')
-@utils.service_type('volumev2')
-def do_encryption_type_create(cs, args):
- """Creates encryption type for a volume type. Admin only."""
- volume_type = _find_volume_type(cs, args.volume_type)
-
- body = {
- 'provider': args.provider,
- 'cipher': args.cipher,
- 'key_size': args.key_size,
- 'control_location': args.control_location
- }
-
- result = cs.volume_encryption_types.create(volume_type, body)
- _print_volume_encryption_type_list([result])
-
-
-@utils.arg('volume_type',
- metavar='<volume-type>',
- type=str,
- help="Name or ID of the volume type")
-@utils.arg('--provider',
- metavar='<provider>',
- type=str,
- required=False,
- default=argparse.SUPPRESS,
- help="Class providing encryption support (e.g. LuksEncryptor) "
- "(Optional)")
-@utils.arg('--cipher',
- metavar='<cipher>',
- type=str,
- nargs='?',
- required=False,
- default=argparse.SUPPRESS,
- const=None,
- help="Encryption algorithm/mode to use (e.g., aes-xts-plain64). "
- "Provide parameter without value to set to provider default. "
- "(Optional)")
-@utils.arg('--key-size',
- dest='key_size',
- metavar='<key-size>',
- type=int,
- nargs='?',
- required=False,
- default=argparse.SUPPRESS,
- const=None,
- help="Size of the encryption key, in bits (e.g., 128, 256). "
- "Provide parameter without value to set to provider default. "
- "(Optional)")
-@utils.arg('--control-location',
- dest='control_location',
- metavar='<control-location>',
- choices=['front-end', 'back-end'],
- type=str,
- required=False,
- default=argparse.SUPPRESS,
- help="Notional service where encryption is performed (e.g., "
- "front-end=Nova). Values: 'front-end', 'back-end' (Optional)")
-@utils.service_type('volumev2')
-def do_encryption_type_update(cs, args):
- """Update encryption type information for a volume type (Admin Only)."""
- volume_type = _find_volume_type(cs, args.volume_type)
-
- # An argument should only be pulled if the user specified the parameter.
- body = {}
- for attr in ['provider', 'cipher', 'key_size', 'control_location']:
- if hasattr(args, attr):
- body[attr] = getattr(args, attr)
-
- cs.volume_encryption_types.update(volume_type, body)
- result = cs.volume_encryption_types.get(volume_type)
- _print_volume_encryption_type_list([result])
-
-
-@utils.arg('volume_type',
- metavar='<volume_type>',
- type=str,
- help='Name or ID of volume type.')
-@utils.service_type('volumev2')
-def do_encryption_type_delete(cs, args):
- """Deletes encryption type for a volume type. Admin only."""
- volume_type = _find_volume_type(cs, args.volume_type)
- cs.volume_encryption_types.delete(volume_type)
-
-
-def _print_qos_specs(qos_specs):
-
- # formatters defines field to be converted from unicode to string
- utils.print_dict(qos_specs._info, formatters=['specs'])
-
-
-def _print_qos_specs_list(q_specs):
- utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
-
-
-def _print_qos_specs_and_associations_list(q_specs):
- utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
-
-
-def _print_associations_list(associations):
- utils.print_list(associations, ['Association_Type', 'Name', 'ID'])
-
-
-@utils.arg('name',
- metavar='<name>',
- help='Name of new QoS specifications.')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='QoS specifications.')
-@utils.service_type('volumev2')
-def do_qos_create(cs, args):
- """Creates a qos specs."""
- keypair = None
- if args.metadata is not None:
- keypair = _extract_metadata(args)
- qos_specs = cs.qos_specs.create(args.name, keypair)
- _print_qos_specs(qos_specs)
-
-
-@utils.service_type('volumev2')
-def do_qos_list(cs, args):
- """Lists qos specs."""
- qos_specs = cs.qos_specs.list()
- _print_qos_specs_list(qos_specs)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications to show.')
-@utils.service_type('volumev2')
-def do_qos_show(cs, args):
- """Shows qos specs details."""
- qos_specs = _find_qos_specs(cs, args.qos_specs)
- _print_qos_specs(qos_specs)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications to delete.')
-@utils.arg('--force',
- metavar='<True|False>',
- const=True,
- nargs='?',
- default=False,
- help='Enables or disables deletion of in-use '
- 'QoS specifications. Default=False.')
-@utils.service_type('volumev2')
-def do_qos_delete(cs, args):
- """Deletes a specified qos specs."""
- force = strutils.bool_from_string(args.force)
- qos_specs = _find_qos_specs(cs, args.qos_specs)
- cs.qos_specs.delete(qos_specs, force)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications.')
-@utils.arg('vol_type_id', metavar='<volume_type_id>',
- help='ID of volume type with which to associate '
- 'QoS specifications.')
-@utils.service_type('volumev2')
-def do_qos_associate(cs, args):
- """Associates qos specs with specified volume type."""
- cs.qos_specs.associate(args.qos_specs, args.vol_type_id)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications.')
-@utils.arg('vol_type_id', metavar='<volume_type_id>',
- help='ID of volume type with which to associate '
- 'QoS specifications.')
-@utils.service_type('volumev2')
-def do_qos_disassociate(cs, args):
- """Disassociates qos specs from specified volume type."""
- cs.qos_specs.disassociate(args.qos_specs, args.vol_type_id)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications on which to operate.')
-@utils.service_type('volumev2')
-def do_qos_disassociate_all(cs, args):
- """Disassociates qos specs from all its associations."""
- cs.qos_specs.disassociate_all(args.qos_specs)
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications.')
-@utils.arg('action',
- metavar='<action>',
- choices=['set', 'unset'],
- help='The action. Valid values are "set" or "unset."')
-@utils.arg('metadata', metavar='key=value',
- nargs='+',
- default=[],
- help='Metadata key and value pair to set or unset. '
- 'For unset, specify only the key.')
-@utils.service_type('volumev2')
-def do_qos_key(cs, args):
- """Sets or unsets specifications for a qos spec."""
- keypair = _extract_metadata(args)
-
- if args.action == 'set':
- cs.qos_specs.set_keys(args.qos_specs, keypair)
- elif args.action == 'unset':
- cs.qos_specs.unset_keys(args.qos_specs, list(keypair))
-
-
-@utils.arg('qos_specs', metavar='<qos_specs>',
- help='ID of QoS specifications.')
-@utils.service_type('volumev2')
-def do_qos_get_association(cs, args):
- """Lists all associations for specified qos specs."""
- associations = cs.qos_specs.get_associations(args.qos_specs)
- _print_associations_list(associations)
-
-
-@utils.arg('snapshot',
- metavar='<snapshot>',
- help='ID of snapshot for which to update metadata.')
-@utils.arg('action',
- metavar='<action>',
- choices=['set', 'unset'],
- help='The action. Valid values are "set" or "unset."')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='Metadata key and value pair to set or unset. '
- 'For unset, specify only the key.')
-@utils.service_type('volumev2')
-def do_snapshot_metadata(cs, args):
- """Sets or deletes snapshot metadata."""
- snapshot = _find_volume_snapshot(cs, args.snapshot)
- metadata = _extract_metadata(args)
-
- if args.action == 'set':
- metadata = snapshot.set_metadata(metadata)
- utils.print_dict(metadata._info)
- elif args.action == 'unset':
- snapshot.delete_metadata(list(metadata.keys()))
-
-
-@utils.arg('snapshot', metavar='<snapshot>',
- help='ID of snapshot.')
-@utils.service_type('volumev2')
-def do_snapshot_metadata_show(cs, args):
- """Shows snapshot metadata."""
- snapshot = _find_volume_snapshot(cs, args.snapshot)
- utils.print_dict(snapshot._info['metadata'], 'Metadata-property')
-
-
-@utils.arg('volume', metavar='<volume>',
- help='ID of volume.')
-@utils.service_type('volumev2')
-def do_metadata_show(cs, args):
- """Shows volume metadata."""
- volume = utils.find_volume(cs, args.volume)
- utils.print_dict(volume._info['metadata'], 'Metadata-property')
-
-
-@utils.arg('volume', metavar='<volume>',
- help='ID of volume.')
-@utils.service_type('volumev2')
-def do_image_metadata_show(cs, args):
- """Shows volume image metadata."""
- volume = utils.find_volume(cs, args.volume)
- resp, body = volume.show_image_metadata(volume)
- utils.print_dict(body['metadata'], 'Metadata-property')
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='ID of volume for which to update metadata.')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='Metadata key and value pair or pairs to update.')
-@utils.service_type('volumev2')
-def do_metadata_update_all(cs, args):
- """Updates volume metadata."""
- volume = utils.find_volume(cs, args.volume)
- metadata = _extract_metadata(args)
- metadata = volume.update_all_metadata(metadata)
- utils.print_dict(metadata['metadata'], 'Metadata-property')
-
-
-@utils.arg('snapshot',
- metavar='<snapshot>',
- help='ID of snapshot for which to update metadata.')
-@utils.arg('metadata',
- metavar='<key=value>',
- nargs='+',
- default=[],
- help='Metadata key and value pair to update.')
-@utils.service_type('volumev2')
-def do_snapshot_metadata_update_all(cs, args):
- """Updates snapshot metadata."""
- snapshot = _find_volume_snapshot(cs, args.snapshot)
- metadata = _extract_metadata(args)
- metadata = snapshot.update_all_metadata(metadata)
- utils.print_dict(metadata)
-
-
-@utils.arg('volume', metavar='<volume>', help='ID of volume to update.')
-@utils.arg('read_only',
- metavar='<True|true|False|false>',
- choices=['True', 'true', 'False', 'false'],
- help='Enables or disables update of volume to '
- 'read-only access mode.')
-@utils.service_type('volumev2')
-def do_readonly_mode_update(cs, args):
- """Updates volume read-only access-mode flag."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.update_readonly_flag(volume,
- strutils.bool_from_string(args.read_only))
-
-
-@utils.arg('volume', metavar='<volume>', help='ID of the volume to update.')
-@utils.arg('bootable',
- metavar='<True|true|False|false>',
- choices=['True', 'true', 'False', 'false'],
- help='Flag to indicate whether volume is bootable.')
-@utils.service_type('volumev2')
-def do_set_bootable(cs, args):
- """Update bootable status of a volume."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.set_bootable(volume,
- strutils.bool_from_string(args.bootable))
-
-
-@utils.arg('host',
- metavar='<host>',
- help='Cinder host on which the existing volume resides; '
- 'takes the form: host@backend-name#pool')
-@utils.arg('identifier',
- metavar='<identifier>',
- help='Name or other Identifier for existing volume')
-@utils.arg('--id-type',
- metavar='<id-type>',
- default='source-name',
- help='Type of backend device identifier provided, '
- 'typically source-name or source-id (Default=source-name)')
-@utils.arg('--name',
- metavar='<name>',
- help='Volume name (Default=None)')
-@utils.arg('--description',
- metavar='<description>',
- help='Volume description (Default=None)')
-@utils.arg('--volume-type',
- metavar='<volume-type>',
- help='Volume type (Default=None)')
-@utils.arg('--availability-zone',
- metavar='<availability-zone>',
- help='Availability zone for volume (Default=None)')
-@utils.arg('--metadata',
- type=str,
- nargs='*',
- metavar='<key=value>',
- help='Metadata key=value pairs (Default=None)')
-@utils.arg('--bootable',
- action='store_true',
- help='Specifies that the newly created volume should be'
- ' marked as bootable')
-@utils.service_type('volumev2')
-def do_manage(cs, args):
- """Manage an existing volume."""
- volume_metadata = None
- if args.metadata is not None:
- volume_metadata = _extract_metadata(args)
-
- # Build a dictionary of key/value pairs to pass to the API.
- ref_dict = {args.id_type: args.identifier}
-
- # The recommended way to specify an existing volume is by ID or name, and
- # have the Cinder driver look for 'source-name' or 'source-id' elements in
- # the ref structure. To make things easier for the user, we have special
- # --source-name and --source-id CLI options that add the appropriate
- # element to the ref structure.
- #
- # Note how argparse converts hyphens to underscores. We use hyphens in the
- # dictionary so that it is consistent with what the user specified on the
- # CLI.
-
- if hasattr(args, 'source_name') and args.source_name is not None:
- ref_dict['source-name'] = args.source_name
- if hasattr(args, 'source_id') and args.source_id is not None:
- ref_dict['source-id'] = args.source_id
-
- volume = cs.volumes.manage(host=args.host,
- ref=ref_dict,
- name=args.name,
- description=args.description,
- volume_type=args.volume_type,
- availability_zone=args.availability_zone,
- metadata=volume_metadata,
- bootable=args.bootable)
-
- info = {}
- volume = cs.volumes.get(volume.id)
- info.update(volume._info)
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of the volume to unmanage.')
-@utils.service_type('volumev2')
-def do_unmanage(cs, args):
- """Stop managing a volume."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.unmanage(volume.id)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of the volume to promote. '
- 'The volume should have the replica volume created with '
- 'source-replica argument.')
-@utils.service_type('volumev2')
-def do_replication_promote(cs, args):
- """Promote a secondary volume to primary for a relationship."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.promote(volume.id)
-
-
-@utils.arg('volume', metavar='<volume>',
- help='Name or ID of the volume to reenable replication. '
- 'The replication-status of the volume should be inactive.')
-@utils.service_type('volumev2')
-def do_replication_reenable(cs, args):
- """Sync the secondary volume with primary for a relationship."""
- volume = utils.find_volume(cs, args.volume)
- cs.volumes.reenable(volume.id)
-
-
-@utils.arg('--all-tenants',
- dest='all_tenants',
- metavar='<0|1>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.service_type('volumev2')
-def do_consisgroup_list(cs, args):
- """Lists all consistencygroups."""
- consistencygroups = cs.consistencygroups.list()
-
- columns = ['ID', 'Status', 'Name']
- utils.print_list(consistencygroups, columns)
-
-
-@utils.arg('consistencygroup',
- metavar='<consistencygroup>',
- help='Name or ID of a consistency group.')
-@utils.service_type('volumev2')
-def do_consisgroup_show(cs, args):
- """Shows details of a consistency group."""
- info = dict()
- consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
- info.update(consistencygroup._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('volumetypes',
- metavar='<volume-types>',
- help='Volume types.')
-@utils.arg('--name',
- metavar='<name>',
- help='Name of a consistency group.')
-@utils.arg('--description',
- metavar='<description>',
- default=None,
- help='Description of a consistency group. Default=None.')
-@utils.arg('--availability-zone',
- metavar='<availability-zone>',
- default=None,
- help='Availability zone for volume. Default=None.')
-@utils.service_type('volumev2')
-def do_consisgroup_create(cs, args):
- """Creates a consistency group."""
-
- consistencygroup = cs.consistencygroups.create(
- args.volumetypes,
- args.name,
- args.description,
- availability_zone=args.availability_zone)
-
- info = dict()
- consistencygroup = cs.consistencygroups.get(consistencygroup.id)
- info.update(consistencygroup._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('--cgsnapshot',
- metavar='<cgsnapshot>',
- help='Name or ID of a cgsnapshot. Default=None.')
-@utils.arg('--source-cg',
- metavar='<source-cg>',
- help='Name or ID of a source CG. Default=None.')
-@utils.arg('--name',
- metavar='<name>',
- help='Name of a consistency group. Default=None.')
-@utils.arg('--description',
- metavar='<description>',
- help='Description of a consistency group. Default=None.')
-@utils.service_type('volumev2')
-def do_consisgroup_create_from_src(cs, args):
- """Creates a consistency group from a cgsnapshot or a source CG."""
- if not args.cgsnapshot and not args.source_cg:
- msg = ('Cannot create consistency group because neither '
- 'cgsnapshot nor source CG is provided.')
- raise exceptions.ClientException(code=1, message=msg)
- if args.cgsnapshot and args.source_cg:
- msg = ('Cannot create consistency group because both '
- 'cgsnapshot and source CG are provided.')
- raise exceptions.ClientException(code=1, message=msg)
- cgsnapshot = None
- if args.cgsnapshot:
- cgsnapshot = _find_cgsnapshot(cs, args.cgsnapshot)
- source_cg = None
- if args.source_cg:
- source_cg = _find_consistencygroup(cs, args.source_cg)
- info = cs.consistencygroups.create_from_src(
- cgsnapshot.id if cgsnapshot else None,
- source_cg.id if source_cg else None,
- args.name,
- args.description)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('consistencygroup',
- metavar='<consistencygroup>', nargs='+',
- help='Name or ID of one or more consistency groups '
- 'to be deleted.')
-@utils.arg('--force',
- action='store_true',
- default=False,
- help='Allows or disallows consistency groups '
- 'to be deleted. If the consistency group is empty, '
- 'it can be deleted without the force flag. '
- 'If the consistency group is not empty, the force '
- 'flag is required for it to be deleted.')
-@utils.service_type('volumev2')
-def do_consisgroup_delete(cs, args):
- """Removes one or more consistency groups."""
- failure_count = 0
- for consistencygroup in args.consistencygroup:
- try:
- _find_consistencygroup(cs, consistencygroup).delete(args.force)
- except Exception as e:
- failure_count += 1
- print("Delete for consistency group %s failed: %s" %
- (consistencygroup, e))
- if failure_count == len(args.consistencygroup):
- raise exceptions.CommandError("Unable to delete any of the specified "
- "consistency groups.")
-
-
-@utils.arg('consistencygroup',
- metavar='<consistencygroup>',
- help='Name or ID of a consistency group.')
-@utils.arg('--name', metavar='<name>',
- help='New name for consistency group. Default=None.')
-@utils.arg('--description', metavar='<description>',
- help='New description for consistency group. Default=None.')
-@utils.arg('--add-volumes',
- metavar='<uuid1,uuid2,......>',
- help='UUID of one or more volumes '
- 'to be added to the consistency group, '
- 'separated by commas. Default=None.')
-@utils.arg('--remove-volumes',
- metavar='<uuid3,uuid4,......>',
- help='UUID of one or more volumes '
- 'to be removed from the consistency group, '
- 'separated by commas. Default=None.')
-@utils.service_type('volumev2')
-def do_consisgroup_update(cs, args):
- """Updates a consistencygroup."""
- kwargs = {}
-
- if args.name is not None:
- kwargs['name'] = args.name
-
- if args.description is not None:
- kwargs['description'] = args.description
-
- if args.add_volumes is not None:
- kwargs['add_volumes'] = args.add_volumes
-
- if args.remove_volumes is not None:
- kwargs['remove_volumes'] = args.remove_volumes
-
- if not kwargs:
- msg = ('At least one of the following args must be supplied: '
- 'name, description, add-volumes, remove-volumes.')
- raise exceptions.ClientException(code=1, message=msg)
-
- _find_consistencygroup(cs, args.consistencygroup).update(**kwargs)
-
-
-@utils.arg('--all-tenants',
- dest='all_tenants',
- metavar='<0|1>',
- nargs='?',
- type=int,
- const=1,
- default=0,
- help='Shows details for all tenants. Admin only.')
-@utils.arg('--status',
- metavar='<status>',
- default=None,
- help='Filters results by a status. Default=None.')
-@utils.arg('--consistencygroup-id',
- metavar='<consistencygroup_id>',
- default=None,
- help='Filters results by a consistency group ID. Default=None.')
-@utils.service_type('volumev2')
-def do_cgsnapshot_list(cs, args):
- """Lists all cgsnapshots."""
-
- all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
-
- search_opts = {
- 'all_tenants': all_tenants,
- 'status': args.status,
- 'consistencygroup_id': args.consistencygroup_id,
- }
-
- cgsnapshots = cs.cgsnapshots.list(search_opts=search_opts)
-
- columns = ['ID', 'Status', 'Name']
- utils.print_list(cgsnapshots, columns)
-
-
-@utils.arg('cgsnapshot',
- metavar='<cgsnapshot>',
- help='Name or ID of cgsnapshot.')
-@utils.service_type('volumev2')
-def do_cgsnapshot_show(cs, args):
- """Shows cgsnapshot details."""
- info = dict()
- cgsnapshot = _find_cgsnapshot(cs, args.cgsnapshot)
- info.update(cgsnapshot._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('consistencygroup',
- metavar='<consistencygroup>',
- help='Name or ID of a consistency group.')
-@utils.arg('--name',
- metavar='<name>',
- default=None,
- help='Cgsnapshot name. Default=None.')
-@utils.arg('--description',
- metavar='<description>',
- default=None,
- help='Cgsnapshot description. Default=None.')
-@utils.service_type('volumev2')
-def do_cgsnapshot_create(cs, args):
- """Creates a cgsnapshot."""
- consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
- cgsnapshot = cs.cgsnapshots.create(
- consistencygroup.id,
- args.name,
- args.description)
-
- info = dict()
- cgsnapshot = cs.cgsnapshots.get(cgsnapshot.id)
- info.update(cgsnapshot._info)
-
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('cgsnapshot',
- metavar='<cgsnapshot>', nargs='+',
- help='Name or ID of one or more cgsnapshots to be deleted.')
-@utils.service_type('volumev2')
-def do_cgsnapshot_delete(cs, args):
- """Removes one or more cgsnapshots."""
- failure_count = 0
- for cgsnapshot in args.cgsnapshot:
- try:
- _find_cgsnapshot(cs, cgsnapshot).delete()
- except Exception as e:
- failure_count += 1
- print("Delete for cgsnapshot %s failed: %s" % (cgsnapshot, e))
- if failure_count == len(args.cgsnapshot):
- raise exceptions.CommandError("Unable to delete any of the specified "
- "cgsnapshots.")
-
-
-@utils.arg('--detail',
- action='store_true',
- help='Show detailed information about pools.')
-@utils.service_type('volumev2')
-def do_get_pools(cs, args):
- """Show pool information for backends. Admin only."""
- pools = cs.volumes.get_pools(args.detail)
- infos = dict()
- infos.update(pools._info)
-
- for info in infos['pools']:
- backend = dict()
- backend['name'] = info['name']
- if args.detail:
- backend.update(info['capabilities'])
- utils.print_dict(backend)
-
-
-@utils.arg('host',
- metavar='<host>',
- help='Cinder host to show backend volume stats and properties; '
- 'takes the form: host@backend-name')
-@utils.service_type('volumev2')
-def do_get_capabilities(cs, args):
- """Show backend volume stats and properties. Admin only."""
-
- capabilities = cs.capabilities.get(args.host)
- infos = dict()
- infos.update(capabilities._info)
-
- prop = infos.pop('properties', None)
- utils.print_dict(infos, "Volume stats")
- utils.print_dict(prop, "Backend properties")
-
-
-@utils.arg('volume',
- metavar='<volume>',
- help='Cinder volume already exists in volume backend')
-@utils.arg('identifier',
- metavar='<identifier>',
- help='Name or other Identifier for existing snapshot')
-@utils.arg('--id-type',
- metavar='<id-type>',
- default='source-name',
- help='Type of backend device identifier provided, '
- 'typically source-name or source-id (Default=source-name)')
-@utils.arg('--name',
- metavar='<name>',
- help='Snapshot name (Default=None)')
-@utils.arg('--description',
- metavar='<description>',
- help='Snapshot description (Default=None)')
-@utils.arg('--metadata',
- type=str,
- nargs='*',
- metavar='<key=value>',
- help='Metadata key=value pairs (Default=None)')
-@utils.service_type('volumev2')
-def do_snapshot_manage(cs, args):
- """Manage an existing snapshot."""
- snapshot_metadata = None
- if args.metadata is not None:
- snapshot_metadata = _extract_metadata(args)
-
- # Build a dictionary of key/value pairs to pass to the API.
- ref_dict = {args.id_type: args.identifier}
-
- if hasattr(args, 'source_name') and args.source_name is not None:
- ref_dict['source-name'] = args.source_name
- if hasattr(args, 'source_id') and args.source_id is not None:
- ref_dict['source-id'] = args.source_id
-
- volume = utils.find_volume(cs, args.volume)
- snapshot = cs.volume_snapshots.manage(volume_id=volume.id,
- ref=ref_dict,
- name=args.name,
- description=args.description,
- metadata=snapshot_metadata)
-
- info = {}
- snapshot = cs.volume_snapshots.get(snapshot.id)
- info.update(snapshot._info)
- info.pop('links', None)
- utils.print_dict(info)
-
-
-@utils.arg('snapshot', metavar='<snapshot>',
- help='Name or ID of the snapshot to unmanage.')
-@utils.service_type('volumev2')
-def do_snapshot_unmanage(cs, args):
- """Stop managing a snapshot."""
- snapshot = _find_volume_snapshot(cs, args.snapshot)
- cs.volume_snapshots.unmanage(snapshot.id)
-
-
-@utils.arg('host', metavar='<hostname>', help='Host name.')
-@utils.service_type('volumev2')
-def do_freeze_host(cs, args):
- """Freeze and disable the specified cinder-volume host."""
- cs.services.freeze_host(args.host)
-
-
-@utils.arg('host', metavar='<hostname>', help='Host name.')
-@utils.service_type('volumev2')
-def do_thaw_host(cs, args):
- """Thaw and enable the specified cinder-volume host."""
- cs.services.thaw_host(args.host)
-
-
-@utils.arg('host', metavar='<hostname>', help='Host name.')
-@utils.arg('--backend_id',
- metavar='<backend-id>',
- help='ID of backend to failover to (Default=None)')
-@utils.service_type('volumev2')
-def do_failover_host(cs, args):
- """Failover a replicating cinder-volume host."""
- cs.services.failover_host(args.host, args.backend_id)
diff --git a/cinderclient/v2/volume_backups.py b/cinderclient/v2/volume_backups.py
index 7039e60..bf716d9 100644
--- a/cinderclient/v2/volume_backups.py
+++ b/cinderclient/v2/volume_backups.py
@@ -14,111 +14,8 @@
# under the License.
"""
-Volume Backups interface (1.1 extension).
+Volume Backups interface (v2 extension).
"""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
+from cinderclient.v3.volume_backups import * # flake8: noqa
-class VolumeBackup(base.Resource):
- """A volume backup is a block level backup of a volume."""
-
- def __repr__(self):
- return "<VolumeBackup: %s>" % self.id
-
- def delete(self):
- """Delete this volume backup."""
- return self.manager.delete(self)
-
- def reset_state(self, state):
- return self.manager.reset_state(self, state)
-
-
-class VolumeBackupManager(base.ManagerWithFind):
- """Manage :class:`VolumeBackup` resources."""
- resource_class = VolumeBackup
-
- def create(self, volume_id, container=None,
- name=None, description=None,
- incremental=False, force=False,
- snapshot_id=None):
- """Creates a volume backup.
-
- :param volume_id: The ID of the volume to backup.
- :param container: The name of the backup service container.
- :param name: The name of the backup.
- :param description: The description of the backup.
- :param incremental: Incremental backup.
- :param force: If True, allows an in-use volume to be backed up.
- :rtype: :class:`VolumeBackup`
- """
- body = {'backup': {'volume_id': volume_id,
- 'container': container,
- 'name': name,
- 'description': description,
- 'incremental': incremental,
- 'force': force,
- 'snapshot_id': snapshot_id, }}
- return self._create('/backups', body, 'backup')
-
- def get(self, backup_id):
- """Show volume backup details.
-
- :param backup_id: The ID of the backup to display.
- :rtype: :class:`VolumeBackup`
- """
- return self._get("/backups/%s" % backup_id, "backup")
-
- def list(self, detailed=True, search_opts=None, marker=None, limit=None,
- sort=None):
- """Get a list of all volume backups.
-
- :rtype: list of :class:`VolumeBackup`
- """
- resource_type = "backups"
- url = self._build_list_url(resource_type, detailed=detailed,
- search_opts=search_opts, marker=marker,
- limit=limit, sort=sort)
- return self._list(url, resource_type, limit=limit)
-
- def delete(self, backup):
- """Delete a volume backup.
-
- :param backup: The :class:`VolumeBackup` to delete.
- """
- return self._delete("/backups/%s" % base.getid(backup))
-
- def reset_state(self, backup, state):
- """Update the specified volume backup with the provided state."""
- return self._action('os-reset_status', backup, {'status': state})
-
- def _action(self, action, backup, info=None, **kwargs):
- """Perform a volume backup action."""
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/backups/%s/action' % base.getid(backup)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
-
- def export_record(self, backup_id):
- """Export volume backup metadata record.
-
- :param backup_id: The ID of the backup to export.
- :rtype: A dictionary containing 'backup_url' and 'backup_service'.
- """
- resp, body = \
- self.api.client.get("/backups/%s/export_record" % backup_id)
- return common_base.DictWithMeta(body['backup-record'], resp)
-
- def import_record(self, backup_service, backup_url):
- """Import volume backup metadata record.
-
- :param backup_service: Backup service to use for importing the backup
- :param backup_url: Backup URL for importing the backup metadata
- :rtype: A dictionary containing volume backup metadata.
- """
- body = {'backup-record': {'backup_service': backup_service,
- 'backup_url': backup_url}}
- self.run_hooks('modify_body_for_update', body, 'backup-record')
- resp, body = self.api.client.post("/backups/import_record", body=body)
- return common_base.DictWithMeta(body['backup'], resp)
diff --git a/cinderclient/v2/volume_backups_restore.py b/cinderclient/v2/volume_backups_restore.py
index 0eafa82..700ca6c 100644
--- a/cinderclient/v2/volume_backups_restore.py
+++ b/cinderclient/v2/volume_backups_restore.py
@@ -13,31 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Volume Backups Restore interface (1.1 extension).
+"""Volume Backups Restore interface (v2 extension).
This is part of the Volume Backups interface.
"""
-from cinderclient import base
+from cinderclient.v3.volume_backups_restore import * # flake8: noqa
-
-class VolumeBackupsRestore(base.Resource):
- """A Volume Backups Restore represents a restore operation."""
- def __repr__(self):
- return "<VolumeBackupsRestore: %s>" % self.volume_id
-
-
-class VolumeBackupRestoreManager(base.Manager):
- """Manage :class:`VolumeBackupsRestore` resources."""
- resource_class = VolumeBackupsRestore
-
- def restore(self, backup_id, volume_id=None):
- """Restore a backup to a volume.
-
- :param backup_id: The ID of the backup to restore.
- :param volume_id: The ID of the volume to restore the backup to.
- :rtype: :class:`Restore`
- """
- body = {'restore': {'volume_id': volume_id}}
- return self._create("/backups/%s/restore" % backup_id,
- body, "restore")
diff --git a/cinderclient/v2/volume_encryption_types.py b/cinderclient/v2/volume_encryption_types.py
index c4c7a69..9923af6 100644
--- a/cinderclient/v2/volume_encryption_types.py
+++ b/cinderclient/v2/volume_encryption_types.py
@@ -13,92 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-
"""
Volume Encryption Type interface
"""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
-
-
-class VolumeEncryptionType(base.Resource):
- """
- A Volume Encryption Type is a collection of settings used to conduct
- encryption for a specific volume type.
- """
- def __repr__(self):
- return "<VolumeEncryptionType: %s>" % self.name
-
-
-class VolumeEncryptionTypeManager(base.ManagerWithFind):
- """
- Manage :class: `VolumeEncryptionType` resources.
- """
- resource_class = VolumeEncryptionType
-
- def list(self, search_opts=None):
- """
- List all volume encryption types.
-
- :param volume_types: a list of volume types
- :return: a list of :class: VolumeEncryptionType instances
- """
- # Since the encryption type is a volume type extension, we cannot get
- # all encryption types without going through all volume types.
- volume_types = self.api.volume_types.list()
- encryption_types = []
- list_of_resp = []
- for volume_type in volume_types:
- encryption_type = self._get("/types/%s/encryption"
- % base.getid(volume_type))
- if hasattr(encryption_type, 'volume_type_id'):
- encryption_types.append(encryption_type)
-
- list_of_resp.extend(encryption_type.request_ids)
-
- return common_base.ListWithMeta(encryption_types, list_of_resp)
-
- def get(self, volume_type):
- """
- Get the volume encryption type for the specified volume type.
-
- :param volume_type: the volume type to query
- :return: an instance of :class: VolumeEncryptionType
- """
- return self._get("/types/%s/encryption" % base.getid(volume_type))
-
- def create(self, volume_type, specs):
- """
- Creates encryption type for a volume type. Default: admin only.
-
- :param volume_type: the volume type on which to add an encryption type
- :param specs: the encryption type specifications to add
- :return: an instance of :class: VolumeEncryptionType
- """
- body = {'encryption': specs}
- return self._create("/types/%s/encryption" % base.getid(volume_type),
- body, "encryption")
-
- def update(self, volume_type, specs):
- """
- Update the encryption type information for the specified volume type.
-
- :param volume_type: the volume type whose encryption type information
- must be updated
- :param specs: the encryption type specifications to update
- :return: an instance of :class: VolumeEncryptionType
- """
- body = {'encryption': specs}
- return self._update("/types/%s/encryption/provider" %
- base.getid(volume_type), body)
-
- def delete(self, volume_type):
- """
- Delete the encryption type information for the specified volume type.
+from cinderclient.v3.volume_encryption_types import * # flake8: noqa
- :param volume_type: the volume type whose encryption type information
- must be deleted
- """
- return self._delete("/types/%s/encryption/provider" %
- base.getid(volume_type))
diff --git a/cinderclient/v2/volume_snapshots.py b/cinderclient/v2/volume_snapshots.py
index 46ae2bb..b0fd892 100644
--- a/cinderclient/v2/volume_snapshots.py
+++ b/cinderclient/v2/volume_snapshots.py
@@ -13,193 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Volume snapshot interface (1.1 extension)."""
+"""Volume snapshot interface (v2 extension)."""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
+from cinderclient.v3.volume_snapshots import * # flake8: noqa
-
-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."""
- return self.manager.delete(self)
-
- def update(self, **kwargs):
- """Update the name or description for this snapshot."""
- return self.manager.update(self, **kwargs)
-
- @property
- def progress(self):
- return self._info.get('os-extended-snapshot-attributes:progress')
-
- @property
- def project_id(self):
- return self._info.get('os-extended-snapshot-attributes:project_id')
-
- def reset_state(self, state):
- """Update the snapshot with the provided state."""
- return self.manager.reset_state(self, state)
-
- def set_metadata(self, metadata):
- """Set metadata of this snapshot."""
- return self.manager.set_metadata(self, metadata)
-
- def delete_metadata(self, keys):
- """Delete metadata of this snapshot."""
- return self.manager.delete_metadata(self, keys)
-
- def update_all_metadata(self, metadata):
- """Update_all metadata of this snapshot."""
- return self.manager.update_all_metadata(self, metadata)
-
- def manage(self, volume_id, ref, name=None, description=None,
- metadata=None):
- """Manage an existing snapshot."""
- self.manager.manage(volume_id=volume_id, ref=ref, name=name,
- description=description, metadata=metadata)
-
- def unmanage(self, snapshot):
- """Unmanage a snapshot."""
- self.manager.unmanage(snapshot)
-
-
-class SnapshotManager(base.ManagerWithFind):
- """Manage :class:`Snapshot` resources."""
- resource_class = Snapshot
-
- def create(self, volume_id, force=False,
- name=None, description=None, metadata=None):
-
- """Creates 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 name: Name of the snapshot
- :param description: Description of the snapshot
- :param metadata: Metadata of the snapshot
- :rtype: :class:`Snapshot`
- """
-
- if metadata is None:
- snapshot_metadata = {}
- else:
- snapshot_metadata = metadata
-
- body = {'snapshot': {'volume_id': volume_id,
- 'force': force,
- 'name': name,
- 'description': description,
- 'metadata': snapshot_metadata}}
- return self._create('/snapshots', body, 'snapshot')
-
- def get(self, snapshot_id):
- """Shows snapshot details.
-
- :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, search_opts=None, marker=None, limit=None,
- sort=None):
- """Get a list of all snapshots.
-
- :rtype: list of :class:`Snapshot`
- """
- resource_type = "snapshots"
- url = self._build_list_url(resource_type, detailed=detailed,
- search_opts=search_opts, marker=marker,
- limit=limit, sort=sort)
- return self._list(url, resource_type, limit=limit)
-
- def delete(self, snapshot):
- """Delete a snapshot.
-
- :param snapshot: The :class:`Snapshot` to delete.
- """
- return self._delete("/snapshots/%s" % base.getid(snapshot))
-
- def update(self, snapshot, **kwargs):
- """Update the name or description for a snapshot.
-
- :param snapshot: The :class:`Snapshot` to update.
- """
- if not kwargs:
- return
-
- body = {"snapshot": kwargs}
-
- return self._update("/snapshots/%s" % base.getid(snapshot), body)
-
- def reset_state(self, snapshot, state):
- """Update the specified snapshot with the provided state."""
- return self._action('os-reset_status', snapshot, {'status': state})
-
- def _action(self, action, snapshot, info=None, **kwargs):
- """Perform a snapshot action."""
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/snapshots/%s/action' % base.getid(snapshot)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
-
- def update_snapshot_status(self, snapshot, update_dict):
- return self._action('os-update_snapshot_status',
- base.getid(snapshot), update_dict)
-
- def set_metadata(self, snapshot, metadata):
- """Update/Set a snapshots metadata.
-
- :param snapshot: The :class:`Snapshot`.
- :param metadata: A list of keys to be set.
- """
- body = {'metadata': metadata}
- return self._create("/snapshots/%s/metadata" % base.getid(snapshot),
- body, "metadata")
-
- def delete_metadata(self, snapshot, keys):
- """Delete specified keys from snapshot metadata.
-
- :param snapshot: The :class:`Snapshot`.
- :param keys: A list of keys to be removed.
- """
- response_list = []
- snapshot_id = base.getid(snapshot)
- for k in keys:
- resp, body = self._delete("/snapshots/%s/metadata/%s" %
- (snapshot_id, k))
- response_list.append(resp)
-
- return common_base.ListWithMeta([], response_list)
-
- def update_all_metadata(self, snapshot, metadata):
- """Update_all snapshot metadata.
-
- :param snapshot: The :class:`Snapshot`.
- :param metadata: A list of keys to be updated.
- """
- body = {'metadata': metadata}
- return self._update("/snapshots/%s/metadata" % base.getid(snapshot),
- body)
-
- def manage(self, volume_id, ref, name=None, description=None,
- metadata=None):
- """Manage an existing snapshot."""
- body = {'snapshot': {'volume_id': volume_id,
- 'ref': ref,
- 'name': name,
- 'description': description,
- 'metadata': metadata
- }
- }
- return self._create('/os-snapshot-manage', body, 'snapshot')
-
- def unmanage(self, snapshot):
- """Unmanage a snapshot."""
- return self._action('os-unmanage', snapshot, None)
diff --git a/cinderclient/v2/volume_transfers.py b/cinderclient/v2/volume_transfers.py
index a2bfe3c..9e17069 100644
--- a/cinderclient/v2/volume_transfers.py
+++ b/cinderclient/v2/volume_transfers.py
@@ -14,88 +14,8 @@
# under the License.
"""
-Volume transfer interface (1.1 extension).
+Volume transfer interface (v2 extension).
"""
-try:
- from urllib import urlencode
-except ImportError:
- from urllib.parse import urlencode
-import six
-from cinderclient import base
+from cinderclient.v3.volume_transfers import * # flake8: noqa
-
-class VolumeTransfer(base.Resource):
- """Transfer a volume from one tenant to another"""
-
- def __repr__(self):
- return "<VolumeTransfer: %s>" % self.id
-
- def delete(self):
- """Delete this volume transfer."""
- return self.manager.delete(self)
-
-
-class VolumeTransferManager(base.ManagerWithFind):
- """Manage :class:`VolumeTransfer` resources."""
- resource_class = VolumeTransfer
-
- def create(self, volume_id, name=None):
- """Creates a volume transfer.
-
- :param volume_id: The ID of the volume to transfer.
- :param name: The name of the transfer.
- :rtype: :class:`VolumeTransfer`
- """
- body = {'transfer': {'volume_id': volume_id,
- 'name': name}}
- return self._create('/os-volume-transfer', body, 'transfer')
-
- def accept(self, transfer_id, auth_key):
- """Accept a volume transfer.
-
- :param transfer_id: The ID of the transfer to accept.
- :param auth_key: The auth_key of the transfer.
- :rtype: :class:`VolumeTransfer`
- """
- body = {'accept': {'auth_key': auth_key}}
- return self._create('/os-volume-transfer/%s/accept' % transfer_id,
- body, 'transfer')
-
- def get(self, transfer_id):
- """Show details of a volume transfer.
-
- :param transfer_id: The ID of the volume transfer to display.
- :rtype: :class:`VolumeTransfer`
- """
- return self._get("/os-volume-transfer/%s" % transfer_id, "transfer")
-
- def list(self, detailed=True, search_opts=None):
- """Get a list of all volume transfer.
-
- :rtype: list of :class:`VolumeTransfer`
- """
- if search_opts is None:
- search_opts = {}
-
- qparams = {}
-
- for opt, val in six.iteritems(search_opts):
- if val:
- qparams[opt] = val
-
- query_string = "?%s" % urlencode(qparams) if qparams else ""
-
- detail = ""
- if detailed:
- detail = "/detail"
-
- return self._list("/os-volume-transfer%s%s" % (detail, query_string),
- "transfers")
-
- def delete(self, transfer_id):
- """Delete a volume transfer.
-
- :param transfer_id: The :class:`VolumeTransfer` to delete.
- """
- return self._delete("/os-volume-transfer/%s" % base.getid(transfer_id))
diff --git a/cinderclient/v2/volume_type_access.py b/cinderclient/v2/volume_type_access.py
index abdfa86..b18368e 100644
--- a/cinderclient/v2/volume_type_access.py
+++ b/cinderclient/v2/volume_type_access.py
@@ -14,40 +14,5 @@
"""Volume type access interface."""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
+from cinderclient.v3.volume_type_access import * # flake8: noqa
-
-class VolumeTypeAccess(base.Resource):
- def __repr__(self):
- return "<VolumeTypeAccess: %s>" % self.project_id
-
-
-class VolumeTypeAccessManager(base.ManagerWithFind):
- """
- Manage :class:`VolumeTypeAccess` resources.
- """
- resource_class = VolumeTypeAccess
-
- def list(self, volume_type):
- return self._list(
- '/types/%s/os-volume-type-access' % base.getid(volume_type),
- 'volume_type_access')
-
- def add_project_access(self, volume_type, project):
- """Add a project to the given volume type access list."""
- info = {'project': project}
- return self._action('addProjectAccess', volume_type, info)
-
- def remove_project_access(self, volume_type, project):
- """Remove a project from the given volume type access list."""
- info = {'project': project}
- return self._action('removeProjectAccess', volume_type, info)
-
- def _action(self, action, volume_type, info, **kwargs):
- """Perform a volume type action."""
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/types/%s/action' % base.getid(volume_type)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v2/volume_types.py b/cinderclient/v2/volume_types.py
index 251e75b..84332e7 100644
--- a/cinderclient/v2/volume_types.py
+++ b/cinderclient/v2/volume_types.py
@@ -13,139 +13,7 @@
# 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 "<VolumeType: %s>" % self.name
-
- @property
- def is_public(self):
- """
- Provide a user-friendly accessor to os-volume-type-access:is_public
- """
- return self._info.get("os-volume-type-access:is_public",
- self._info.get("is_public", 'N/A'))
-
- def get_keys(self):
- """Get extra specs from a volume type.
-
- :param vol_type: The :class:`VolumeType` to get extra specs from
- """
- _resp, body = self.manager.api.client.get(
- "/types/%s/extra_specs" %
- base.getid(self))
- return body["extra_specs"]
-
- def set_keys(self, metadata):
- """Set extra specs on a volume type.
-
- :param type : The :class:`VolumeType` to set extra spec on
- :param metadata: A dict of key/value pairs to be set
- """
- body = {'extra_specs': metadata}
- return self.manager._create(
- "/types/%s/extra_specs" % base.getid(self),
- body,
- "extra_specs",
- return_raw=True)
-
- def unset_keys(self, keys):
- """Unset extra specs on a volue type.
-
- :param type_id: The :class:`VolumeType` to unset extra spec on
- :param keys: A list of keys to be unset
- """
-
- # NOTE(jdg): This wasn't actually doing all of the keys before
- # the return in the loop resulted in ony ONE key being unset.
- # since on success the return was NONE, we'll only interrupt the loop
- # and return if there's an error
- for k in keys:
- resp = self.manager._delete(
- "/types/%s/extra_specs/%s" % (
- base.getid(self), k))
- if resp is not None:
- return resp
-
-
-class VolumeTypeManager(base.ManagerWithFind):
- """Manage :class:`VolumeType` resources."""
- resource_class = VolumeType
-
- def list(self, search_opts=None, is_public=None):
- """Lists all volume types.
-
- :rtype: list of :class:`VolumeType`.
- """
- query_string = ''
- if not is_public:
- query_string = '?is_public=%s' % is_public
- return self._list("/types%s" % (query_string), "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 default(self):
- """Get the default volume type.
-
- :rtype: :class:`VolumeType`
- """
- return self._get("/types/default", "volume_type")
-
- def delete(self, volume_type):
- """Deletes a specific volume_type.
-
- :param volume_type: The name or ID of the :class:`VolumeType` to get.
- """
- return self._delete("/types/%s" % base.getid(volume_type))
-
- def create(self, name, description=None, is_public=True):
- """Creates a volume type.
-
- :param name: Descriptive name of the volume type
- :param description: Description of the the volume type
- :param is_public: Volume type visibility
- :rtype: :class:`VolumeType`
- """
-
- body = {
- "volume_type": {
- "name": name,
- "description": description,
- "os-volume-type-access:is_public": is_public,
- }
- }
-
- return self._create("/types", body, "volume_type")
-
- def update(self, volume_type, name=None, description=None, is_public=None):
- """Update the name and/or description for a volume type.
-
- :param volume_type: The ID of the :class:`VolumeType` to update.
- :param name: Descriptive name of the volume type.
- :param description: Description of the the volume type.
- :rtype: :class:`VolumeType`
- """
-
- body = {
- "volume_type": {
- "name": name,
- "description": description
- }
- }
- if is_public is not None:
- body["volume_type"]["is_public"] = is_public
+from cinderclient.v3.volume_types import * # flake8: noqa
- return self._update("/types/%s" % base.getid(volume_type),
- body, response_key="volume_type")
diff --git a/cinderclient/v2/volumes.py b/cinderclient/v2/volumes.py
index bab4796..093b911 100644
--- a/cinderclient/v2/volumes.py
+++ b/cinderclient/v2/volumes.py
@@ -15,590 +15,5 @@
"""Volume interface (v2 extension)."""
-from cinderclient import base
-from cinderclient.openstack.common.apiclient import base as common_base
+from cinderclient.v3.volumes import * # flake8: noqa
-
-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, cascade=False):
- """Delete this volume."""
- return self.manager.delete(self, cascade=cascade)
-
- def update(self, **kwargs):
- """Update the name or description for this volume."""
- return self.manager.update(self, **kwargs)
-
- def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None):
- """Set attachment metadata.
-
- :param instance_uuid: uuid of the attaching instance.
- :param mountpoint: mountpoint on the attaching instance or host.
- :param mode: the access mode.
- :param host_name: name of the attaching host.
- """
- return self.manager.attach(self, instance_uuid, mountpoint, mode,
- host_name)
-
- def detach(self):
- """Clear attachment metadata."""
- return self.manager.detach(self)
-
- def reserve(self, volume):
- """Reserve this volume."""
- return self.manager.reserve(self)
-
- def unreserve(self, volume):
- """Unreserve this volume."""
- return self.manager.unreserve(self)
-
- def begin_detaching(self, volume):
- """Begin detaching volume."""
- return self.manager.begin_detaching(self)
-
- def roll_detaching(self, volume):
- """Roll detaching volume."""
- return self.manager.roll_detaching(self)
-
- def initialize_connection(self, volume, connector):
- """Initialize a volume connection.
-
- :param connector: connector dict from nova.
- """
- return self.manager.initialize_connection(self, connector)
-
- def terminate_connection(self, volume, connector):
- """Terminate a volume connection.
-
- :param connector: connector dict from nova.
- """
- return self.manager.terminate_connection(self, connector)
-
- def set_metadata(self, volume, metadata):
- """Set or Append metadata to a volume.
-
- :param volume : The :class: `Volume` to set metadata on
- :param metadata: A dict of key/value pairs to set
- """
- return self.manager.set_metadata(self, metadata)
-
- def set_image_metadata(self, volume, metadata):
- """Set a volume's image metadata.
-
- :param volume : The :class: `Volume` to set metadata on
- :param metadata: A dict of key/value pairs to set
- """
- return self.manager.set_image_metadata(self, volume, metadata)
-
- def delete_image_metadata(self, volume, keys):
- """Delete specified keys from volume's image metadata.
-
- :param volume: The :class:`Volume`.
- :param keys: A list of keys to be removed.
- """
- return self.manager.delete_image_metadata(self, volume, keys)
-
- def show_image_metadata(self, volume):
- """Show a volume's image metadata.
-
- :param volume : The :class: `Volume` where the image metadata
- associated.
- """
- return self.manager.show_image_metadata(self)
-
- def upload_to_image(self, force, image_name, container_format,
- disk_format):
- """Upload a volume to image service as an image."""
- return self.manager.upload_to_image(self, force, image_name,
- container_format, disk_format)
-
- def force_delete(self):
- """Delete the specified volume ignoring its current state.
-
- :param volume: The UUID of the volume to force-delete.
- """
- return self.manager.force_delete(self)
-
- def reset_state(self, state, attach_status=None, migration_status=None):
- """Update the volume with the provided state.
-
- :param state: The state of the volume to set.
- :param attach_status: The attach_status of the volume to be set,
- or None to keep the current status.
- :param migration_status: The migration_status of the volume to be set,
- or None to keep the current status.
- """
- return self.manager.reset_state(self, state, attach_status,
- migration_status)
-
- def extend(self, volume, new_size):
- """Extend the size of the specified volume.
-
- :param volume: The UUID of the volume to extend
- :param new_size: The desired size to extend volume to.
- """
- return self.manager.extend(self, new_size)
-
- def migrate_volume(self, host, force_host_copy, lock_volume):
- """Migrate the volume to a new host."""
- return self.manager.migrate_volume(self, host, force_host_copy,
- lock_volume)
-
- def retype(self, volume_type, policy):
- """Change a volume's type."""
- return self.manager.retype(self, volume_type, policy)
-
- def update_all_metadata(self, metadata):
- """Update all metadata of this volume."""
- return self.manager.update_all_metadata(self, metadata)
-
- def update_readonly_flag(self, volume, read_only):
- """Update the read-only access mode flag of the specified volume.
-
- :param volume: The UUID of the volume to update.
- :param read_only: The value to indicate whether to update volume to
- read-only access mode.
- """
- return self.manager.update_readonly_flag(self, read_only)
-
- def manage(self, host, ref, name=None, description=None,
- volume_type=None, availability_zone=None, metadata=None,
- bootable=False):
- """Manage an existing volume."""
- return self.manager.manage(host=host, ref=ref, name=name,
- description=description,
- volume_type=volume_type,
- availability_zone=availability_zone,
- metadata=metadata, bootable=bootable)
-
- def unmanage(self, volume):
- """Unmanage a volume."""
- return self.manager.unmanage(volume)
-
- def promote(self, volume):
- """Promote secondary to be primary in relationship."""
- return self.manager.promote(volume)
-
- def reenable(self, volume):
- """Sync the secondary volume with primary for a relationship."""
- return self.manager.reenable(volume)
-
- def get_pools(self, detail):
- """Show pool information for backends."""
- return self.manager.get_pools(detail)
-
-
-class VolumeManager(base.ManagerWithFind):
- """Manage :class:`Volume` resources."""
- resource_class = Volume
-
- def create(self, size, consistencygroup_id=None, snapshot_id=None,
- source_volid=None, name=None, description=None,
- volume_type=None, user_id=None,
- project_id=None, availability_zone=None,
- metadata=None, imageRef=None, scheduler_hints=None,
- source_replica=None, multiattach=False):
- """Create a volume.
-
- :param size: Size of volume in GB
- :param consistencygroup_id: ID of the consistencygroup
- :param snapshot_id: ID of the snapshot
- :param name: Name of the volume
- :param description: Description of the volume
- :param volume_type: Type of 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
- :param source_replica: ID of source volume to clone replica
- :param scheduler_hints: (optional extension) arbitrary key-value pairs
- specified by the client to help boot an instance
- :param multiattach: Allow the volume to be attached to more than
- one instance
- :rtype: :class:`Volume`
- """
- if metadata is None:
- volume_metadata = {}
- else:
- volume_metadata = metadata
-
- body = {'volume': {'size': size,
- 'consistencygroup_id': consistencygroup_id,
- 'snapshot_id': snapshot_id,
- 'name': name,
- 'description': description,
- 'volume_type': volume_type,
- 'user_id': user_id,
- 'project_id': project_id,
- 'availability_zone': availability_zone,
- 'status': "creating",
- 'attach_status': "detached",
- 'metadata': volume_metadata,
- 'imageRef': imageRef,
- 'source_volid': source_volid,
- 'source_replica': source_replica,
- 'multiattach': multiattach,
- }}
-
- if scheduler_hints:
- body['OS-SCH-HNT:scheduler_hints'] = scheduler_hints
-
- return self._create('/volumes', body, 'volume')
-
- def get(self, volume_id):
- """Get a volume.
-
- :param volume_id: The ID of the volume to get.
- :rtype: :class:`Volume`
- """
- return self._get("/volumes/%s" % volume_id, "volume")
-
- def list(self, detailed=True, search_opts=None, marker=None, limit=None,
- sort_key=None, sort_dir=None, sort=None):
- """Lists all volumes.
-
- :param detailed: Whether to return detailed volume info.
- :param search_opts: Search options to filter out volumes.
- :param marker: Begin returning volumes that appear later in the volume
- list than that represented by this volume id.
- :param limit: Maximum number of volumes to return.
- :param sort_key: Key to be sorted; deprecated in kilo
- :param sort_dir: Sort direction, should be 'desc' or 'asc'; deprecated
- in kilo
- :param sort: Sort information
- :rtype: list of :class:`Volume`
- """
-
- resource_type = "volumes"
- url = self._build_list_url(resource_type, detailed=detailed,
- search_opts=search_opts, marker=marker,
- limit=limit, sort_key=sort_key,
- sort_dir=sort_dir, sort=sort)
- return self._list(url, resource_type, limit=limit)
-
- def delete(self, volume, cascade=False):
- """Delete a volume.
-
- :param volume: The :class:`Volume` to delete.
- :param cascade: Also delete dependent snapshots.
- """
-
- loc = "/volumes/%s" % base.getid(volume)
-
- if cascade:
- loc += '?cascade=True'
-
- return self._delete(loc)
-
- def update(self, volume, **kwargs):
- """Update the name or description for a volume.
-
- :param volume: The :class:`Volume` to update.
- """
- if not kwargs:
- return
-
- body = {"volume": kwargs}
-
- return self._update("/volumes/%s" % base.getid(volume), body)
-
- def _action(self, action, volume, info=None, **kwargs):
- """Perform a volume "action."
- """
- body = {action: info}
- self.run_hooks('modify_body_for_action', body, **kwargs)
- url = '/volumes/%s/action' % base.getid(volume)
- resp, body = self.api.client.post(url, body=body)
- return common_base.TupleWithMeta((resp, body), resp)
-
- def attach(self, volume, instance_uuid, mountpoint, mode='rw',
- host_name=None):
- """Set attachment metadata.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to attach.
- :param instance_uuid: uuid of the attaching instance.
- :param mountpoint: mountpoint on the attaching instance or host.
- :param mode: the access mode.
- :param host_name: name of the attaching host.
- """
- body = {'mountpoint': mountpoint, 'mode': mode}
- if instance_uuid is not None:
- body.update({'instance_uuid': instance_uuid})
- if host_name is not None:
- body.update({'host_name': host_name})
- return self._action('os-attach', volume, body)
-
- def detach(self, volume, attachment_uuid=None):
- """Clear attachment metadata.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to detach.
- :param attachment_uuid: The uuid of the volume attachment.
- """
- return self._action('os-detach', volume,
- {'attachment_id': attachment_uuid})
-
- def reserve(self, volume):
- """Reserve this volume.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to reserve.
- """
- return self._action('os-reserve', volume)
-
- def unreserve(self, volume):
- """Unreserve this volume.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to unreserve.
- """
- return self._action('os-unreserve', volume)
-
- def begin_detaching(self, volume):
- """Begin detaching this volume.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to detach.
- """
- return self._action('os-begin_detaching', volume)
-
- def roll_detaching(self, volume):
- """Roll detaching this volume.
-
- :param volume: The :class:`Volume` (or its ID)
- you would like to roll detaching.
- """
- return self._action('os-roll_detaching', volume)
-
- def initialize_connection(self, volume, connector):
- """Initialize a volume connection.
-
- :param volume: The :class:`Volume` (or its ID).
- :param connector: connector dict from nova.
- """
- resp, body = self._action('os-initialize_connection', volume,
- {'connector': connector})
- return common_base.DictWithMeta(body['connection_info'], resp)
-
- def terminate_connection(self, volume, connector):
- """Terminate a volume connection.
-
- :param volume: The :class:`Volume` (or its ID).
- :param connector: connector dict from nova.
- """
- return self._action('os-terminate_connection', volume,
- {'connector': connector})
-
- def set_metadata(self, volume, metadata):
- """Update/Set a volumes metadata.
-
- :param volume: The :class:`Volume`.
- :param metadata: A list of keys to be set.
- """
- body = {'metadata': metadata}
- return self._create("/volumes/%s/metadata" % base.getid(volume),
- body, "metadata")
-
- def delete_metadata(self, volume, keys):
- """Delete specified keys from volumes metadata.
-
- :param volume: The :class:`Volume`.
- :param keys: A list of keys to be removed.
- """
- response_list = []
- for k in keys:
- resp, body = self._delete("/volumes/%s/metadata/%s" %
- (base.getid(volume), k))
- response_list.append(resp)
-
- return common_base.ListWithMeta([], response_list)
-
- def set_image_metadata(self, volume, metadata):
- """Set a volume's image metadata.
-
- :param volume: The :class:`Volume`.
- :param metadata: keys and the values to be set with.
- :type metadata: dict
- """
- return self._action("os-set_image_metadata", volume,
- {'metadata': metadata})
-
- def delete_image_metadata(self, volume, keys):
- """Delete specified keys from volume's image metadata.
-
- :param volume: The :class:`Volume`.
- :param keys: A list of keys to be removed.
- """
- response_list = []
- for key in keys:
- resp, body = self._action("os-unset_image_metadata", volume,
- {'key': key})
- response_list.append(resp)
-
- return common_base.ListWithMeta([], response_list)
-
- def show_image_metadata(self, volume):
- """Show a volume's image metadata.
-
- :param volume : The :class: `Volume` where the image metadata
- associated.
- """
- return self._action("os-show_image_metadata", volume)
-
- def upload_to_image(self, volume, force, image_name, container_format,
- disk_format):
- """Upload volume to image service as image.
-
- :param volume: The :class:`Volume` to upload.
- """
- return self._action('os-volume_upload_image',
- volume,
- {'force': force,
- 'image_name': image_name,
- 'container_format': container_format,
- 'disk_format': disk_format})
-
- def force_delete(self, volume):
- """Delete the specified volume ignoring its current state.
-
- :param volume: The :class:`Volume` to force-delete.
- """
- return self._action('os-force_delete', base.getid(volume))
-
- def reset_state(self, volume, state, attach_status=None,
- migration_status=None):
- """Update the provided volume with the provided state.
-
- :param volume: The :class:`Volume` to set the state.
- :param state: The state of the volume to be set.
- :param attach_status: The attach_status of the volume to be set,
- or None to keep the current status.
- :param migration_status: The migration_status of the volume to be set,
- or None to keep the current status.
- """
- body = {'status': state}
- if attach_status:
- body.update({'attach_status': attach_status})
- if migration_status:
- body.update({'migration_status': migration_status})
- return self._action('os-reset_status', volume, body)
-
- def extend(self, volume, new_size):
- """Extend the size of the specified volume.
-
- :param volume: The UUID of the volume to extend.
- :param new_size: The requested size to extend volume to.
- """
- return self._action('os-extend',
- base.getid(volume),
- {'new_size': new_size})
-
- def get_encryption_metadata(self, volume_id):
- """
- Retrieve the encryption metadata from the desired volume.
-
- :param volume_id: the id of the volume to query
- :return: a dictionary of volume encryption metadata
- """
- metadata = self._get("/volumes/%s/encryption" % volume_id)
- return common_base.DictWithMeta(metadata._info, metadata.request_ids)
-
- def migrate_volume(self, volume, host, force_host_copy, lock_volume):
- """Migrate volume to new host.
-
- :param volume: The :class:`Volume` to migrate
- :param host: The destination host
- :param force_host_copy: Skip driver optimizations
- :param lock_volume: Lock the volume and guarantee the migration
- to finish
- """
- return self._action('os-migrate_volume',
- volume,
- {'host': host, 'force_host_copy': force_host_copy,
- 'lock_volume': lock_volume})
-
- def migrate_volume_completion(self, old_volume, new_volume, error):
- """Complete the migration from the old volume to the temp new one.
-
- :param old_volume: The original :class:`Volume` in the migration
- :param new_volume: The new temporary :class:`Volume` in the migration
- :param error: Inform of an error to cause migration cleanup
- """
- new_volume_id = base.getid(new_volume)
- resp, body = self._action('os-migrate_volume_completion', old_volume,
- {'new_volume': new_volume_id,
- 'error': error})
- return common_base.DictWithMeta(body, resp)
-
- def update_all_metadata(self, volume, metadata):
- """Update all metadata of a volume.
-
- :param volume: The :class:`Volume`.
- :param metadata: A list of keys to be updated.
- """
- body = {'metadata': metadata}
- return self._update("/volumes/%s/metadata" % base.getid(volume),
- body)
-
- def update_readonly_flag(self, volume, flag):
- return self._action('os-update_readonly_flag',
- base.getid(volume),
- {'readonly': flag})
-
- def retype(self, volume, volume_type, policy):
- """Change a volume's type.
-
- :param volume: The :class:`Volume` to retype
- :param volume_type: New volume type
- :param policy: Policy for migration during the retype
- """
- return self._action('os-retype',
- volume,
- {'new_type': volume_type,
- 'migration_policy': policy})
-
- def set_bootable(self, volume, flag):
- return self._action('os-set_bootable',
- base.getid(volume),
- {'bootable': flag})
-
- def manage(self, host, ref, name=None, description=None,
- volume_type=None, availability_zone=None, metadata=None,
- bootable=False):
- """Manage an existing volume."""
- body = {'volume': {'host': host,
- 'ref': ref,
- 'name': name,
- 'description': description,
- 'volume_type': volume_type,
- 'availability_zone': availability_zone,
- 'metadata': metadata,
- 'bootable': bootable
- }}
- return self._create('/os-volume-manage', body, 'volume')
-
- def unmanage(self, volume):
- """Unmanage a volume."""
- return self._action('os-unmanage', volume, None)
-
- def promote(self, volume):
- """Promote secondary to be primary in relationship."""
- return self._action('os-promote-replica', volume, None)
-
- def reenable(self, volume):
- """Sync the secondary volume with primary for a relationship."""
- return self._action('os-reenable-replica', volume, None)
-
- def get_pools(self, detail):
- """Show pool information for backends."""
- query_string = ""
- if detail:
- query_string = "?detail=True"
-
- return self._get('/scheduler-stats/get_pools%s' % query_string, None)
diff --git a/cinderclient/v3/__init__.py b/cinderclient/v3/__init__.py
new file mode 100644
index 0000000..515d90c
--- /dev/null
+++ b/cinderclient/v3/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2013 OpenStack Foundation
+#
+# 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.v3.client import Client # noqa
diff --git a/cinderclient/v3/availability_zones.py b/cinderclient/v3/availability_zones.py
new file mode 100644
index 0000000..db6b8da
--- /dev/null
+++ b/cinderclient/v3/availability_zones.py
@@ -0,0 +1,42 @@
+# Copyright 2011-2013 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# 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.
+
+"""Availability Zone interface (v3 extension)"""
+
+from cinderclient import base
+
+
+class AvailabilityZone(base.Resource):
+ NAME_ATTR = 'display_name'
+
+ def __repr__(self):
+ return "<AvailabilityZone: %s>" % self.zoneName
+
+
+class AvailabilityZoneManager(base.ManagerWithFind):
+ """Manage :class:`AvailabilityZone` resources."""
+ resource_class = AvailabilityZone
+
+ def list(self, detailed=False):
+ """Lists all availability zones.
+
+ :rtype: list of :class:`AvailabilityZone`
+ """
+ if detailed is True:
+ return self._list("/os-availability-zone/detail",
+ "availabilityZoneInfo")
+ else:
+ return self._list("/os-availability-zone", "availabilityZoneInfo")
diff --git a/cinderclient/v3/capabilities.py b/cinderclient/v3/capabilities.py
new file mode 100644
index 0000000..d0f8ab2
--- /dev/null
+++ b/cinderclient/v3/capabilities.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2015 Hitachi Data 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.
+
+"""Capabilities interface (v3 extension)"""
+
+
+from cinderclient import base
+
+
+class Capabilities(base.Resource):
+ NAME_ATTR = 'name'
+
+ def __repr__(self):
+ return "<Capabilities: %s>" % self.name
+
+
+class CapabilitiesManager(base.Manager):
+ """Manage :class:`Capabilities` resources."""
+ resource_class = Capabilities
+
+ def get(self, host):
+ """Show backend volume stats and properties.
+
+ :param host: Specified backend to obtain volume stats and properties.
+ :rtype: :class:`Capabilities`
+ """
+ return self._get('/capabilities/%s' % host, None)
diff --git a/cinderclient/v3/cgsnapshots.py b/cinderclient/v3/cgsnapshots.py
new file mode 100644
index 0000000..951cedf
--- /dev/null
+++ b/cinderclient/v3/cgsnapshots.py
@@ -0,0 +1,126 @@
+# Copyright (C) 2012 - 2014 EMC Corporation.
+# 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.
+
+"""cgsnapshot interface (v3 extension)."""
+
+import six
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class Cgsnapshot(base.Resource):
+ """A cgsnapshot is snapshot of a consistency group."""
+ def __repr__(self):
+ return "<cgsnapshot: %s>" % self.id
+
+ def delete(self):
+ """Delete this cgsnapshot."""
+ return self.manager.delete(self)
+
+ def update(self, **kwargs):
+ """Update the name or description for this cgsnapshot."""
+ return self.manager.update(self, **kwargs)
+
+
+class CgsnapshotManager(base.ManagerWithFind):
+ """Manage :class:`Cgsnapshot` resources."""
+ resource_class = Cgsnapshot
+
+ def create(self, consistencygroup_id, name=None, description=None,
+ user_id=None,
+ project_id=None):
+ """Creates a cgsnapshot.
+
+ :param consistencygroup: Name or uuid of a consistencygroup
+ :param name: Name of the cgsnapshot
+ :param description: Description of the cgsnapshot
+ :param user_id: User id derived from context
+ :param project_id: Project id derived from context
+ :rtype: :class:`Cgsnapshot`
+ """
+
+ body = {'cgsnapshot': {'consistencygroup_id': consistencygroup_id,
+ 'name': name,
+ 'description': description,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'status': "creating",
+ }}
+
+ return self._create('/cgsnapshots', body, 'cgsnapshot')
+
+ def get(self, cgsnapshot_id):
+ """Get a cgsnapshot.
+
+ :param cgsnapshot_id: The ID of the cgsnapshot to get.
+ :rtype: :class:`Cgsnapshot`
+ """
+ return self._get("/cgsnapshots/%s" % cgsnapshot_id, "cgsnapshot")
+
+ def list(self, detailed=True, search_opts=None):
+ """Lists all cgsnapshots.
+
+ :rtype: list of :class:`Cgsnapshot`
+ """
+ if search_opts is None:
+ search_opts = {}
+
+ qparams = {}
+
+ for opt, val in six.iteritems(search_opts):
+ if val:
+ qparams[opt] = val
+
+ query_string = "?%s" % urlencode(qparams) if qparams else ""
+
+ detail = ""
+ if detailed:
+ detail = "/detail"
+
+ return self._list("/cgsnapshots%s%s" % (detail, query_string),
+ "cgsnapshots")
+
+ def delete(self, cgsnapshot):
+ """Delete a cgsnapshot.
+
+ :param cgsnapshot: The :class:`Cgsnapshot` to delete.
+ """
+ return self._delete("/cgsnapshots/%s" % base.getid(cgsnapshot))
+
+ def update(self, cgsnapshot, **kwargs):
+ """Update the name or description for a cgsnapshot.
+
+ :param cgsnapshot: The :class:`Cgsnapshot` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"cgsnapshot": kwargs}
+
+ return self._update("/cgsnapshots/%s" % base.getid(cgsnapshot), body)
+
+ def _action(self, action, cgsnapshot, info=None, **kwargs):
+ """Perform a cgsnapshot "action."
+ """
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/cgsnapshots/%s/action' % base.getid(cgsnapshot)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py
new file mode 100644
index 0000000..58f6ee5
--- /dev/null
+++ b/cinderclient/v3/client.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 import client
+from cinderclient.v3 import availability_zones
+from cinderclient.v3 import cgsnapshots
+from cinderclient.v3 import consistencygroups
+from cinderclient.v3 import capabilities
+from cinderclient.v3 import limits
+from cinderclient.v3 import pools
+from cinderclient.v3 import qos_specs
+from cinderclient.v3 import quota_classes
+from cinderclient.v3 import quotas
+from cinderclient.v3 import services
+from cinderclient.v3 import volumes
+from cinderclient.v3 import volume_snapshots
+from cinderclient.v3 import volume_types
+from cinderclient.v3 import volume_type_access
+from cinderclient.v3 import volume_encryption_types
+from cinderclient.v3 import volume_backups
+from cinderclient.v3 import volume_backups_restore
+from cinderclient.v3 import volume_transfers
+
+
+class Client(object):
+ """Top-level object to access the OpenStack Volume API.
+
+ Create an instance with your creds::
+
+ >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
+
+ Then call methods on its managers::
+
+ >>> client.volumes.list()
+ ...
+ """
+
+ version = '3'
+
+ def __init__(self, username=None, api_key=None, project_id=None,
+ auth_url='', insecure=False, timeout=None, tenant_id=None,
+ proxy_tenant_id=None, proxy_token=None, region_name=None,
+ endpoint_type='publicURL', extensions=None,
+ service_type='volumev3', service_name=None,
+ volume_service_name=None, bypass_url=None, retries=None,
+ http_log_debug=False, cacert=None, auth_system='keystone',
+ auth_plugin=None, session=None, **kwargs):
+ # FIXME(comstud): Rename the api_key argument above when we
+ # know it's not being used as keyword argument
+ password = api_key
+ self.limits = limits.LimitsManager(self)
+
+ # extensions
+ self.volumes = volumes.VolumeManager(self)
+ self.volume_snapshots = volume_snapshots.SnapshotManager(self)
+ self.volume_types = volume_types.VolumeTypeManager(self)
+ self.volume_type_access = \
+ volume_type_access.VolumeTypeAccessManager(self)
+ self.volume_encryption_types = \
+ volume_encryption_types.VolumeEncryptionTypeManager(self)
+ self.qos_specs = qos_specs.QoSSpecsManager(self)
+ self.quota_classes = quota_classes.QuotaClassSetManager(self)
+ self.quotas = quotas.QuotaSetManager(self)
+ self.backups = volume_backups.VolumeBackupManager(self)
+ self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
+ self.transfers = volume_transfers.VolumeTransferManager(self)
+ self.services = services.ServiceManager(self)
+ self.consistencygroups = consistencygroups.\
+ ConsistencygroupManager(self)
+ self.cgsnapshots = cgsnapshots.CgsnapshotManager(self)
+ self.availability_zones = \
+ availability_zones.AvailabilityZoneManager(self)
+ self.pools = pools.PoolManager(self)
+ self.capabilities = capabilities.CapabilitiesManager(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._construct_http_client(
+ username=username,
+ password=password,
+ project_id=project_id,
+ auth_url=auth_url,
+ insecure=insecure,
+ timeout=timeout,
+ tenant_id=tenant_id,
+ proxy_tenant_id=tenant_id,
+ proxy_token=proxy_token,
+ region_name=region_name,
+ endpoint_type=endpoint_type,
+ service_type=service_type,
+ service_name=service_name,
+ volume_service_name=volume_service_name,
+ bypass_url=bypass_url,
+ retries=retries,
+ http_log_debug=http_log_debug,
+ cacert=cacert,
+ auth_system=auth_system,
+ auth_plugin=auth_plugin,
+ session=session,
+ **kwargs)
+
+ 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()
+
+ def get_volume_api_version_from_endpoint(self):
+ return self.client.get_volume_api_version_from_endpoint()
diff --git a/cinderclient/v3/consistencygroups.py b/cinderclient/v3/consistencygroups.py
new file mode 100644
index 0000000..2d5421e
--- /dev/null
+++ b/cinderclient/v3/consistencygroups.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2012 - 2014 EMC Corporation.
+# 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.
+
+"""Consistencygroup interface (v3 extension)."""
+
+import six
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class Consistencygroup(base.Resource):
+ """A Consistencygroup of volumes."""
+ def __repr__(self):
+ return "<Consistencygroup: %s>" % self.id
+
+ def delete(self, force='False'):
+ """Delete this consistencygroup."""
+ return self.manager.delete(self, force)
+
+ def update(self, **kwargs):
+ """Update the name or description for this consistencygroup."""
+ return self.manager.update(self, **kwargs)
+
+
+class ConsistencygroupManager(base.ManagerWithFind):
+ """Manage :class:`Consistencygroup` resources."""
+ resource_class = Consistencygroup
+
+ def create(self, volume_types, name=None,
+ description=None, user_id=None,
+ project_id=None, availability_zone=None):
+ """Creates a consistencygroup.
+
+ :param name: Name of the ConsistencyGroup
+ :param description: Description of the ConsistencyGroup
+ :param volume_types: Types of volume
+ :param user_id: User id derived from context
+ :param project_id: Project id derived from context
+ :param availability_zone: Availability Zone to use
+ :rtype: :class:`Consistencygroup`
+ """
+
+ body = {'consistencygroup': {'name': name,
+ 'description': description,
+ 'volume_types': volume_types,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'availability_zone': availability_zone,
+ 'status': "creating",
+ }}
+
+ return self._create('/consistencygroups', body, 'consistencygroup')
+
+ def create_from_src(self, cgsnapshot_id, source_cgid, name=None,
+ description=None, user_id=None,
+ project_id=None):
+ """Creates a consistencygroup from a cgsnapshot or a source CG.
+
+ :param cgsnapshot_id: UUID of a CGSnapshot
+ :param source_cgid: UUID of a source CG
+ :param name: Name of the ConsistencyGroup
+ :param description: Description of the ConsistencyGroup
+ :param user_id: User id derived from context
+ :param project_id: Project id derived from context
+ :rtype: A dictionary containing Consistencygroup metadata
+ """
+ body = {'consistencygroup-from-src': {'name': name,
+ 'description': description,
+ 'cgsnapshot_id': cgsnapshot_id,
+ 'source_cgid': source_cgid,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'status': "creating",
+ }}
+
+ self.run_hooks('modify_body_for_update', body,
+ 'consistencygroup-from-src')
+ resp, body = self.api.client.post(
+ "/consistencygroups/create_from_src", body=body)
+ return common_base.DictWithMeta(body['consistencygroup'], resp)
+
+ def get(self, group_id):
+ """Get a consistencygroup.
+
+ :param group_id: The ID of the consistencygroup to get.
+ :rtype: :class:`Consistencygroup`
+ """
+ return self._get("/consistencygroups/%s" % group_id,
+ "consistencygroup")
+
+ def list(self, detailed=True, search_opts=None):
+ """Lists all consistencygroups.
+
+ :rtype: list of :class:`Consistencygroup`
+ """
+ if search_opts is None:
+ search_opts = {}
+
+ qparams = {}
+
+ for opt, val in six.iteritems(search_opts):
+ if val:
+ qparams[opt] = val
+
+ query_string = "?%s" % urlencode(qparams) if qparams else ""
+
+ detail = ""
+ if detailed:
+ detail = "/detail"
+
+ return self._list("/consistencygroups%s%s" % (detail, query_string),
+ "consistencygroups")
+
+ def delete(self, consistencygroup, force=False):
+ """Delete a consistencygroup.
+
+ :param Consistencygroup: The :class:`Consistencygroup` to delete.
+ """
+ body = {'consistencygroup': {'force': force}}
+ self.run_hooks('modify_body_for_action', body, 'consistencygroup')
+ url = '/consistencygroups/%s/delete' % base.getid(consistencygroup)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def update(self, consistencygroup, **kwargs):
+ """Update the name or description for a consistencygroup.
+
+ :param Consistencygroup: The :class:`Consistencygroup` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"consistencygroup": kwargs}
+
+ return self._update("/consistencygroups/%s" %
+ base.getid(consistencygroup), body)
+
+ def _action(self, action, consistencygroup, info=None, **kwargs):
+ """Perform a consistencygroup "action."
+ """
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/consistencygroups/%s/action' % base.getid(consistencygroup)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v3/contrib/__init__.py b/cinderclient/v3/contrib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cinderclient/v3/contrib/__init__.py
diff --git a/cinderclient/v3/contrib/list_extensions.py b/cinderclient/v3/contrib/list_extensions.py
new file mode 100644
index 0000000..d9e17a8
--- /dev/null
+++ b/cinderclient/v3/contrib/list_extensions.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 import base
+from cinderclient import utils
+
+
+class ListExtResource(base.Resource):
+ @property
+ def summary(self):
+ descr = self.description.strip()
+ if not descr:
+ return '??'
+ lines = descr.split("\n")
+ if len(lines) == 1:
+ return lines[0]
+ else:
+ return lines[0] + "..."
+
+
+class ListExtManager(base.Manager):
+ resource_class = ListExtResource
+
+ def show_all(self):
+ return self._list("/extensions", 'extensions')
+
+
+@utils.service_type('volumev3')
+def do_list_extensions(client, _args):
+ """
+ Lists all available os-api extensions.
+ """
+ extensions = client.list_extensions.show_all()
+ fields = ["Name", "Summary", "Alias", "Updated"]
+ utils.print_list(extensions, fields)
diff --git a/cinderclient/v3/limits.py b/cinderclient/v3/limits.py
new file mode 100644
index 0000000..512a58d
--- /dev/null
+++ b/cinderclient/v3/limits.py
@@ -0,0 +1,91 @@
+# Copyright 2013 OpenStack Foundation
+#
+# 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 import base
+
+
+class Limits(base.Resource):
+ """A collection of RateLimit and AbsoluteLimit objects."""
+
+ def __repr__(self):
+ return "<Limits>"
+
+ @property
+ def absolute(self):
+ for (name, value) in list(self._info['absolute'].items()):
+ yield AbsoluteLimit(name, value)
+
+ @property
+ def rate(self):
+ for group in self._info['rate']:
+ uri = group['uri']
+ regex = group['regex']
+ for rate in group['limit']:
+ yield RateLimit(rate['verb'], uri, regex, rate['value'],
+ rate['remaining'], rate['unit'],
+ rate['next-available'])
+
+
+class RateLimit(object):
+ """Data model that represents a flattened view of a single rate limit."""
+
+ def __init__(self, verb, uri, regex, value, remain,
+ unit, next_available):
+ self.verb = verb
+ self.uri = uri
+ self.regex = regex
+ self.value = value
+ self.remain = remain
+ self.unit = unit
+ self.next_available = next_available
+
+ def __eq__(self, other):
+ return self.uri == other.uri \
+ and self.regex == other.regex \
+ and self.value == other.value \
+ and self.verb == other.verb \
+ and self.remain == other.remain \
+ and self.unit == other.unit \
+ and self.next_available == other.next_available
+
+ def __repr__(self):
+ return "<RateLimit: method=%s uri=%s>" % (self.verb, self.uri)
+
+
+class AbsoluteLimit(object):
+ """Data model that represents a single absolute limit."""
+
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+
+ def __eq__(self, other):
+ return self.value == other.value and self.name == other.name
+
+ def __repr__(self):
+ return "<AbsoluteLimit: name=%s>" % (self.name)
+
+
+class LimitsManager(base.Manager):
+ """Manager object used to interact with limits resource."""
+
+ resource_class = Limits
+
+ def get(self):
+ """Get a specific extension.
+
+ :rtype: :class:`Limits`
+ """
+ return self._get("/limits", "limits")
diff --git a/cinderclient/v3/pools.py b/cinderclient/v3/pools.py
new file mode 100644
index 0000000..2f72605
--- /dev/null
+++ b/cinderclient/v3/pools.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2015 Hewlett-Packard Development Company, L.P.
+# 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.
+
+"""Pools interface (v3 extension)"""
+
+import six
+
+from cinderclient import base
+
+
+class Pool(base.Resource):
+ NAME_ATTR = 'name'
+
+ def __repr__(self):
+ return "<Pool: %s>" % self.name
+
+
+class PoolManager(base.Manager):
+ """Manage :class:`Pool` resources."""
+ resource_class = Pool
+
+ def list(self, detailed=False):
+ """Lists all
+
+ :rtype: list of :class:`Pool`
+ """
+ if detailed is True:
+ pools = self._list("/scheduler-stats/get_pools?detail=True",
+ "pools")
+ # Other than the name, all of the pool data is buried below in
+ # a 'capabilities' dictionary. In order to be consistent with the
+ # get-pools command line, these elements are moved up a level to
+ # be attributes of the pool itself.
+ for pool in pools:
+ if hasattr(pool, 'capabilities'):
+ for k, v in six.iteritems(pool.capabilities):
+ setattr(pool, k, v)
+
+ # Remove the capabilities dictionary since all of its
+ # elements have been copied up to the containing pool
+ del pool.capabilities
+ return pools
+ else:
+ pools = self._list("/scheduler-stats/get_pools", "pools")
+
+ # avoid cluttering the basic pool list with capabilities dict
+ for pool in pools:
+ if hasattr(pool, 'capabilities'):
+ del pool.capabilities
+ return pools
diff --git a/cinderclient/v3/qos_specs.py b/cinderclient/v3/qos_specs.py
new file mode 100644
index 0000000..84b8e0a
--- /dev/null
+++ b/cinderclient/v3/qos_specs.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2013 eBay Inc.
+# Copyright (c) OpenStack LLC.
+#
+# 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.
+
+
+"""
+QoS Specs interface.
+"""
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class QoSSpecs(base.Resource):
+ """QoS specs entity represents quality-of-service parameters/requirements.
+
+ A QoS specs is a set of parameters or requirements for quality-of-service
+ purpose, which can be associated with volume types (for now). In future,
+ QoS specs may be extended to be associated other entities, such as single
+ volume.
+ """
+ def __repr__(self):
+ return "<QoSSpecs: %s>" % self.name
+
+ def delete(self):
+ return self.manager.delete(self)
+
+
+class QoSSpecsManager(base.ManagerWithFind):
+ """
+ Manage :class:`QoSSpecs` resources.
+ """
+ resource_class = QoSSpecs
+
+ def list(self, search_opts=None):
+ """Get a list of all qos specs.
+
+ :rtype: list of :class:`QoSSpecs`.
+ """
+ return self._list("/qos-specs", "qos_specs")
+
+ def get(self, qos_specs):
+ """Get a specific qos specs.
+
+ :param qos_specs: The ID of the :class:`QoSSpecs` to get.
+ :rtype: :class:`QoSSpecs`
+ """
+ return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs")
+
+ def delete(self, qos_specs, force=False):
+ """Delete a specific qos specs.
+
+ :param qos_specs: The ID of the :class:`QoSSpecs` to be removed.
+ :param force: Flag that indicates whether to delete target qos specs
+ if it was in-use.
+ """
+ return self._delete("/qos-specs/%s?force=%s" %
+ (base.getid(qos_specs), force))
+
+ def create(self, name, specs):
+ """Create a qos specs.
+
+ :param name: Descriptive name of the qos specs, must be unique
+ :param specs: A dict of key/value pairs to be set
+ :rtype: :class:`QoSSpecs`
+ """
+
+ body = {
+ "qos_specs": {
+ "name": name,
+ }
+ }
+
+ body["qos_specs"].update(specs)
+ return self._create("/qos-specs", body, "qos_specs")
+
+ def set_keys(self, qos_specs, specs):
+ """Add/Update keys in qos specs.
+
+ :param qos_specs: The ID of qos specs
+ :param specs: A dict of key/value pairs to be set
+ :rtype: :class:`QoSSpecs`
+ """
+
+ body = {
+ "qos_specs": {}
+ }
+
+ body["qos_specs"].update(specs)
+ return self._update("/qos-specs/%s" % qos_specs, body)
+
+ def unset_keys(self, qos_specs, specs):
+ """Remove keys from a qos specs.
+
+ :param qos_specs: The ID of qos specs
+ :param specs: A list of key to be unset
+ :rtype: :class:`QoSSpecs`
+ """
+
+ body = {'keys': specs}
+
+ return self._update("/qos-specs/%s/delete_keys" % qos_specs,
+ body)
+
+ def get_associations(self, qos_specs):
+ """Get associated entities of a qos specs.
+
+ :param qos_specs: The id of the :class: `QoSSpecs`
+ :return: a list of entities that associated with specific qos specs.
+ """
+ return self._list("/qos-specs/%s/associations" % base.getid(qos_specs),
+ "qos_associations")
+
+ def associate(self, qos_specs, vol_type_id):
+ """Associate a volume type with specific qos specs.
+
+ :param qos_specs: The qos specs to be associated with
+ :param vol_type_id: The volume type id to be associated with
+ """
+ resp, body = self.api.client.get(
+ "/qos-specs/%s/associate?vol_type_id=%s" %
+ (base.getid(qos_specs), vol_type_id))
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def disassociate(self, qos_specs, vol_type_id):
+ """Disassociate qos specs from volume type.
+
+ :param qos_specs: The qos specs to be associated with
+ :param vol_type_id: The volume type id to be associated with
+ """
+ resp, body = self.api.client.get(
+ "/qos-specs/%s/disassociate?vol_type_id=%s" %
+ (base.getid(qos_specs), vol_type_id))
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def disassociate_all(self, qos_specs):
+ """Disassociate all entities from specific qos specs.
+
+ :param qos_specs: The qos specs to be associated with
+ """
+ resp, body = self.api.client.get(
+ "/qos-specs/%s/disassociate_all" %
+ base.getid(qos_specs))
+ return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v3/quota_classes.py b/cinderclient/v3/quota_classes.py
new file mode 100644
index 0000000..0e5fb5b
--- /dev/null
+++ b/cinderclient/v3/quota_classes.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 import base
+
+
+class QuotaClassSet(base.Resource):
+
+ @property
+ def id(self):
+ """Needed by base.Resource to self-refresh and be indexed."""
+ return self.class_name
+
+ def update(self, *args, **kwargs):
+ return self.manager.update(self.class_name, *args, **kwargs)
+
+
+class QuotaClassSetManager(base.Manager):
+ resource_class = QuotaClassSet
+
+ def get(self, class_name):
+ return self._get("/os-quota-class-sets/%s" % (class_name),
+ "quota_class_set")
+
+ def update(self, class_name, **updates):
+ body = {'quota_class_set': {'class_name': class_name}}
+
+ for update in updates:
+ body['quota_class_set'][update] = updates[update]
+
+ result = self._update('/os-quota-class-sets/%s' % (class_name), body)
+ return self.resource_class(self,
+ result['quota_class_set'], loaded=True,
+ resp=result.request_ids)
diff --git a/cinderclient/v3/quotas.py b/cinderclient/v3/quotas.py
new file mode 100644
index 0000000..bebf32a
--- /dev/null
+++ b/cinderclient/v3/quotas.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 import base
+
+
+class QuotaSet(base.Resource):
+
+ @property
+ def id(self):
+ """Needed by base.Resource to self-refresh and be indexed."""
+ return self.tenant_id
+
+ def update(self, *args, **kwargs):
+ return self.manager.update(self.tenant_id, *args, **kwargs)
+
+
+class QuotaSetManager(base.Manager):
+ resource_class = QuotaSet
+
+ def get(self, tenant_id, usage=False):
+ if hasattr(tenant_id, 'tenant_id'):
+ tenant_id = tenant_id.tenant_id
+ return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage),
+ "quota_set")
+
+ def update(self, tenant_id, **updates):
+ body = {'quota_set': {'tenant_id': tenant_id}}
+
+ for update in updates:
+ body['quota_set'][update] = updates[update]
+
+ result = self._update('/os-quota-sets/%s' % (tenant_id), body)
+ return self.resource_class(self, result['quota_set'], loaded=True,
+ resp=result.request_ids)
+
+ def defaults(self, tenant_id):
+ return self._get('/os-quota-sets/%s/defaults' % tenant_id,
+ 'quota_set')
+
+ def delete(self, tenant_id):
+ if hasattr(tenant_id, 'tenant_id'):
+ tenant_id = tenant_id.tenant_id
+ return self._delete("/os-quota-sets/%s" % tenant_id)
diff --git a/cinderclient/v3/services.py b/cinderclient/v3/services.py
new file mode 100644
index 0000000..8cefc34
--- /dev/null
+++ b/cinderclient/v3/services.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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.
+
+"""
+service interface
+"""
+from cinderclient import base
+
+
+class Service(base.Resource):
+
+ def __repr__(self):
+ return "<Service: %s>" % self.service
+
+
+class ServiceManager(base.ManagerWithFind):
+ resource_class = Service
+
+ def list(self, host=None, binary=None):
+ """
+ Describes service list for host.
+
+ :param host: destination host name.
+ :param binary: service binary.
+ """
+ url = "/os-services"
+ filters = []
+ if host:
+ filters.append("host=%s" % host)
+ if binary:
+ filters.append("binary=%s" % binary)
+ if filters:
+ url = "%s?%s" % (url, "&".join(filters))
+ return self._list(url, "services")
+
+ def enable(self, host, binary):
+ """Enable the service specified by hostname and binary."""
+ body = {"host": host, "binary": binary}
+ result = self._update("/os-services/enable", body)
+ return self.resource_class(self, result, resp=result.request_ids)
+
+ def disable(self, host, binary):
+ """Disable the service specified by hostname and binary."""
+ body = {"host": host, "binary": binary}
+ result = self._update("/os-services/disable", body)
+ return self.resource_class(self, result, resp=result.request_ids)
+
+ def disable_log_reason(self, host, binary, reason):
+ """Disable the service with reason."""
+ body = {"host": host, "binary": binary, "disabled_reason": reason}
+ result = self._update("/os-services/disable-log-reason", body)
+ return self.resource_class(self, result, resp=result.request_ids)
+
+ def freeze_host(self, host):
+ """Freeze the service specified by hostname."""
+ body = {"host": host}
+ return self._update("/os-services/freeze", body)
+
+ def thaw_host(self, host):
+ """Thaw the service specified by hostname."""
+ body = {"host": host}
+ return self._update("/os-services/thaw", body)
+
+ def failover_host(self, host, backend_id):
+ """Failover a replicated backend by hostname."""
+ body = {"host": host, "backend_id": backend_id}
+ return self._update("/os-services/failover_host", body)
diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py
new file mode 100644
index 0000000..f28dedb
--- /dev/null
+++ b/cinderclient/v3/shell.py
@@ -0,0 +1,2655 @@
+# Copyright (c) 2013-2014 OpenStack Foundation
+# 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 __future__ import print_function
+
+import argparse
+import copy
+import os
+import sys
+import time
+
+import six
+
+from cinderclient import base
+from cinderclient import exceptions
+from cinderclient import utils
+from cinderclient.v3 import availability_zones
+from oslo_utils import strutils
+
+
+def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
+ poll_period=5, show_progress=True):
+ """Blocks while an action occurs. Periodically shows 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" % {'action': action})
+ break
+ else:
+ print_progress(progress)
+ time.sleep(poll_period)
+
+
+def _find_volume_snapshot(cs, snapshot):
+ """Gets a volume snapshot by name or ID."""
+ return utils.find_resource(cs.volume_snapshots, snapshot)
+
+
+def _find_vtype(cs, vtype):
+ """Gets a volume type by name or ID."""
+ return utils.find_resource(cs.volume_types, vtype)
+
+
+def _find_backup(cs, backup):
+ """Gets a backup by name or ID."""
+ return utils.find_resource(cs.backups, backup)
+
+
+def _find_consistencygroup(cs, consistencygroup):
+ """Gets a consistencygroup by name or ID."""
+ return utils.find_resource(cs.consistencygroups, consistencygroup)
+
+
+def _find_cgsnapshot(cs, cgsnapshot):
+ """Gets a cgsnapshot by name or ID."""
+ return utils.find_resource(cs.cgsnapshots, cgsnapshot)
+
+
+def _find_transfer(cs, transfer):
+ """Gets a transfer by name or ID."""
+ return utils.find_resource(cs.transfers, transfer)
+
+
+def _find_qos_specs(cs, qos_specs):
+ """Gets a qos specs by ID."""
+ return utils.find_resource(cs.qos_specs, qos_specs)
+
+
+def _print_volume_snapshot(snapshot):
+ utils.print_dict(snapshot._info)
+
+
+def _print_volume_image(image):
+ utils.print_dict(image[1]['os-volume_upload_image'])
+
+
+def _translate_keys(collection, convert):
+ for item in collection:
+ keys = item.__dict__
+ 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_keys(collection):
+ convert = [('volumeType', 'volume_type'),
+ ('os-vol-tenant-attr:tenant_id', 'tenant_id')]
+ _translate_keys(collection, convert)
+
+
+def _translate_volume_snapshot_keys(collection):
+ convert = [('volumeId', 'volume_id')]
+ _translate_keys(collection, convert)
+
+
+def _translate_availability_zone_keys(collection):
+ convert = [('zoneName', 'name'), ('zoneState', 'status')]
+ _translate_keys(collection, convert)
+
+
+def _extract_metadata(args):
+ metadata = {}
+ for metadatum in args.metadata:
+ # unset doesn't require a val, so we have the if/else
+ if '=' in metadatum:
+ (key, value) = metadatum.split('=', 1)
+ else:
+ key = metadatum
+ value = None
+
+ metadata[key] = value
+ return metadata
+
+
+@utils.arg('--all-tenants',
+ dest='all_tenants',
+ metavar='<0|1>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.arg('--all_tenants',
+ nargs='?',
+ type=int,
+ const=1,
+ help=argparse.SUPPRESS)
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Filters results by a name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.arg('--status',
+ metavar='<status>',
+ default=None,
+ help='Filters results by a status. Default=None.')
+@utils.arg('--bootable',
+ metavar='<True|true|False|false>',
+ const=True,
+ nargs='?',
+ choices=['True', 'true', 'False', 'false'],
+ help='Filters results by bootable status. Default=None.')
+@utils.arg('--migration_status',
+ metavar='<migration_status>',
+ default=None,
+ help='Filters results by a migration status. Default=None. '
+ 'Admin only.')
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ default=None,
+ help='Filters results by a metadata key and value pair. '
+ 'Default=None.')
+@utils.arg('--marker',
+ metavar='<marker>',
+ default=None,
+ help='Begin returning volumes that appear later in the volume '
+ 'list than that represented by this volume id. '
+ 'Default=None.')
+@utils.arg('--limit',
+ metavar='<limit>',
+ default=None,
+ help='Maximum number of volumes to return. Default=None.')
+@utils.arg('--fields',
+ default=None,
+ metavar='<fields>',
+ help='Comma-separated list of fields to display. '
+ 'Use the show command to see which fields are available. '
+ 'Unavailable/non-existent fields will be ignored. '
+ 'Default=None.')
+@utils.arg('--sort_key',
+ metavar='<sort_key>',
+ default=None,
+ help=argparse.SUPPRESS)
+@utils.arg('--sort_dir',
+ metavar='<sort_dir>',
+ default=None,
+ help=argparse.SUPPRESS)
+@utils.arg('--sort',
+ metavar='<key>[:<direction>]',
+ default=None,
+ help=(('Comma-separated list of sort keys and directions in the '
+ 'form of <key>[:<asc|desc>]. '
+ 'Valid keys: %s. '
+ 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
+@utils.arg('--tenant',
+ type=str,
+ dest='tenant',
+ nargs='?',
+ metavar='<tenant>',
+ help='Display information from single tenant (Admin only).')
+@utils.service_type('volumev3')
+def do_list(cs, args):
+ """Lists all volumes."""
+ # NOTE(thingee): Backwards-compatibility with v1 args
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ all_tenants = 1 if args.tenant else \
+ int(os.environ.get("ALL_TENANTS", args.all_tenants))
+ search_opts = {
+ 'all_tenants': all_tenants,
+ 'project_id': args.tenant,
+ 'name': args.name,
+ 'status': args.status,
+ 'bootable': args.bootable,
+ 'migration_status': args.migration_status,
+ 'metadata': _extract_metadata(args) if args.metadata else None,
+ }
+
+ # If unavailable/non-existent fields are specified, these fields will
+ # be removed from key_list at the print_list() during key validation.
+ field_titles = []
+ if args.fields:
+ for field_title in args.fields.split(','):
+ field_titles.append(field_title)
+
+ # --sort_key and --sort_dir deprecated in kilo and is not supported
+ # with --sort
+ if args.sort and (args.sort_key or args.sort_dir):
+ raise exceptions.CommandError(
+ 'The --sort_key and --sort_dir arguments are deprecated and are '
+ 'not supported with --sort.')
+
+ volumes = cs.volumes.list(search_opts=search_opts, marker=args.marker,
+ limit=args.limit, sort_key=args.sort_key,
+ sort_dir=args.sort_dir, sort=args.sort)
+ _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)))
+
+ if field_titles:
+ key_list = ['ID'] + field_titles
+ else:
+ key_list = ['ID', 'Status', 'Name', 'Size', 'Volume Type',
+ 'Bootable', 'Attached to']
+ # If all_tenants is specified, print
+ # Tenant ID as well.
+ if search_opts['all_tenants']:
+ key_list.insert(1, 'Tenant ID')
+
+ if args.sort_key or args.sort_dir or args.sort:
+ sortby_index = None
+ else:
+ sortby_index = 0
+ utils.print_list(volumes, key_list, exclude_unavailable=True,
+ sortby_index=sortby_index)
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume.')
+@utils.service_type('volumev3')
+def do_show(cs, args):
+ """Shows volume details."""
+ info = dict()
+ volume = utils.find_volume(cs, args.volume)
+ info.update(volume._info)
+
+ info.pop('links', None)
+ utils.print_dict(info,
+ formatters=['metadata', 'volume_image_metadata'])
+
+
+class CheckSizeArgForCreate(argparse.Action):
+ def __call__(self, parser, args, values, option_string=None):
+ if ((args.snapshot_id or args.source_volid or args.source_replica)
+ is None and values is None):
+ parser.error('Size is a required parameter if snapshot '
+ 'or source volume is not specified.')
+ setattr(args, self.dest, values)
+
+
+@utils.arg('size',
+ metavar='<size>',
+ nargs='?',
+ type=int,
+ action=CheckSizeArgForCreate,
+ help='Size of volume, in GiBs. (Required unless '
+ 'snapshot-id/source-volid is specified).')
+@utils.arg('--consisgroup-id',
+ metavar='<consistencygroup-id>',
+ default=None,
+ help='ID of a consistency group where the new volume belongs to. '
+ 'Default=None.')
+@utils.arg('--snapshot-id',
+ metavar='<snapshot-id>',
+ default=None,
+ help='Creates volume from snapshot ID. Default=None.')
+@utils.arg('--snapshot_id',
+ help=argparse.SUPPRESS)
+@utils.arg('--source-volid',
+ metavar='<source-volid>',
+ default=None,
+ help='Creates volume from volume ID. Default=None.')
+@utils.arg('--source_volid',
+ help=argparse.SUPPRESS)
+@utils.arg('--source-replica',
+ metavar='<source-replica>',
+ default=None,
+ help='Creates volume from replicated volume ID. Default=None.')
+@utils.arg('--image-id',
+ metavar='<image-id>',
+ default=None,
+ help='Creates volume from image ID. Default=None.')
+@utils.arg('--image_id',
+ help=argparse.SUPPRESS)
+@utils.arg('--image',
+ metavar='<image>',
+ default=None,
+ help='Creates a volume from image (ID or name). Default=None.')
+@utils.arg('--image_ref',
+ help=argparse.SUPPRESS)
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Volume name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_name',
+ help=argparse.SUPPRESS)
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Volume description. Default=None.')
+@utils.arg('--display-description',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_description',
+ help=argparse.SUPPRESS)
+@utils.arg('--volume-type',
+ metavar='<volume-type>',
+ default=None,
+ help='Volume type. Default=None.')
+@utils.arg('--volume_type',
+ help=argparse.SUPPRESS)
+@utils.arg('--availability-zone',
+ metavar='<availability-zone>',
+ default=None,
+ help='Availability zone for volume. Default=None.')
+@utils.arg('--availability_zone',
+ help=argparse.SUPPRESS)
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ default=None,
+ help='Metadata key and value pairs. Default=None.')
+@utils.arg('--hint',
+ metavar='<key=value>',
+ dest='scheduler_hints',
+ action='append',
+ default=[],
+ help='Scheduler hint, like in nova.')
+@utils.arg('--allow-multiattach',
+ dest='multiattach',
+ action="store_true",
+ help=('Allow volume to be attached more than once.'
+ ' Default=False'),
+ default=False)
+@utils.service_type('volumev3')
+def do_create(cs, args):
+ """Creates a volume."""
+ # NOTE(thingee): Backwards-compatibility with v1 args
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ if args.display_description is not None:
+ args.description = args.display_description
+
+ volume_metadata = None
+ if args.metadata is not None:
+ volume_metadata = _extract_metadata(args)
+
+ # NOTE(N.S.): take this piece from novaclient
+ hints = {}
+ if args.scheduler_hints:
+ for hint in args.scheduler_hints:
+ key, _sep, value = hint.partition('=')
+ # NOTE(vish): multiple copies of same hint will
+ # result in a list of values
+ if key in hints:
+ if isinstance(hints[key], six.string_types):
+ hints[key] = [hints[key]]
+ hints[key] += [value]
+ else:
+ hints[key] = value
+ # NOTE(N.S.): end of taken piece
+
+ # Keep backward compatibility with image_id, favoring explicit ID
+ image_ref = args.image_id or args.image or args.image_ref
+
+ volume = cs.volumes.create(args.size,
+ args.consisgroup_id,
+ args.snapshot_id,
+ args.source_volid,
+ args.name,
+ args.description,
+ args.volume_type,
+ availability_zone=args.availability_zone,
+ imageRef=image_ref,
+ metadata=volume_metadata,
+ scheduler_hints=hints,
+ source_replica=args.source_replica,
+ multiattach=args.multiattach)
+
+ info = dict()
+ volume = cs.volumes.get(volume.id)
+ info.update(volume._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('--cascade',
+ metavar='<cascade>',
+ default=False,
+ const=True,
+ nargs='?',
+ help='Remove any snapshots along with volume. Default=False.')
+@utils.arg('volume',
+ metavar='<volume>', nargs='+',
+ help='Name or ID of volume or volumes to delete.')
+@utils.service_type('volumev3')
+def do_delete(cs, args):
+ """Removes one or more volumes."""
+ failure_count = 0
+ for volume in args.volume:
+ try:
+ utils.find_volume(cs, volume).delete(cascade=args.cascade)
+ print("Request to delete volume %s has been accepted." % (volume))
+ except Exception as e:
+ failure_count += 1
+ print("Delete for volume %s failed: %s" % (volume, e))
+ if failure_count == len(args.volume):
+ raise exceptions.CommandError("Unable to delete any of the specified "
+ "volumes.")
+
+
+@utils.arg('volume',
+ metavar='<volume>', nargs='+',
+ help='Name or ID of volume or volumes to delete.')
+@utils.service_type('volumev3')
+def do_force_delete(cs, args):
+ """Attempts force-delete of volume, regardless of state."""
+ failure_count = 0
+ for volume in args.volume:
+ try:
+ utils.find_volume(cs, volume).force_delete()
+ except Exception as e:
+ failure_count += 1
+ print("Delete for volume %s failed: %s" % (volume, e))
+ if failure_count == len(args.volume):
+ raise exceptions.CommandError("Unable to force delete any of the "
+ "specified volumes.")
+
+
+@utils.arg('volume', metavar='<volume>', nargs='+',
+ help='Name or ID of volume to modify.')
+@utils.arg('--state', metavar='<state>', default='available',
+ help=('The state to assign to the volume. Valid values are '
+ '"available", "error", "creating", "deleting", "in-use", '
+ '"attaching", "detaching", "error_deleting" and '
+ '"maintenance". '
+ 'NOTE: This command simply changes the state of the '
+ 'Volume in the DataBase with no regard to actual status, '
+ 'exercise caution when using. Default=available.'))
+@utils.arg('--attach-status', metavar='<attach-status>', default=None,
+ help=('The attach status to assign to the volume in the DataBase, '
+ 'with no regard to the actual status. Valid values are '
+ '"attached" and "detached". Default=None, that means the '
+ 'status is unchanged.'))
+@utils.arg('--reset-migration-status',
+ action='store_true',
+ help=('Clears the migration status of the volume in the DataBase '
+ 'that indicates the volume is source or destination of '
+ 'volume migration, with no regard to the actual status.'))
+@utils.service_type('volumev3')
+def do_reset_state(cs, args):
+ """Explicitly updates the volume state in the Cinder database.
+
+ Note that this does not affect whether the volume is actually attached to
+ the Nova compute host or instance and can result in an unusable volume.
+ Being a database change only, this has no impact on the true state of the
+ volume and may not match the actual state. This can render a volume
+ unusable in the case of change to the 'available' state.
+ """
+ failure_flag = False
+ migration_status = 'none' if args.reset_migration_status else None
+
+ for volume in args.volume:
+ try:
+ utils.find_volume(cs, volume).reset_state(args.state,
+ args.attach_status,
+ migration_status)
+ except Exception as e:
+ failure_flag = True
+ msg = "Reset state for volume %s failed: %s" % (volume, e)
+ print(msg)
+
+ if failure_flag:
+ msg = "Unable to reset the state for the specified volume(s)."
+ raise exceptions.CommandError(msg)
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume to rename.')
+@utils.arg('name',
+ nargs='?',
+ metavar='<name>',
+ help='New name for volume.')
+@utils.arg('--description', metavar='<description>',
+ help='Volume description. Default=None.',
+ default=None)
+@utils.arg('--display-description',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_description',
+ help=argparse.SUPPRESS)
+@utils.service_type('volumev3')
+def do_rename(cs, args):
+ """Renames a volume."""
+ kwargs = {}
+
+ if args.name is not None:
+ kwargs['name'] = args.name
+ if args.display_description is not None:
+ kwargs['description'] = args.display_description
+ elif args.description is not None:
+ kwargs['description'] = args.description
+
+ if not any(kwargs):
+ msg = 'Must supply either name or description.'
+ raise exceptions.ClientException(code=1, message=msg)
+
+ utils.find_volume(cs, args.volume).update(**kwargs)
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume for which to update metadata.')
+@utils.arg('action',
+ metavar='<action>',
+ choices=['set', 'unset'],
+ help='The action. Valid values are "set" or "unset."')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair to set or unset. '
+ 'For unset, specify only the key.')
+@utils.service_type('volumev3')
+def do_metadata(cs, args):
+ """Sets or deletes volume metadata."""
+ volume = utils.find_volume(cs, args.volume)
+ metadata = _extract_metadata(args)
+
+ if args.action == 'set':
+ cs.volumes.set_metadata(volume, metadata)
+ elif args.action == 'unset':
+ # NOTE(zul): Make sure py2/py3 sorting is the same
+ cs.volumes.delete_metadata(volume, sorted(metadata.keys(),
+ reverse=True))
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume for which to update metadata.')
+@utils.arg('action',
+ metavar='<action>',
+ choices=['set', 'unset'],
+ help="The action. Valid values are 'set' or 'unset.'")
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair to set or unset. '
+ 'For unset, specify only the key.')
+@utils.service_type('volumev3')
+def do_image_metadata(cs, args):
+ """Sets or deletes volume image metadata."""
+ volume = utils.find_volume(cs, args.volume)
+ metadata = _extract_metadata(args)
+
+ if args.action == 'set':
+ cs.volumes.set_image_metadata(volume, metadata)
+ elif args.action == 'unset':
+ cs.volumes.delete_image_metadata(volume, sorted(metadata.keys(),
+ reverse=True))
+
+
+@utils.arg('--all-tenants',
+ dest='all_tenants',
+ metavar='<0|1>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.arg('--all_tenants',
+ nargs='?',
+ type=int,
+ const=1,
+ help=argparse.SUPPRESS)
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Filters results by a name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_name',
+ help=argparse.SUPPRESS)
+@utils.arg('--status',
+ metavar='<status>',
+ default=None,
+ help='Filters results by a status. Default=None.')
+@utils.arg('--volume-id',
+ metavar='<volume-id>',
+ default=None,
+ help='Filters results by a volume ID. Default=None.')
+@utils.arg('--volume_id',
+ help=argparse.SUPPRESS)
+@utils.arg('--marker',
+ metavar='<marker>',
+ default=None,
+ help='Begin returning snapshots that appear later in the snapshot '
+ 'list than that represented by this id. '
+ 'Default=None.')
+@utils.arg('--limit',
+ metavar='<limit>',
+ default=None,
+ help='Maximum number of snapshots to return. Default=None.')
+@utils.arg('--sort',
+ metavar='<key>[:<direction>]',
+ default=None,
+ help=(('Comma-separated list of sort keys and directions in the '
+ 'form of <key>[:<asc|desc>]. '
+ 'Valid keys: %s. '
+ 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
+@utils.arg('--tenant',
+ type=str,
+ dest='tenant',
+ nargs='?',
+ metavar='<tenant>',
+ help='Display information from single tenant (Admin only).')
+@utils.service_type('volumev3')
+def do_snapshot_list(cs, args):
+ """Lists all snapshots."""
+ all_tenants = (1 if args.tenant else
+ int(os.environ.get("ALL_TENANTS", args.all_tenants)))
+
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ search_opts = {
+ 'all_tenants': all_tenants,
+ 'display_name': args.name,
+ 'status': args.status,
+ 'volume_id': args.volume_id,
+ 'project_id': args.tenant,
+ }
+
+ snapshots = cs.volume_snapshots.list(search_opts=search_opts,
+ marker=args.marker,
+ limit=args.limit,
+ sort=args.sort)
+ _translate_volume_snapshot_keys(snapshots)
+ if args.sort:
+ sortby_index = None
+ else:
+ sortby_index = 0
+
+ utils.print_list(snapshots,
+ ['ID', 'Volume ID', 'Status', 'Name', 'Size'],
+ sortby_index=sortby_index)
+
+
+@utils.arg('snapshot',
+ metavar='<snapshot>',
+ help='Name or ID of snapshot.')
+@utils.service_type('volumev3')
+def do_snapshot_show(cs, args):
+ """Shows snapshot details."""
+ snapshot = _find_volume_snapshot(cs, args.snapshot)
+ _print_volume_snapshot(snapshot)
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume to snapshot.')
+@utils.arg('--force',
+ metavar='<True|False>',
+ const=True,
+ nargs='?',
+ default=False,
+ help='Allows or disallows snapshot of '
+ 'a volume when the volume is attached to an instance. '
+ 'If set to True, ignores the current status of the '
+ 'volume when attempting to snapshot it rather '
+ 'than forcing it to be available. '
+ 'Default=False.')
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Snapshot name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_name',
+ help=argparse.SUPPRESS)
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Snapshot description. Default=None.')
+@utils.arg('--display-description',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_description',
+ help=argparse.SUPPRESS)
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ default=None,
+ help='Snapshot metadata key and value pairs. Default=None.')
+@utils.service_type('volumev3')
+def do_snapshot_create(cs, args):
+ """Creates a snapshot."""
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ if args.display_description is not None:
+ args.description = args.display_description
+
+ snapshot_metadata = None
+ if args.metadata is not None:
+ snapshot_metadata = _extract_metadata(args)
+
+ volume = utils.find_volume(cs, args.volume)
+ snapshot = cs.volume_snapshots.create(volume.id,
+ args.force,
+ args.name,
+ args.description,
+ metadata=snapshot_metadata)
+ _print_volume_snapshot(snapshot)
+
+
+@utils.arg('snapshot',
+ metavar='<snapshot>', nargs='+',
+ help='Name or ID of the snapshot(s) to delete.')
+@utils.service_type('volumev3')
+def do_snapshot_delete(cs, args):
+ """Removes one or more snapshots."""
+ failure_count = 0
+ for snapshot in args.snapshot:
+ try:
+ _find_volume_snapshot(cs, snapshot).delete()
+ except Exception as e:
+ failure_count += 1
+ print("Delete for snapshot %s failed: %s" % (snapshot, e))
+ if failure_count == len(args.snapshot):
+ raise exceptions.CommandError("Unable to delete any of the specified "
+ "snapshots.")
+
+
+@utils.arg('snapshot', metavar='<snapshot>',
+ help='Name or ID of snapshot.')
+@utils.arg('name', nargs='?', metavar='<name>',
+ help='New name for snapshot.')
+@utils.arg('--description', metavar='<description>',
+ default=None,
+ help='Snapshot description. Default=None.')
+@utils.arg('--display-description',
+ help=argparse.SUPPRESS)
+@utils.arg('--display_description',
+ help=argparse.SUPPRESS)
+@utils.service_type('volumev3')
+def do_snapshot_rename(cs, args):
+ """Renames a snapshot."""
+ kwargs = {}
+
+ if args.name is not None:
+ kwargs['name'] = args.name
+
+ if args.description is not None:
+ kwargs['description'] = args.description
+ elif args.display_description is not None:
+ kwargs['description'] = args.display_description
+
+ if not any(kwargs):
+ msg = 'Must supply either name or description.'
+ raise exceptions.ClientException(code=1, message=msg)
+
+ _find_volume_snapshot(cs, args.snapshot).update(**kwargs)
+
+
+@utils.arg('snapshot', metavar='<snapshot>', nargs='+',
+ help='Name or ID of snapshot to modify.')
+@utils.arg('--state', metavar='<state>',
+ default='available',
+ help=('The state to assign to the snapshot. Valid values are '
+ '"available", "error", "creating", "deleting", and '
+ '"error_deleting". NOTE: This command simply changes '
+ 'the state of the Snapshot in the DataBase with no regard '
+ 'to actual status, exercise caution when using. '
+ 'Default=available.'))
+@utils.service_type('volumev3')
+def do_snapshot_reset_state(cs, args):
+ """Explicitly updates the snapshot state."""
+ failure_count = 0
+
+ single = (len(args.snapshot) == 1)
+
+ for snapshot in args.snapshot:
+ try:
+ _find_volume_snapshot(cs, snapshot).reset_state(args.state)
+ except Exception as e:
+ failure_count += 1
+ msg = "Reset state for snapshot %s failed: %s" % (snapshot, e)
+ if not single:
+ print(msg)
+
+ if failure_count == len(args.snapshot):
+ if not single:
+ msg = ("Unable to reset the state for any of the specified "
+ "snapshots.")
+ raise exceptions.CommandError(msg)
+
+
+def _print_volume_type_list(vtypes):
+ utils.print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public'])
+
+
+@utils.service_type('volumev3')
+def do_type_list(cs, args):
+ """Lists available 'volume types'. (Admin only will see private types)"""
+ vtypes = cs.volume_types.list()
+ _print_volume_type_list(vtypes)
+
+
+@utils.service_type('volumev3')
+def do_type_default(cs, args):
+ """List the default volume type."""
+ vtype = cs.volume_types.default()
+ _print_volume_type_list([vtype])
+
+
+@utils.arg('volume_type',
+ metavar='<volume_type>',
+ help='Name or ID of the volume type.')
+@utils.service_type('volumev3')
+def do_type_show(cs, args):
+ """Show volume type details."""
+ vtype = _find_vtype(cs, args.volume_type)
+ info = dict()
+ info.update(vtype._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('id',
+ metavar='<id>',
+ help='ID of the volume type.')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Name of the volume type.')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Description of the volume type.')
+@utils.arg('--is-public',
+ metavar='<is-public>',
+ help='Make type accessible to the public or not.')
+@utils.service_type('volumev3')
+def do_type_update(cs, args):
+ """Updates volume type name, description, and/or is_public."""
+ is_public = strutils.bool_from_string(args.is_public)
+ vtype = cs.volume_types.update(args.id, args.name, args.description,
+ is_public)
+ _print_volume_type_list([vtype])
+
+
+@utils.service_type('volumev3')
+def do_extra_specs_list(cs, args):
+ """Lists current volume types and extra specs."""
+ vtypes = cs.volume_types.list()
+ utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'])
+
+
+@utils.arg('name',
+ metavar='<name>',
+ help='Name of new volume type.')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Description of new volume type.')
+@utils.arg('--is-public',
+ metavar='<is-public>',
+ default=True,
+ help='Make type accessible to the public (default true).')
+@utils.service_type('volumev3')
+def do_type_create(cs, args):
+ """Creates a volume type."""
+ is_public = strutils.bool_from_string(args.is_public)
+ vtype = cs.volume_types.create(args.name, args.description, is_public)
+ _print_volume_type_list([vtype])
+
+
+@utils.arg('vol_type',
+ metavar='<vol_type>', nargs='+',
+ help='Name or ID of volume type or types to delete.')
+@utils.service_type('volumev3')
+def do_type_delete(cs, args):
+ """Deletes volume type or types."""
+ failure_count = 0
+ for vol_type in args.vol_type:
+ try:
+ vtype = _find_volume_type(cs, vol_type)
+ cs.volume_types.delete(vtype)
+ print("Request to delete volume type %s has been accepted."
+ % (vol_type))
+ except Exception as e:
+ failure_count += 1
+ print("Delete for volume type %s failed: %s" % (vol_type, e))
+ if failure_count == len(args.vol_type):
+ raise exceptions.CommandError("Unable to delete any of the "
+ "specified types.")
+
+
+@utils.arg('vtype',
+ metavar='<vtype>',
+ help='Name or ID of volume type.')
+@utils.arg('action',
+ metavar='<action>',
+ choices=['set', 'unset'],
+ help='The action. Valid values are "set" or "unset."')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='The extra specs key and value pair to set or unset. '
+ 'For unset, specify only the key.')
+@utils.service_type('volumev3')
+def do_type_key(cs, args):
+ """Sets or unsets extra_spec for a volume type."""
+ vtype = _find_volume_type(cs, args.vtype)
+ keypair = _extract_metadata(args)
+
+ if args.action == 'set':
+ vtype.set_keys(keypair)
+ elif args.action == 'unset':
+ vtype.unset_keys(list(keypair))
+
+
+@utils.arg('--volume-type', metavar='<volume_type>', required=True,
+ help='Filter results by volume type name or ID.')
+@utils.service_type('volumev3')
+def do_type_access_list(cs, args):
+ """Print access information about the given volume type."""
+ volume_type = _find_volume_type(cs, args.volume_type)
+ if volume_type.is_public:
+ raise exceptions.CommandError("Failed to get access list "
+ "for public volume type.")
+ access_list = cs.volume_type_access.list(volume_type)
+
+ columns = ['Volume_type_ID', 'Project_ID']
+ utils.print_list(access_list, columns)
+
+
+@utils.arg('--volume-type', metavar='<volume_type>', required=True,
+ help='Volume type name or ID to add access for the given project.')
+@utils.arg('--project-id', metavar='<project_id>', required=True,
+ help='Project ID to add volume type access for.')
+@utils.service_type('volumev3')
+def do_type_access_add(cs, args):
+ """Adds volume type access for the given project."""
+ vtype = _find_volume_type(cs, args.volume_type)
+ cs.volume_type_access.add_project_access(vtype, args.project_id)
+
+
+@utils.arg('--volume-type', metavar='<volume_type>', required=True,
+ help=('Volume type name or ID to remove access '
+ 'for the given project.'))
+@utils.arg('--project-id', metavar='<project_id>', required=True,
+ help='Project ID to remove volume type access for.')
+@utils.service_type('volumev3')
+def do_type_access_remove(cs, args):
+ """Removes volume type access for the given project."""
+ vtype = _find_volume_type(cs, args.volume_type)
+ cs.volume_type_access.remove_project_access(
+ vtype, args.project_id)
+
+
+@utils.service_type('volumev3')
+def do_endpoints(cs, args):
+ """Discovers endpoints registered by authentication service."""
+ catalog = cs.client.service_catalog.catalog
+ for e in catalog['serviceCatalog']:
+ utils.print_dict(e['endpoints'][0], e['name'])
+
+
+@utils.service_type('volumev3')
+def do_credentials(cs, args):
+ """Shows user credentials returned from auth."""
+ catalog = cs.client.service_catalog.catalog
+
+ # formatters defines field to be converted from unicode to string
+ utils.print_dict(catalog['user'], "User Credentials",
+ formatters=['domain', 'roles'])
+ utils.print_dict(catalog['token'], "Token",
+ formatters=['audit_ids', 'tenant'])
+
+_quota_resources = ['volumes', 'snapshots', 'gigabytes',
+ 'backups', 'backup_gigabytes',
+ 'consistencygroups', 'per_volume_gigabytes']
+_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
+
+
+def _quota_show(quotas):
+ quota_dict = {}
+ for resource in quotas._info:
+ good_name = False
+ for name in _quota_resources:
+ if resource.startswith(name):
+ good_name = True
+ if not good_name:
+ continue
+ quota_dict[resource] = getattr(quotas, resource, None)
+ utils.print_dict(quota_dict)
+
+
+def _quota_usage_show(quotas):
+ quota_list = []
+ for resource in quotas._info.keys():
+ good_name = False
+ for name in _quota_resources:
+ if resource.startswith(name):
+ good_name = True
+ if not good_name:
+ continue
+ quota_info = getattr(quotas, resource, None)
+ quota_info['Type'] = resource
+ quota_info = dict((k.capitalize(), v) for k, v in quota_info.items())
+ quota_list.append(quota_info)
+ utils.print_list(quota_list, _quota_infos)
+
+
+def _quota_update(manager, identifier, args):
+ updates = {}
+ for resource in _quota_resources:
+ val = getattr(args, resource, None)
+ if val is not None:
+ if args.volume_type:
+ resource = resource + '_%s' % args.volume_type
+ updates[resource] = val
+
+ if updates:
+ _quota_show(manager.update(identifier, **updates))
+
+
+@utils.arg('tenant',
+ metavar='<tenant_id>',
+ help='ID of tenant for which to list quotas.')
+@utils.service_type('volumev3')
+def do_quota_show(cs, args):
+ """Lists quotas for a tenant."""
+
+ _quota_show(cs.quotas.get(args.tenant))
+
+
+@utils.arg('tenant', metavar='<tenant_id>',
+ help='ID of tenant for which to list quota usage.')
+@utils.service_type('volumev3')
+def do_quota_usage(cs, args):
+ """Lists quota usage for a tenant."""
+
+ _quota_usage_show(cs.quotas.get(args.tenant, usage=True))
+
+
+@utils.arg('tenant',
+ metavar='<tenant_id>',
+ help='ID of tenant for which to list quota defaults.')
+@utils.service_type('volumev3')
+def do_quota_defaults(cs, args):
+ """Lists default quotas for a tenant."""
+
+ _quota_show(cs.quotas.defaults(args.tenant))
+
+
+@utils.arg('tenant',
+ metavar='<tenant_id>',
+ help='ID of tenant for which to set quotas.')
+@utils.arg('--volumes',
+ metavar='<volumes>',
+ type=int, default=None,
+ help='The new "volumes" quota value. Default=None.')
+@utils.arg('--snapshots',
+ metavar='<snapshots>',
+ type=int, default=None,
+ help='The new "snapshots" quota value. Default=None.')
+@utils.arg('--gigabytes',
+ metavar='<gigabytes>',
+ type=int, default=None,
+ help='The new "gigabytes" quota value. Default=None.')
+@utils.arg('--backups',
+ metavar='<backups>',
+ type=int, default=None,
+ help='The new "backups" quota value. Default=None.')
+@utils.arg('--backup-gigabytes',
+ metavar='<backup_gigabytes>',
+ type=int, default=None,
+ help='The new "backup_gigabytes" quota value. Default=None.')
+@utils.arg('--consistencygroups',
+ metavar='<consistencygroups>',
+ type=int, default=None,
+ help='The new "consistencygroups" quota value. Default=None.')
+@utils.arg('--volume-type',
+ metavar='<volume_type_name>',
+ default=None,
+ help='Volume type. Default=None.')
+@utils.arg('--per-volume-gigabytes',
+ metavar='<per_volume_gigabytes>',
+ type=int, default=None,
+ help='Set max volume size limit. Default=None.')
+@utils.service_type('volumev3')
+def do_quota_update(cs, args):
+ """Updates quotas for a tenant."""
+
+ _quota_update(cs.quotas, args.tenant, args)
+
+
+@utils.arg('tenant', metavar='<tenant_id>',
+ help='UUID of tenant to delete the quotas for.')
+@utils.service_type('volumev3')
+def do_quota_delete(cs, args):
+ """Delete the quotas for a tenant."""
+
+ cs.quotas.delete(args.tenant)
+
+
+@utils.arg('class_name',
+ metavar='<class>',
+ help='Name of quota class for which to list quotas.')
+@utils.service_type('volumev3')
+def do_quota_class_show(cs, args):
+ """Lists quotas for a quota class."""
+
+ _quota_show(cs.quota_classes.get(args.class_name))
+
+
+@utils.arg('class_name',
+ metavar='<class_name>',
+ help='Name of quota class for which to set quotas.')
+@utils.arg('--volumes',
+ metavar='<volumes>',
+ type=int, default=None,
+ help='The new "volumes" quota value. Default=None.')
+@utils.arg('--snapshots',
+ metavar='<snapshots>',
+ type=int, default=None,
+ help='The new "snapshots" quota value. Default=None.')
+@utils.arg('--gigabytes',
+ metavar='<gigabytes>',
+ type=int, default=None,
+ help='The new "gigabytes" quota value. Default=None.')
+@utils.arg('--volume-type',
+ metavar='<volume_type_name>',
+ default=None,
+ help='Volume type. Default=None.')
+@utils.service_type('volumev3')
+def do_quota_class_update(cs, args):
+ """Updates quotas for a quota class."""
+
+ _quota_update(cs.quota_classes, args.class_name, args)
+
+
+@utils.service_type('volumev3')
+def do_absolute_limits(cs, args):
+ """Lists absolute limits for a user."""
+ limits = cs.limits.get().absolute
+ columns = ['Name', 'Value']
+ utils.print_list(limits, columns)
+
+
+@utils.service_type('volumev3')
+def do_rate_limits(cs, args):
+ """Lists rate limits for a user."""
+ limits = cs.limits.get().rate
+ columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
+ utils.print_list(limits, columns)
+
+
+def _find_volume_type(cs, vtype):
+ """Gets a volume type by name or ID."""
+ return utils.find_resource(cs.volume_types, vtype)
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Name or ID of volume to snapshot.')
+@utils.arg('--force',
+ metavar='<True|False>',
+ const=True,
+ nargs='?',
+ default=False,
+ help='Enables or disables upload of '
+ 'a volume that is attached to an instance. '
+ 'Default=False.')
+@utils.arg('--container-format',
+ metavar='<container-format>',
+ default='bare',
+ help='Container format type. '
+ 'Default is bare.')
+@utils.arg('--container_format',
+ help=argparse.SUPPRESS)
+@utils.arg('--disk-format',
+ metavar='<disk-format>',
+ default='raw',
+ help='Disk format type. '
+ 'Default is raw.')
+@utils.arg('--disk_format',
+ help=argparse.SUPPRESS)
+@utils.arg('image_name',
+ metavar='<image-name>',
+ help='The new image name.')
+@utils.arg('--image_name',
+ help=argparse.SUPPRESS)
+@utils.service_type('volumev3')
+def do_upload_to_image(cs, args):
+ """Uploads volume to Image Service as an image."""
+ volume = utils.find_volume(cs, args.volume)
+ _print_volume_image(volume.upload_to_image(args.force,
+ args.image_name,
+ args.container_format,
+ args.disk_format))
+
+
+@utils.arg('volume', metavar='<volume>', help='ID of volume to migrate.')
+@utils.arg('host', metavar='<host>', help='Destination host. Takes the form: '
+ 'host@backend-name#pool')
+@utils.arg('--force-host-copy', metavar='<True|False>',
+ choices=['True', 'False'],
+ required=False,
+ const=True,
+ nargs='?',
+ default=False,
+ help='Enables or disables generic host-based '
+ 'force-migration, which bypasses driver '
+ 'optimizations. Default=False.')
+@utils.arg('--lock-volume', metavar='<True|False>',
+ choices=['True', 'False'],
+ required=False,
+ const=True,
+ nargs='?',
+ default=False,
+ help='Enables or disables the termination of volume migration '
+ 'caused by other commands. This option applies to the '
+ 'available volume. True means it locks the volume '
+ 'state and does not allow the migration to be aborted. The '
+ 'volume status will be in maintenance during the '
+ 'migration. False means it allows the volume migration '
+ 'to be aborted. The volume status is still in the original '
+ 'status. Default=False.')
+@utils.service_type('volumev3')
+def do_migrate(cs, args):
+ """Migrates volume to a new host."""
+ volume = utils.find_volume(cs, args.volume)
+ try:
+ volume.migrate_volume(args.host, args.force_host_copy,
+ args.lock_volume)
+ print("Request to migrate volume %s has been accepted." % (volume))
+ except Exception as e:
+ print("Migration for volume %s failed: %s." % (volume,
+ six.text_type(e)))
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of volume for which to modify type.')
+@utils.arg('new_type', metavar='<volume-type>', help='New volume type.')
+@utils.arg('--migration-policy', metavar='<never|on-demand>', required=False,
+ choices=['never', 'on-demand'], default='never',
+ help='Migration policy during retype of volume.')
+@utils.service_type('volumev3')
+def do_retype(cs, args):
+ """Changes the volume type for a volume."""
+ volume = utils.find_volume(cs, args.volume)
+ volume.retype(args.new_type, args.migration_policy)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of volume to backup.')
+@utils.arg('--container', metavar='<container>',
+ default=None,
+ help='Backup container name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.arg('--name', metavar='<name>',
+ default=None,
+ help='Backup name. Default=None.')
+@utils.arg('--display-description',
+ help=argparse.SUPPRESS)
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Backup description. Default=None.')
+@utils.arg('--incremental',
+ action='store_true',
+ help='Incremental backup. Default=False.',
+ default=False)
+@utils.arg('--force',
+ action='store_true',
+ help='Allows or disallows backup of a volume '
+ 'when the volume is attached to an instance. '
+ 'If set to True, backs up the volume whether '
+ 'its status is "available" or "in-use". The backup '
+ 'of an "in-use" volume means your data is crash '
+ 'consistent. Default=False.',
+ default=False)
+@utils.arg('--snapshot-id',
+ metavar='<snapshot-id>',
+ default=None,
+ help='ID of snapshot to backup. Default=None.')
+@utils.service_type('volumev3')
+def do_backup_create(cs, args):
+ """Creates a volume backup."""
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ if args.display_description is not None:
+ args.description = args.display_description
+
+ volume = utils.find_volume(cs, args.volume)
+ backup = cs.backups.create(volume.id,
+ args.container,
+ args.name,
+ args.description,
+ args.incremental,
+ args.force,
+ args.snapshot_id)
+
+ info = {"volume_id": volume.id}
+ info.update(backup._info)
+
+ if 'links' in info:
+ info.pop('links')
+
+ utils.print_dict(info)
+
+
+@utils.arg('backup', metavar='<backup>', help='Name or ID of backup.')
+@utils.service_type('volumev3')
+def do_backup_show(cs, args):
+ """Shows backup details."""
+ backup = _find_backup(cs, args.backup)
+ info = dict()
+ info.update(backup._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('--all-tenants',
+ metavar='<all_tenants>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.arg('--all_tenants',
+ nargs='?',
+ type=int,
+ const=1,
+ help=argparse.SUPPRESS)
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Filters results by a name. Default=None.')
+@utils.arg('--status',
+ metavar='<status>',
+ default=None,
+ help='Filters results by a status. Default=None.')
+@utils.arg('--volume-id',
+ metavar='<volume-id>',
+ default=None,
+ help='Filters results by a volume ID. Default=None.')
+@utils.arg('--volume_id',
+ help=argparse.SUPPRESS)
+@utils.arg('--marker',
+ metavar='<marker>',
+ default=None,
+ help='Begin returning backups that appear later in the backup '
+ 'list than that represented by this id. '
+ 'Default=None.')
+@utils.arg('--limit',
+ metavar='<limit>',
+ default=None,
+ help='Maximum number of backups to return. Default=None.')
+@utils.arg('--sort',
+ metavar='<key>[:<direction>]',
+ default=None,
+ help=(('Comma-separated list of sort keys and directions in the '
+ 'form of <key>[:<asc|desc>]. '
+ 'Valid keys: %s. '
+ 'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
+@utils.service_type('volumev3')
+def do_backup_list(cs, args):
+ """Lists all backups."""
+
+ search_opts = {
+ 'all_tenants': args.all_tenants,
+ 'name': args.name,
+ 'status': args.status,
+ 'volume_id': args.volume_id,
+ }
+
+ backups = cs.backups.list(search_opts=search_opts,
+ marker=args.marker,
+ limit=args.limit,
+ sort=args.sort)
+ _translate_volume_snapshot_keys(backups)
+ columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count',
+ 'Container']
+ if args.sort:
+ sortby_index = None
+ else:
+ sortby_index = 0
+ utils.print_list(backups, columns, sortby_index=sortby_index)
+
+
+@utils.arg('backup', metavar='<backup>', nargs='+',
+ help='Name or ID of backup(s) to delete.')
+@utils.service_type('volumev3')
+def do_backup_delete(cs, args):
+ """Removes one or more backups."""
+ failure_count = 0
+ for backup in args.backup:
+ try:
+ _find_backup(cs, backup).delete()
+ print("Request to delete backup %s has been accepted." % (backup))
+ except Exception as e:
+ failure_count += 1
+ print("Delete for backup %s failed: %s" % (backup, e))
+ if failure_count == len(args.backup):
+ raise exceptions.CommandError("Unable to delete any of the specified "
+ "backups.")
+
+
+@utils.arg('backup', metavar='<backup>',
+ help='ID of backup to restore.')
+@utils.arg('--volume-id', metavar='<volume>',
+ default=None,
+ help=argparse.SUPPRESS)
+@utils.arg('--volume', metavar='<volume>',
+ default=None,
+ help='Name or ID of volume to which to restore. '
+ 'Default=None.')
+@utils.service_type('volumev3')
+def do_backup_restore(cs, args):
+ """Restores a backup."""
+ vol = args.volume or args.volume_id
+ if vol:
+ volume_id = utils.find_volume(cs, vol).id
+ else:
+ volume_id = None
+
+ restore = cs.restores.restore(args.backup, volume_id)
+
+ info = {"backup_id": args.backup}
+ info.update(restore._info)
+
+ info.pop('links', None)
+
+ utils.print_dict(info)
+
+
+@utils.arg('backup', metavar='<backup>',
+ help='ID of the backup to export.')
+@utils.service_type('volumev3')
+def do_backup_export(cs, args):
+ """Export backup metadata record."""
+ info = cs.backups.export_record(args.backup)
+ utils.print_dict(info)
+
+
+@utils.arg('backup_service', metavar='<backup_service>',
+ help='Backup service to use for importing the backup.')
+@utils.arg('backup_url', metavar='<backup_url>',
+ help='Backup URL for importing the backup metadata.')
+@utils.service_type('volumev3')
+def do_backup_import(cs, args):
+ """Import backup metadata record."""
+ info = cs.backups.import_record(args.backup_service, args.backup_url)
+ info.pop('links', None)
+
+ utils.print_dict(info)
+
+
+@utils.arg('backup', metavar='<backup>', nargs='+',
+ help='Name or ID of the backup to modify.')
+@utils.arg('--state', metavar='<state>',
+ default='available',
+ help='The state to assign to the backup. Valid values are '
+ '"available", "error". Default=available.')
+@utils.service_type('volumev3')
+def do_backup_reset_state(cs, args):
+ """Explicitly updates the backup state."""
+ failure_count = 0
+
+ single = (len(args.backup) == 1)
+
+ for backup in args.backup:
+ try:
+ _find_backup(cs, backup).reset_state(args.state)
+ except Exception as e:
+ failure_count += 1
+ msg = "Reset state for backup %s failed: %s" % (backup, e)
+ if not single:
+ print(msg)
+
+ if failure_count == len(args.backup):
+ if not single:
+ msg = ("Unable to reset the state for any of the specified "
+ "backups.")
+ raise exceptions.CommandError(msg)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of volume to transfer.')
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Transfer name. Default=None.')
+@utils.arg('--display-name',
+ help=argparse.SUPPRESS)
+@utils.service_type('volumev3')
+def do_transfer_create(cs, args):
+ """Creates a volume transfer."""
+ if args.display_name is not None:
+ args.name = args.display_name
+
+ volume = utils.find_volume(cs, args.volume)
+ transfer = cs.transfers.create(volume.id,
+ args.name)
+ info = dict()
+ info.update(transfer._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('transfer', metavar='<transfer>',
+ help='Name or ID of transfer to delete.')
+@utils.service_type('volumev3')
+def do_transfer_delete(cs, args):
+ """Undoes a transfer."""
+ transfer = _find_transfer(cs, args.transfer)
+ transfer.delete()
+
+
+@utils.arg('transfer', metavar='<transfer>',
+ help='ID of transfer to accept.')
+@utils.arg('auth_key', metavar='<auth_key>',
+ help='Authentication key of transfer to accept.')
+@utils.service_type('volumev3')
+def do_transfer_accept(cs, args):
+ """Accepts a volume transfer."""
+ transfer = cs.transfers.accept(args.transfer, args.auth_key)
+ info = dict()
+ info.update(transfer._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('--all-tenants',
+ dest='all_tenants',
+ metavar='<0|1>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.arg('--all_tenants',
+ nargs='?',
+ type=int,
+ const=1,
+ help=argparse.SUPPRESS)
+@utils.service_type('volumev3')
+def do_transfer_list(cs, args):
+ """Lists all transfers."""
+ all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
+ search_opts = {
+ 'all_tenants': all_tenants,
+ }
+ transfers = cs.transfers.list(search_opts=search_opts)
+ columns = ['ID', 'Volume ID', 'Name']
+ utils.print_list(transfers, columns)
+
+
+@utils.arg('transfer', metavar='<transfer>',
+ help='Name or ID of transfer to accept.')
+@utils.service_type('volumev3')
+def do_transfer_show(cs, args):
+ """Shows transfer details."""
+ transfer = _find_transfer(cs, args.transfer)
+ info = dict()
+ info.update(transfer._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of volume to extend.')
+@utils.arg('new_size',
+ metavar='<new_size>',
+ type=int,
+ help='New size of volume, in GiBs.')
+@utils.service_type('volumev3')
+def do_extend(cs, args):
+ """Attempts to extend size of an existing volume."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.extend(volume, args.new_size)
+
+
+@utils.arg('--host', metavar='<hostname>', default=None,
+ help='Host name. Default=None.')
+@utils.arg('--binary', metavar='<binary>', default=None,
+ help='Service binary. Default=None.')
+@utils.arg('--withreplication',
+ metavar='<True|False>',
+ const=True,
+ nargs='?',
+ default=False,
+ help='Enables or disables display of '
+ 'Replication info for c-vol services. Default=False.')
+@utils.service_type('volumev3')
+def do_service_list(cs, args):
+ """Lists all services. Filter by host and service binary."""
+ replication = strutils.bool_from_string(args.withreplication)
+ result = cs.services.list(host=args.host, binary=args.binary)
+ columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"]
+ if replication:
+ columns.extend(["Replication Status", "Active Backend ID", "Frozen"])
+ # NOTE(jay-lau-513): we check if the response has disabled_reason
+ # so as not to add the column when the extended ext is not enabled.
+ if result and hasattr(result[0], 'disabled_reason'):
+ columns.append("Disabled Reason")
+ utils.print_list(result, columns)
+
+
+@utils.arg('host', metavar='<hostname>', help='Host name.')
+@utils.arg('binary', metavar='<binary>', help='Service binary.')
+@utils.service_type('volumev3')
+def do_service_enable(cs, args):
+ """Enables the service."""
+ result = cs.services.enable(args.host, args.binary)
+ columns = ["Host", "Binary", "Status"]
+ utils.print_list([result], columns)
+
+
+@utils.arg('host', metavar='<hostname>', help='Host name.')
+@utils.arg('binary', metavar='<binary>', help='Service binary.')
+@utils.arg('--reason', metavar='<reason>',
+ help='Reason for disabling service.')
+@utils.service_type('volumev3')
+def do_service_disable(cs, args):
+ """Disables the service."""
+ columns = ["Host", "Binary", "Status"]
+ if args.reason:
+ columns.append('Disabled Reason')
+ result = cs.services.disable_log_reason(args.host, args.binary,
+ args.reason)
+ else:
+ result = cs.services.disable(args.host, args.binary)
+ utils.print_list([result], columns)
+
+
+@utils.service_type('volumev3')
+def _treeizeAvailabilityZone(zone):
+ """Builds a tree view for availability zones."""
+ AvailabilityZone = availability_zones.AvailabilityZone
+
+ az = AvailabilityZone(zone.manager,
+ copy.deepcopy(zone._info), zone._loaded)
+ result = []
+
+ # Zone tree view item
+ az.zoneName = zone.zoneName
+ az.zoneState = ('available'
+ if zone.zoneState['available'] else 'not available')
+ az._info['zoneName'] = az.zoneName
+ az._info['zoneState'] = az.zoneState
+ result.append(az)
+
+ if getattr(zone, "hosts", None) and zone.hosts is not None:
+ for (host, services) in zone.hosts.items():
+ # Host tree view item
+ az = AvailabilityZone(zone.manager,
+ copy.deepcopy(zone._info), zone._loaded)
+ az.zoneName = '|- %s' % host
+ az.zoneState = ''
+ az._info['zoneName'] = az.zoneName
+ az._info['zoneState'] = az.zoneState
+ result.append(az)
+
+ for (svc, state) in services.items():
+ # Service tree view item
+ az = AvailabilityZone(zone.manager,
+ copy.deepcopy(zone._info), zone._loaded)
+ az.zoneName = '| |- %s' % svc
+ az.zoneState = '%s %s %s' % (
+ 'enabled' if state['active'] else 'disabled',
+ ':-)' if state['available'] else 'XXX',
+ state['updated_at'])
+ az._info['zoneName'] = az.zoneName
+ az._info['zoneState'] = az.zoneState
+ result.append(az)
+ return result
+
+
+@utils.service_type('volumev3')
+def do_availability_zone_list(cs, _args):
+ """Lists all availability zones."""
+ try:
+ availability_zones = cs.availability_zones.list()
+ except exceptions.Forbidden as e: # policy doesn't allow probably
+ try:
+ availability_zones = cs.availability_zones.list(detailed=False)
+ except Exception:
+ raise e
+
+ result = []
+ for zone in availability_zones:
+ result += _treeizeAvailabilityZone(zone)
+ _translate_availability_zone_keys(result)
+ utils.print_list(result, ['Name', 'Status'])
+
+
+def _print_volume_encryption_type_list(encryption_types):
+ """
+ Lists volume encryption types.
+
+ :param encryption_types: a list of :class: VolumeEncryptionType instances
+ """
+ utils.print_list(encryption_types, ['Volume Type ID', 'Provider',
+ 'Cipher', 'Key Size',
+ 'Control Location'])
+
+
+@utils.service_type('volumev3')
+def do_encryption_type_list(cs, args):
+ """Shows encryption type details for volume types. Admin only."""
+ result = cs.volume_encryption_types.list()
+ utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher',
+ 'Key Size', 'Control Location'])
+
+
+@utils.arg('volume_type',
+ metavar='<volume_type>',
+ type=str,
+ help='Name or ID of volume type.')
+@utils.service_type('volumev3')
+def do_encryption_type_show(cs, args):
+ """Shows encryption type details for a volume type. Admin only."""
+ volume_type = _find_volume_type(cs, args.volume_type)
+
+ result = cs.volume_encryption_types.get(volume_type)
+
+ # Display result or an empty table if no result
+ if hasattr(result, 'volume_type_id'):
+ _print_volume_encryption_type_list([result])
+ else:
+ _print_volume_encryption_type_list([])
+
+
+@utils.arg('volume_type',
+ metavar='<volume_type>',
+ type=str,
+ help='Name or ID of volume type.')
+@utils.arg('provider',
+ metavar='<provider>',
+ type=str,
+ help='The class that provides encryption support. '
+ 'For example, LuksEncryptor.')
+@utils.arg('--cipher',
+ metavar='<cipher>',
+ type=str,
+ required=False,
+ default=None,
+ help='The encryption algorithm or mode. '
+ 'For example, aes-xts-plain64. Default=None.')
+@utils.arg('--key_size',
+ metavar='<key_size>',
+ type=int,
+ required=False,
+ default=None,
+ help='Size of encryption key, in bits. '
+ 'For example, 128 or 256. Default=None.')
+@utils.arg('--control_location',
+ metavar='<control_location>',
+ choices=['front-end', 'back-end'],
+ type=str,
+ required=False,
+ default='front-end',
+ help='Notional service where encryption is performed. '
+ 'Valid values are "front-end" or "back-end." '
+ 'For example, front-end=Nova. Default is "front-end."')
+@utils.service_type('volumev3')
+def do_encryption_type_create(cs, args):
+ """Creates encryption type for a volume type. Admin only."""
+ volume_type = _find_volume_type(cs, args.volume_type)
+
+ body = {
+ 'provider': args.provider,
+ 'cipher': args.cipher,
+ 'key_size': args.key_size,
+ 'control_location': args.control_location
+ }
+
+ result = cs.volume_encryption_types.create(volume_type, body)
+ _print_volume_encryption_type_list([result])
+
+
+@utils.arg('volume_type',
+ metavar='<volume-type>',
+ type=str,
+ help="Name or ID of the volume type")
+@utils.arg('--provider',
+ metavar='<provider>',
+ type=str,
+ required=False,
+ default=argparse.SUPPRESS,
+ help="Class providing encryption support (e.g. LuksEncryptor) "
+ "(Optional)")
+@utils.arg('--cipher',
+ metavar='<cipher>',
+ type=str,
+ nargs='?',
+ required=False,
+ default=argparse.SUPPRESS,
+ const=None,
+ help="Encryption algorithm/mode to use (e.g., aes-xts-plain64). "
+ "Provide parameter without value to set to provider default. "
+ "(Optional)")
+@utils.arg('--key-size',
+ dest='key_size',
+ metavar='<key-size>',
+ type=int,
+ nargs='?',
+ required=False,
+ default=argparse.SUPPRESS,
+ const=None,
+ help="Size of the encryption key, in bits (e.g., 128, 256). "
+ "Provide parameter without value to set to provider default. "
+ "(Optional)")
+@utils.arg('--control-location',
+ dest='control_location',
+ metavar='<control-location>',
+ choices=['front-end', 'back-end'],
+ type=str,
+ required=False,
+ default=argparse.SUPPRESS,
+ help="Notional service where encryption is performed (e.g., "
+ "front-end=Nova). Values: 'front-end', 'back-end' (Optional)")
+@utils.service_type('volumev3')
+def do_encryption_type_update(cs, args):
+ """Update encryption type information for a volume type (Admin Only)."""
+ volume_type = _find_volume_type(cs, args.volume_type)
+
+ # An argument should only be pulled if the user specified the parameter.
+ body = {}
+ for attr in ['provider', 'cipher', 'key_size', 'control_location']:
+ if hasattr(args, attr):
+ body[attr] = getattr(args, attr)
+
+ cs.volume_encryption_types.update(volume_type, body)
+ result = cs.volume_encryption_types.get(volume_type)
+ _print_volume_encryption_type_list([result])
+
+
+@utils.arg('volume_type',
+ metavar='<volume_type>',
+ type=str,
+ help='Name or ID of volume type.')
+@utils.service_type('volumev3')
+def do_encryption_type_delete(cs, args):
+ """Deletes encryption type for a volume type. Admin only."""
+ volume_type = _find_volume_type(cs, args.volume_type)
+ cs.volume_encryption_types.delete(volume_type)
+
+
+def _print_qos_specs(qos_specs):
+
+ # formatters defines field to be converted from unicode to string
+ utils.print_dict(qos_specs._info, formatters=['specs'])
+
+
+def _print_qos_specs_list(q_specs):
+ utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
+
+
+def _print_qos_specs_and_associations_list(q_specs):
+ utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
+
+
+def _print_associations_list(associations):
+ utils.print_list(associations, ['Association_Type', 'Name', 'ID'])
+
+
+@utils.arg('name',
+ metavar='<name>',
+ help='Name of new QoS specifications.')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='QoS specifications.')
+@utils.service_type('volumev3')
+def do_qos_create(cs, args):
+ """Creates a qos specs."""
+ keypair = None
+ if args.metadata is not None:
+ keypair = _extract_metadata(args)
+ qos_specs = cs.qos_specs.create(args.name, keypair)
+ _print_qos_specs(qos_specs)
+
+
+@utils.service_type('volumev3')
+def do_qos_list(cs, args):
+ """Lists qos specs."""
+ qos_specs = cs.qos_specs.list()
+ _print_qos_specs_list(qos_specs)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications to show.')
+@utils.service_type('volumev3')
+def do_qos_show(cs, args):
+ """Shows qos specs details."""
+ qos_specs = _find_qos_specs(cs, args.qos_specs)
+ _print_qos_specs(qos_specs)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications to delete.')
+@utils.arg('--force',
+ metavar='<True|False>',
+ const=True,
+ nargs='?',
+ default=False,
+ help='Enables or disables deletion of in-use '
+ 'QoS specifications. Default=False.')
+@utils.service_type('volumev3')
+def do_qos_delete(cs, args):
+ """Deletes a specified qos specs."""
+ force = strutils.bool_from_string(args.force)
+ qos_specs = _find_qos_specs(cs, args.qos_specs)
+ cs.qos_specs.delete(qos_specs, force)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications.')
+@utils.arg('vol_type_id', metavar='<volume_type_id>',
+ help='ID of volume type with which to associate '
+ 'QoS specifications.')
+@utils.service_type('volumev3')
+def do_qos_associate(cs, args):
+ """Associates qos specs with specified volume type."""
+ cs.qos_specs.associate(args.qos_specs, args.vol_type_id)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications.')
+@utils.arg('vol_type_id', metavar='<volume_type_id>',
+ help='ID of volume type with which to associate '
+ 'QoS specifications.')
+@utils.service_type('volumev3')
+def do_qos_disassociate(cs, args):
+ """Disassociates qos specs from specified volume type."""
+ cs.qos_specs.disassociate(args.qos_specs, args.vol_type_id)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications on which to operate.')
+@utils.service_type('volumev3')
+def do_qos_disassociate_all(cs, args):
+ """Disassociates qos specs from all its associations."""
+ cs.qos_specs.disassociate_all(args.qos_specs)
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications.')
+@utils.arg('action',
+ metavar='<action>',
+ choices=['set', 'unset'],
+ help='The action. Valid values are "set" or "unset."')
+@utils.arg('metadata', metavar='key=value',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair to set or unset. '
+ 'For unset, specify only the key.')
+@utils.service_type('volumev3')
+def do_qos_key(cs, args):
+ """Sets or unsets specifications for a qos spec."""
+ keypair = _extract_metadata(args)
+
+ if args.action == 'set':
+ cs.qos_specs.set_keys(args.qos_specs, keypair)
+ elif args.action == 'unset':
+ cs.qos_specs.unset_keys(args.qos_specs, list(keypair))
+
+
+@utils.arg('qos_specs', metavar='<qos_specs>',
+ help='ID of QoS specifications.')
+@utils.service_type('volumev3')
+def do_qos_get_association(cs, args):
+ """Lists all associations for specified qos specs."""
+ associations = cs.qos_specs.get_associations(args.qos_specs)
+ _print_associations_list(associations)
+
+
+@utils.arg('snapshot',
+ metavar='<snapshot>',
+ help='ID of snapshot for which to update metadata.')
+@utils.arg('action',
+ metavar='<action>',
+ choices=['set', 'unset'],
+ help='The action. Valid values are "set" or "unset."')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair to set or unset. '
+ 'For unset, specify only the key.')
+@utils.service_type('volumev3')
+def do_snapshot_metadata(cs, args):
+ """Sets or deletes snapshot metadata."""
+ snapshot = _find_volume_snapshot(cs, args.snapshot)
+ metadata = _extract_metadata(args)
+
+ if args.action == 'set':
+ metadata = snapshot.set_metadata(metadata)
+ utils.print_dict(metadata._info)
+ elif args.action == 'unset':
+ snapshot.delete_metadata(list(metadata.keys()))
+
+
+@utils.arg('snapshot', metavar='<snapshot>',
+ help='ID of snapshot.')
+@utils.service_type('volumev3')
+def do_snapshot_metadata_show(cs, args):
+ """Shows snapshot metadata."""
+ snapshot = _find_volume_snapshot(cs, args.snapshot)
+ utils.print_dict(snapshot._info['metadata'], 'Metadata-property')
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='ID of volume.')
+@utils.service_type('volumev3')
+def do_metadata_show(cs, args):
+ """Shows volume metadata."""
+ volume = utils.find_volume(cs, args.volume)
+ utils.print_dict(volume._info['metadata'], 'Metadata-property')
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='ID of volume.')
+@utils.service_type('volumev3')
+def do_image_metadata_show(cs, args):
+ """Shows volume image metadata."""
+ volume = utils.find_volume(cs, args.volume)
+ resp, body = volume.show_image_metadata(volume)
+ utils.print_dict(body['metadata'], 'Metadata-property')
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='ID of volume for which to update metadata.')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair or pairs to update.')
+@utils.service_type('volumev3')
+def do_metadata_update_all(cs, args):
+ """Updates volume metadata."""
+ volume = utils.find_volume(cs, args.volume)
+ metadata = _extract_metadata(args)
+ metadata = volume.update_all_metadata(metadata)
+ utils.print_dict(metadata['metadata'], 'Metadata-property')
+
+
+@utils.arg('snapshot',
+ metavar='<snapshot>',
+ help='ID of snapshot for which to update metadata.')
+@utils.arg('metadata',
+ metavar='<key=value>',
+ nargs='+',
+ default=[],
+ help='Metadata key and value pair to update.')
+@utils.service_type('volumev3')
+def do_snapshot_metadata_update_all(cs, args):
+ """Updates snapshot metadata."""
+ snapshot = _find_volume_snapshot(cs, args.snapshot)
+ metadata = _extract_metadata(args)
+ metadata = snapshot.update_all_metadata(metadata)
+ utils.print_dict(metadata)
+
+
+@utils.arg('volume', metavar='<volume>', help='ID of volume to update.')
+@utils.arg('read_only',
+ metavar='<True|true|False|false>',
+ choices=['True', 'true', 'False', 'false'],
+ help='Enables or disables update of volume to '
+ 'read-only access mode.')
+@utils.service_type('volumev3')
+def do_readonly_mode_update(cs, args):
+ """Updates volume read-only access-mode flag."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.update_readonly_flag(volume,
+ strutils.bool_from_string(args.read_only))
+
+
+@utils.arg('volume', metavar='<volume>', help='ID of the volume to update.')
+@utils.arg('bootable',
+ metavar='<True|true|False|false>',
+ choices=['True', 'true', 'False', 'false'],
+ help='Flag to indicate whether volume is bootable.')
+@utils.service_type('volumev3')
+def do_set_bootable(cs, args):
+ """Update bootable status of a volume."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.set_bootable(volume,
+ strutils.bool_from_string(args.bootable))
+
+
+@utils.arg('host',
+ metavar='<host>',
+ help='Cinder host on which the existing volume resides; '
+ 'takes the form: host@backend-name#pool')
+@utils.arg('identifier',
+ metavar='<identifier>',
+ help='Name or other Identifier for existing volume')
+@utils.arg('--id-type',
+ metavar='<id-type>',
+ default='source-name',
+ help='Type of backend device identifier provided, '
+ 'typically source-name or source-id (Default=source-name)')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Volume name (Default=None)')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Volume description (Default=None)')
+@utils.arg('--volume-type',
+ metavar='<volume-type>',
+ help='Volume type (Default=None)')
+@utils.arg('--availability-zone',
+ metavar='<availability-zone>',
+ help='Availability zone for volume (Default=None)')
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ help='Metadata key=value pairs (Default=None)')
+@utils.arg('--bootable',
+ action='store_true',
+ help='Specifies that the newly created volume should be'
+ ' marked as bootable')
+@utils.service_type('volumev3')
+def do_manage(cs, args):
+ """Manage an existing volume."""
+ volume_metadata = None
+ if args.metadata is not None:
+ volume_metadata = _extract_metadata(args)
+
+ # Build a dictionary of key/value pairs to pass to the API.
+ ref_dict = {args.id_type: args.identifier}
+
+ # The recommended way to specify an existing volume is by ID or name, and
+ # have the Cinder driver look for 'source-name' or 'source-id' elements in
+ # the ref structure. To make things easier for the user, we have special
+ # --source-name and --source-id CLI options that add the appropriate
+ # element to the ref structure.
+ #
+ # Note how argparse converts hyphens to underscores. We use hyphens in the
+ # dictionary so that it is consistent with what the user specified on the
+ # CLI.
+
+ if hasattr(args, 'source_name') and args.source_name is not None:
+ ref_dict['source-name'] = args.source_name
+ if hasattr(args, 'source_id') and args.source_id is not None:
+ ref_dict['source-id'] = args.source_id
+
+ volume = cs.volumes.manage(host=args.host,
+ ref=ref_dict,
+ name=args.name,
+ description=args.description,
+ volume_type=args.volume_type,
+ availability_zone=args.availability_zone,
+ metadata=volume_metadata,
+ bootable=args.bootable)
+
+ info = {}
+ volume = cs.volumes.get(volume.id)
+ info.update(volume._info)
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of the volume to unmanage.')
+@utils.service_type('volumev3')
+def do_unmanage(cs, args):
+ """Stop managing a volume."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.unmanage(volume.id)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of the volume to promote. '
+ 'The volume should have the replica volume created with '
+ 'source-replica argument.')
+@utils.service_type('volumev3')
+def do_replication_promote(cs, args):
+ """Promote a secondary volume to primary for a relationship."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.promote(volume.id)
+
+
+@utils.arg('volume', metavar='<volume>',
+ help='Name or ID of the volume to reenable replication. '
+ 'The replication-status of the volume should be inactive.')
+@utils.service_type('volumev3')
+def do_replication_reenable(cs, args):
+ """Sync the secondary volume with primary for a relationship."""
+ volume = utils.find_volume(cs, args.volume)
+ cs.volumes.reenable(volume.id)
+
+
+@utils.arg('--all-tenants',
+ dest='all_tenants',
+ metavar='<0|1>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.service_type('volumev3')
+def do_consisgroup_list(cs, args):
+ """Lists all consistencygroups."""
+ consistencygroups = cs.consistencygroups.list()
+
+ columns = ['ID', 'Status', 'Name']
+ utils.print_list(consistencygroups, columns)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>',
+ help='Name or ID of a consistency group.')
+@utils.service_type('volumev3')
+def do_consisgroup_show(cs, args):
+ """Shows details of a consistency group."""
+ info = dict()
+ consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
+ info.update(consistencygroup._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('volumetypes',
+ metavar='<volume-types>',
+ help='Volume types.')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Name of a consistency group.')
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Description of a consistency group. Default=None.')
+@utils.arg('--availability-zone',
+ metavar='<availability-zone>',
+ default=None,
+ help='Availability zone for volume. Default=None.')
+@utils.service_type('volumev3')
+def do_consisgroup_create(cs, args):
+ """Creates a consistency group."""
+
+ consistencygroup = cs.consistencygroups.create(
+ args.volumetypes,
+ args.name,
+ args.description,
+ availability_zone=args.availability_zone)
+
+ info = dict()
+ consistencygroup = cs.consistencygroups.get(consistencygroup.id)
+ info.update(consistencygroup._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('--cgsnapshot',
+ metavar='<cgsnapshot>',
+ help='Name or ID of a cgsnapshot. Default=None.')
+@utils.arg('--source-cg',
+ metavar='<source-cg>',
+ help='Name or ID of a source CG. Default=None.')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Name of a consistency group. Default=None.')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Description of a consistency group. Default=None.')
+@utils.service_type('volumev3')
+def do_consisgroup_create_from_src(cs, args):
+ """Creates a consistency group from a cgsnapshot or a source CG."""
+ if not args.cgsnapshot and not args.source_cg:
+ msg = ('Cannot create consistency group because neither '
+ 'cgsnapshot nor source CG is provided.')
+ raise exceptions.ClientException(code=1, message=msg)
+ if args.cgsnapshot and args.source_cg:
+ msg = ('Cannot create consistency group because both '
+ 'cgsnapshot and source CG are provided.')
+ raise exceptions.ClientException(code=1, message=msg)
+ cgsnapshot = None
+ if args.cgsnapshot:
+ cgsnapshot = _find_cgsnapshot(cs, args.cgsnapshot)
+ source_cg = None
+ if args.source_cg:
+ source_cg = _find_consistencygroup(cs, args.source_cg)
+ info = cs.consistencygroups.create_from_src(
+ cgsnapshot.id if cgsnapshot else None,
+ source_cg.id if source_cg else None,
+ args.name,
+ args.description)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>', nargs='+',
+ help='Name or ID of one or more consistency groups '
+ 'to be deleted.')
+@utils.arg('--force',
+ action='store_true',
+ default=False,
+ help='Allows or disallows consistency groups '
+ 'to be deleted. If the consistency group is empty, '
+ 'it can be deleted without the force flag. '
+ 'If the consistency group is not empty, the force '
+ 'flag is required for it to be deleted.')
+@utils.service_type('volumev3')
+def do_consisgroup_delete(cs, args):
+ """Removes one or more consistency groups."""
+ failure_count = 0
+ for consistencygroup in args.consistencygroup:
+ try:
+ _find_consistencygroup(cs, consistencygroup).delete(args.force)
+ except Exception as e:
+ failure_count += 1
+ print("Delete for consistency group %s failed: %s" %
+ (consistencygroup, e))
+ if failure_count == len(args.consistencygroup):
+ raise exceptions.CommandError("Unable to delete any of the specified "
+ "consistency groups.")
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>',
+ help='Name or ID of a consistency group.')
+@utils.arg('--name', metavar='<name>',
+ help='New name for consistency group. Default=None.')
+@utils.arg('--description', metavar='<description>',
+ help='New description for consistency group. Default=None.')
+@utils.arg('--add-volumes',
+ metavar='<uuid1,uuid2,......>',
+ help='UUID of one or more volumes '
+ 'to be added to the consistency group, '
+ 'separated by commas. Default=None.')
+@utils.arg('--remove-volumes',
+ metavar='<uuid3,uuid4,......>',
+ help='UUID of one or more volumes '
+ 'to be removed from the consistency group, '
+ 'separated by commas. Default=None.')
+@utils.service_type('volumev3')
+def do_consisgroup_update(cs, args):
+ """Updates a consistencygroup."""
+ kwargs = {}
+
+ if args.name is not None:
+ kwargs['name'] = args.name
+
+ if args.description is not None:
+ kwargs['description'] = args.description
+
+ if args.add_volumes is not None:
+ kwargs['add_volumes'] = args.add_volumes
+
+ if args.remove_volumes is not None:
+ kwargs['remove_volumes'] = args.remove_volumes
+
+ if not kwargs:
+ msg = ('At least one of the following args must be supplied: '
+ 'name, description, add-volumes, remove-volumes.')
+ raise exceptions.ClientException(code=1, message=msg)
+
+ _find_consistencygroup(cs, args.consistencygroup).update(**kwargs)
+
+
+@utils.arg('--all-tenants',
+ dest='all_tenants',
+ metavar='<0|1>',
+ nargs='?',
+ type=int,
+ const=1,
+ default=0,
+ help='Shows details for all tenants. Admin only.')
+@utils.arg('--status',
+ metavar='<status>',
+ default=None,
+ help='Filters results by a status. Default=None.')
+@utils.arg('--consistencygroup-id',
+ metavar='<consistencygroup_id>',
+ default=None,
+ help='Filters results by a consistency group ID. Default=None.')
+@utils.service_type('volumev3')
+def do_cgsnapshot_list(cs, args):
+ """Lists all cgsnapshots."""
+
+ all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
+
+ search_opts = {
+ 'all_tenants': all_tenants,
+ 'status': args.status,
+ 'consistencygroup_id': args.consistencygroup_id,
+ }
+
+ cgsnapshots = cs.cgsnapshots.list(search_opts=search_opts)
+
+ columns = ['ID', 'Status', 'Name']
+ utils.print_list(cgsnapshots, columns)
+
+
+@utils.arg('cgsnapshot',
+ metavar='<cgsnapshot>',
+ help='Name or ID of cgsnapshot.')
+@utils.service_type('volumev3')
+def do_cgsnapshot_show(cs, args):
+ """Shows cgsnapshot details."""
+ info = dict()
+ cgsnapshot = _find_cgsnapshot(cs, args.cgsnapshot)
+ info.update(cgsnapshot._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('consistencygroup',
+ metavar='<consistencygroup>',
+ help='Name or ID of a consistency group.')
+@utils.arg('--name',
+ metavar='<name>',
+ default=None,
+ help='Cgsnapshot name. Default=None.')
+@utils.arg('--description',
+ metavar='<description>',
+ default=None,
+ help='Cgsnapshot description. Default=None.')
+@utils.service_type('volumev3')
+def do_cgsnapshot_create(cs, args):
+ """Creates a cgsnapshot."""
+ consistencygroup = _find_consistencygroup(cs, args.consistencygroup)
+ cgsnapshot = cs.cgsnapshots.create(
+ consistencygroup.id,
+ args.name,
+ args.description)
+
+ info = dict()
+ cgsnapshot = cs.cgsnapshots.get(cgsnapshot.id)
+ info.update(cgsnapshot._info)
+
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('cgsnapshot',
+ metavar='<cgsnapshot>', nargs='+',
+ help='Name or ID of one or more cgsnapshots to be deleted.')
+@utils.service_type('volumev3')
+def do_cgsnapshot_delete(cs, args):
+ """Removes one or more cgsnapshots."""
+ failure_count = 0
+ for cgsnapshot in args.cgsnapshot:
+ try:
+ _find_cgsnapshot(cs, cgsnapshot).delete()
+ except Exception as e:
+ failure_count += 1
+ print("Delete for cgsnapshot %s failed: %s" % (cgsnapshot, e))
+ if failure_count == len(args.cgsnapshot):
+ raise exceptions.CommandError("Unable to delete any of the specified "
+ "cgsnapshots.")
+
+
+@utils.arg('--detail',
+ action='store_true',
+ help='Show detailed information about pools.')
+@utils.service_type('volumev3')
+def do_get_pools(cs, args):
+ """Show pool information for backends. Admin only."""
+ pools = cs.volumes.get_pools(args.detail)
+ infos = dict()
+ infos.update(pools._info)
+
+ for info in infos['pools']:
+ backend = dict()
+ backend['name'] = info['name']
+ if args.detail:
+ backend.update(info['capabilities'])
+ utils.print_dict(backend)
+
+
+@utils.arg('host',
+ metavar='<host>',
+ help='Cinder host to show backend volume stats and properties; '
+ 'takes the form: host@backend-name')
+@utils.service_type('volumev3')
+def do_get_capabilities(cs, args):
+ """Show backend volume stats and properties. Admin only."""
+
+ capabilities = cs.capabilities.get(args.host)
+ infos = dict()
+ infos.update(capabilities._info)
+
+ prop = infos.pop('properties', None)
+ utils.print_dict(infos, "Volume stats")
+ utils.print_dict(prop, "Backend properties")
+
+
+@utils.arg('volume',
+ metavar='<volume>',
+ help='Cinder volume already exists in volume backend')
+@utils.arg('identifier',
+ metavar='<identifier>',
+ help='Name or other Identifier for existing snapshot')
+@utils.arg('--id-type',
+ metavar='<id-type>',
+ default='source-name',
+ help='Type of backend device identifier provided, '
+ 'typically source-name or source-id (Default=source-name)')
+@utils.arg('--name',
+ metavar='<name>',
+ help='Snapshot name (Default=None)')
+@utils.arg('--description',
+ metavar='<description>',
+ help='Snapshot description (Default=None)')
+@utils.arg('--metadata',
+ type=str,
+ nargs='*',
+ metavar='<key=value>',
+ help='Metadata key=value pairs (Default=None)')
+@utils.service_type('volumev3')
+def do_snapshot_manage(cs, args):
+ """Manage an existing snapshot."""
+ snapshot_metadata = None
+ if args.metadata is not None:
+ snapshot_metadata = _extract_metadata(args)
+
+ # Build a dictionary of key/value pairs to pass to the API.
+ ref_dict = {args.id_type: args.identifier}
+
+ if hasattr(args, 'source_name') and args.source_name is not None:
+ ref_dict['source-name'] = args.source_name
+ if hasattr(args, 'source_id') and args.source_id is not None:
+ ref_dict['source-id'] = args.source_id
+
+ volume = utils.find_volume(cs, args.volume)
+ snapshot = cs.volume_snapshots.manage(volume_id=volume.id,
+ ref=ref_dict,
+ name=args.name,
+ description=args.description,
+ metadata=snapshot_metadata)
+
+ info = {}
+ snapshot = cs.volume_snapshots.get(snapshot.id)
+ info.update(snapshot._info)
+ info.pop('links', None)
+ utils.print_dict(info)
+
+
+@utils.arg('snapshot', metavar='<snapshot>',
+ help='Name or ID of the snapshot to unmanage.')
+@utils.service_type('volumev3')
+def do_snapshot_unmanage(cs, args):
+ """Stop managing a snapshot."""
+ snapshot = _find_volume_snapshot(cs, args.snapshot)
+ cs.volume_snapshots.unmanage(snapshot.id)
+
+
+@utils.arg('host', metavar='<hostname>', help='Host name.')
+@utils.service_type('volumev3')
+def do_freeze_host(cs, args):
+ """Freeze and disable the specified cinder-volume host."""
+ cs.services.freeze_host(args.host)
+
+
+@utils.arg('host', metavar='<hostname>', help='Host name.')
+@utils.service_type('volumev3')
+def do_thaw_host(cs, args):
+ """Thaw and enable the specified cinder-volume host."""
+ cs.services.thaw_host(args.host)
+
+
+@utils.arg('host', metavar='<hostname>', help='Host name.')
+@utils.arg('--backend_id',
+ metavar='<backend-id>',
+ help='ID of backend to failover to (Default=None)')
+@utils.service_type('volumev3')
+def do_failover_host(cs, args):
+ """Failover a replicating cinder-volume host."""
+ cs.services.failover_host(args.host, args.backend_id)
diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py
new file mode 100644
index 0000000..b264dec
--- /dev/null
+++ b/cinderclient/v3/volume_backups.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
+# 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 Backups interface (v3 extension).
+"""
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class VolumeBackup(base.Resource):
+ """A volume backup is a block level backup of a volume."""
+
+ def __repr__(self):
+ return "<VolumeBackup: %s>" % self.id
+
+ def delete(self):
+ """Delete this volume backup."""
+ return self.manager.delete(self)
+
+ def reset_state(self, state):
+ return self.manager.reset_state(self, state)
+
+
+class VolumeBackupManager(base.ManagerWithFind):
+ """Manage :class:`VolumeBackup` resources."""
+ resource_class = VolumeBackup
+
+ def create(self, volume_id, container=None,
+ name=None, description=None,
+ incremental=False, force=False,
+ snapshot_id=None):
+ """Creates a volume backup.
+
+ :param volume_id: The ID of the volume to backup.
+ :param container: The name of the backup service container.
+ :param name: The name of the backup.
+ :param description: The description of the backup.
+ :param incremental: Incremental backup.
+ :param force: If True, allows an in-use volume to be backed up.
+ :rtype: :class:`VolumeBackup`
+ """
+ body = {'backup': {'volume_id': volume_id,
+ 'container': container,
+ 'name': name,
+ 'description': description,
+ 'incremental': incremental,
+ 'force': force,
+ 'snapshot_id': snapshot_id, }}
+ return self._create('/backups', body, 'backup')
+
+ def get(self, backup_id):
+ """Show volume backup details.
+
+ :param backup_id: The ID of the backup to display.
+ :rtype: :class:`VolumeBackup`
+ """
+ return self._get("/backups/%s" % backup_id, "backup")
+
+ def list(self, detailed=True, search_opts=None, marker=None, limit=None,
+ sort=None):
+ """Get a list of all volume backups.
+
+ :rtype: list of :class:`VolumeBackup`
+ """
+ resource_type = "backups"
+ url = self._build_list_url(resource_type, detailed=detailed,
+ search_opts=search_opts, marker=marker,
+ limit=limit, sort=sort)
+ return self._list(url, resource_type, limit=limit)
+
+ def delete(self, backup):
+ """Delete a volume backup.
+
+ :param backup: The :class:`VolumeBackup` to delete.
+ """
+ return self._delete("/backups/%s" % base.getid(backup))
+
+ def reset_state(self, backup, state):
+ """Update the specified volume backup with the provided state."""
+ return self._action('os-reset_status', backup, {'status': state})
+
+ def _action(self, action, backup, info=None, **kwargs):
+ """Perform a volume backup action."""
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/backups/%s/action' % base.getid(backup)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def export_record(self, backup_id):
+ """Export volume backup metadata record.
+
+ :param backup_id: The ID of the backup to export.
+ :rtype: A dictionary containing 'backup_url' and 'backup_service'.
+ """
+ resp, body = \
+ self.api.client.get("/backups/%s/export_record" % backup_id)
+ return common_base.DictWithMeta(body['backup-record'], resp)
+
+ def import_record(self, backup_service, backup_url):
+ """Import volume backup metadata record.
+
+ :param backup_service: Backup service to use for importing the backup
+ :param backup_url: Backup URL for importing the backup metadata
+ :rtype: A dictionary containing volume backup metadata.
+ """
+ body = {'backup-record': {'backup_service': backup_service,
+ 'backup_url': backup_url}}
+ self.run_hooks('modify_body_for_update', body, 'backup-record')
+ resp, body = self.api.client.post("/backups/import_record", body=body)
+ return common_base.DictWithMeta(body['backup'], resp)
diff --git a/cinderclient/v3/volume_backups_restore.py b/cinderclient/v3/volume_backups_restore.py
new file mode 100644
index 0000000..911356d
--- /dev/null
+++ b/cinderclient/v3/volume_backups_restore.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
+# 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 Backups Restore interface (v3 extension).
+
+This is part of the Volume Backups interface.
+"""
+
+from cinderclient import base
+
+
+class VolumeBackupsRestore(base.Resource):
+ """A Volume Backups Restore represents a restore operation."""
+ def __repr__(self):
+ return "<VolumeBackupsRestore: %s>" % self.volume_id
+
+
+class VolumeBackupRestoreManager(base.Manager):
+ """Manage :class:`VolumeBackupsRestore` resources."""
+ resource_class = VolumeBackupsRestore
+
+ def restore(self, backup_id, volume_id=None):
+ """Restore a backup to a volume.
+
+ :param backup_id: The ID of the backup to restore.
+ :param volume_id: The ID of the volume to restore the backup to.
+ :rtype: :class:`Restore`
+ """
+ body = {'restore': {'volume_id': volume_id}}
+ return self._create("/backups/%s/restore" % backup_id,
+ body, "restore")
diff --git a/cinderclient/v3/volume_encryption_types.py b/cinderclient/v3/volume_encryption_types.py
new file mode 100644
index 0000000..c4c7a69
--- /dev/null
+++ b/cinderclient/v3/volume_encryption_types.py
@@ -0,0 +1,104 @@
+# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
+# 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 Encryption Type interface
+"""
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class VolumeEncryptionType(base.Resource):
+ """
+ A Volume Encryption Type is a collection of settings used to conduct
+ encryption for a specific volume type.
+ """
+ def __repr__(self):
+ return "<VolumeEncryptionType: %s>" % self.name
+
+
+class VolumeEncryptionTypeManager(base.ManagerWithFind):
+ """
+ Manage :class: `VolumeEncryptionType` resources.
+ """
+ resource_class = VolumeEncryptionType
+
+ def list(self, search_opts=None):
+ """
+ List all volume encryption types.
+
+ :param volume_types: a list of volume types
+ :return: a list of :class: VolumeEncryptionType instances
+ """
+ # Since the encryption type is a volume type extension, we cannot get
+ # all encryption types without going through all volume types.
+ volume_types = self.api.volume_types.list()
+ encryption_types = []
+ list_of_resp = []
+ for volume_type in volume_types:
+ encryption_type = self._get("/types/%s/encryption"
+ % base.getid(volume_type))
+ if hasattr(encryption_type, 'volume_type_id'):
+ encryption_types.append(encryption_type)
+
+ list_of_resp.extend(encryption_type.request_ids)
+
+ return common_base.ListWithMeta(encryption_types, list_of_resp)
+
+ def get(self, volume_type):
+ """
+ Get the volume encryption type for the specified volume type.
+
+ :param volume_type: the volume type to query
+ :return: an instance of :class: VolumeEncryptionType
+ """
+ return self._get("/types/%s/encryption" % base.getid(volume_type))
+
+ def create(self, volume_type, specs):
+ """
+ Creates encryption type for a volume type. Default: admin only.
+
+ :param volume_type: the volume type on which to add an encryption type
+ :param specs: the encryption type specifications to add
+ :return: an instance of :class: VolumeEncryptionType
+ """
+ body = {'encryption': specs}
+ return self._create("/types/%s/encryption" % base.getid(volume_type),
+ body, "encryption")
+
+ def update(self, volume_type, specs):
+ """
+ Update the encryption type information for the specified volume type.
+
+ :param volume_type: the volume type whose encryption type information
+ must be updated
+ :param specs: the encryption type specifications to update
+ :return: an instance of :class: VolumeEncryptionType
+ """
+ body = {'encryption': specs}
+ return self._update("/types/%s/encryption/provider" %
+ base.getid(volume_type), body)
+
+ def delete(self, volume_type):
+ """
+ Delete the encryption type information for the specified volume type.
+
+ :param volume_type: the volume type whose encryption type information
+ must be deleted
+ """
+ return self._delete("/types/%s/encryption/provider" %
+ base.getid(volume_type))
diff --git a/cinderclient/v3/volume_snapshots.py b/cinderclient/v3/volume_snapshots.py
new file mode 100644
index 0000000..0039b8d
--- /dev/null
+++ b/cinderclient/v3/volume_snapshots.py
@@ -0,0 +1,205 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 (v3 extension)."""
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_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."""
+ return self.manager.delete(self)
+
+ def update(self, **kwargs):
+ """Update the name or description for this snapshot."""
+ return self.manager.update(self, **kwargs)
+
+ @property
+ def progress(self):
+ return self._info.get('os-extended-snapshot-attributes:progress')
+
+ @property
+ def project_id(self):
+ return self._info.get('os-extended-snapshot-attributes:project_id')
+
+ def reset_state(self, state):
+ """Update the snapshot with the provided state."""
+ return self.manager.reset_state(self, state)
+
+ def set_metadata(self, metadata):
+ """Set metadata of this snapshot."""
+ return self.manager.set_metadata(self, metadata)
+
+ def delete_metadata(self, keys):
+ """Delete metadata of this snapshot."""
+ return self.manager.delete_metadata(self, keys)
+
+ def update_all_metadata(self, metadata):
+ """Update_all metadata of this snapshot."""
+ return self.manager.update_all_metadata(self, metadata)
+
+ def manage(self, volume_id, ref, name=None, description=None,
+ metadata=None):
+ """Manage an existing snapshot."""
+ self.manager.manage(volume_id=volume_id, ref=ref, name=name,
+ description=description, metadata=metadata)
+
+ def unmanage(self, snapshot):
+ """Unmanage a snapshot."""
+ self.manager.unmanage(snapshot)
+
+
+class SnapshotManager(base.ManagerWithFind):
+ """Manage :class:`Snapshot` resources."""
+ resource_class = Snapshot
+
+ def create(self, volume_id, force=False,
+ name=None, description=None, metadata=None):
+
+ """Creates 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 name: Name of the snapshot
+ :param description: Description of the snapshot
+ :param metadata: Metadata of the snapshot
+ :rtype: :class:`Snapshot`
+ """
+
+ if metadata is None:
+ snapshot_metadata = {}
+ else:
+ snapshot_metadata = metadata
+
+ body = {'snapshot': {'volume_id': volume_id,
+ 'force': force,
+ 'name': name,
+ 'description': description,
+ 'metadata': snapshot_metadata}}
+ return self._create('/snapshots', body, 'snapshot')
+
+ def get(self, snapshot_id):
+ """Shows snapshot details.
+
+ :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, search_opts=None, marker=None, limit=None,
+ sort=None):
+ """Get a list of all snapshots.
+
+ :rtype: list of :class:`Snapshot`
+ """
+ resource_type = "snapshots"
+ url = self._build_list_url(resource_type, detailed=detailed,
+ search_opts=search_opts, marker=marker,
+ limit=limit, sort=sort)
+ return self._list(url, resource_type, limit=limit)
+
+ def delete(self, snapshot):
+ """Delete a snapshot.
+
+ :param snapshot: The :class:`Snapshot` to delete.
+ """
+ return self._delete("/snapshots/%s" % base.getid(snapshot))
+
+ def update(self, snapshot, **kwargs):
+ """Update the name or description for a snapshot.
+
+ :param snapshot: The :class:`Snapshot` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"snapshot": kwargs}
+
+ return self._update("/snapshots/%s" % base.getid(snapshot), body)
+
+ def reset_state(self, snapshot, state):
+ """Update the specified snapshot with the provided state."""
+ return self._action('os-reset_status', snapshot, {'status': state})
+
+ def _action(self, action, snapshot, info=None, **kwargs):
+ """Perform a snapshot action."""
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/snapshots/%s/action' % base.getid(snapshot)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def update_snapshot_status(self, snapshot, update_dict):
+ return self._action('os-update_snapshot_status',
+ base.getid(snapshot), update_dict)
+
+ def set_metadata(self, snapshot, metadata):
+ """Update/Set a snapshots metadata.
+
+ :param snapshot: The :class:`Snapshot`.
+ :param metadata: A list of keys to be set.
+ """
+ body = {'metadata': metadata}
+ return self._create("/snapshots/%s/metadata" % base.getid(snapshot),
+ body, "metadata")
+
+ def delete_metadata(self, snapshot, keys):
+ """Delete specified keys from snapshot metadata.
+
+ :param snapshot: The :class:`Snapshot`.
+ :param keys: A list of keys to be removed.
+ """
+ response_list = []
+ snapshot_id = base.getid(snapshot)
+ for k in keys:
+ resp, body = self._delete("/snapshots/%s/metadata/%s" %
+ (snapshot_id, k))
+ response_list.append(resp)
+
+ return common_base.ListWithMeta([], response_list)
+
+ def update_all_metadata(self, snapshot, metadata):
+ """Update_all snapshot metadata.
+
+ :param snapshot: The :class:`Snapshot`.
+ :param metadata: A list of keys to be updated.
+ """
+ body = {'metadata': metadata}
+ return self._update("/snapshots/%s/metadata" % base.getid(snapshot),
+ body)
+
+ def manage(self, volume_id, ref, name=None, description=None,
+ metadata=None):
+ """Manage an existing snapshot."""
+ body = {'snapshot': {'volume_id': volume_id,
+ 'ref': ref,
+ 'name': name,
+ 'description': description,
+ 'metadata': metadata
+ }
+ }
+ return self._create('/os-snapshot-manage', body, 'snapshot')
+
+ def unmanage(self, snapshot):
+ """Unmanage a snapshot."""
+ return self._action('os-unmanage', snapshot, None)
diff --git a/cinderclient/v3/volume_transfers.py b/cinderclient/v3/volume_transfers.py
new file mode 100644
index 0000000..40aa15a
--- /dev/null
+++ b/cinderclient/v3/volume_transfers.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
+# 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 transfer interface (v3 extension).
+"""
+
+try:
+ from urllib import urlencode
+except ImportError:
+ from urllib.parse import urlencode
+import six
+from cinderclient import base
+
+
+class VolumeTransfer(base.Resource):
+ """Transfer a volume from one tenant to another"""
+
+ def __repr__(self):
+ return "<VolumeTransfer: %s>" % self.id
+
+ def delete(self):
+ """Delete this volume transfer."""
+ return self.manager.delete(self)
+
+
+class VolumeTransferManager(base.ManagerWithFind):
+ """Manage :class:`VolumeTransfer` resources."""
+ resource_class = VolumeTransfer
+
+ def create(self, volume_id, name=None):
+ """Creates a volume transfer.
+
+ :param volume_id: The ID of the volume to transfer.
+ :param name: The name of the transfer.
+ :rtype: :class:`VolumeTransfer`
+ """
+ body = {'transfer': {'volume_id': volume_id,
+ 'name': name}}
+ return self._create('/os-volume-transfer', body, 'transfer')
+
+ def accept(self, transfer_id, auth_key):
+ """Accept a volume transfer.
+
+ :param transfer_id: The ID of the transfer to accept.
+ :param auth_key: The auth_key of the transfer.
+ :rtype: :class:`VolumeTransfer`
+ """
+ body = {'accept': {'auth_key': auth_key}}
+ return self._create('/os-volume-transfer/%s/accept' % transfer_id,
+ body, 'transfer')
+
+ def get(self, transfer_id):
+ """Show details of a volume transfer.
+
+ :param transfer_id: The ID of the volume transfer to display.
+ :rtype: :class:`VolumeTransfer`
+ """
+ return self._get("/os-volume-transfer/%s" % transfer_id, "transfer")
+
+ def list(self, detailed=True, search_opts=None):
+ """Get a list of all volume transfer.
+
+ :rtype: list of :class:`VolumeTransfer`
+ """
+ if search_opts is None:
+ search_opts = {}
+
+ qparams = {}
+
+ for opt, val in six.iteritems(search_opts):
+ if val:
+ qparams[opt] = val
+
+ query_string = "?%s" % urlencode(qparams) if qparams else ""
+
+ detail = ""
+ if detailed:
+ detail = "/detail"
+
+ return self._list("/os-volume-transfer%s%s" % (detail, query_string),
+ "transfers")
+
+ def delete(self, transfer_id):
+ """Delete a volume transfer.
+
+ :param transfer_id: The :class:`VolumeTransfer` to delete.
+ """
+ return self._delete("/os-volume-transfer/%s" % base.getid(transfer_id))
diff --git a/cinderclient/v3/volume_type_access.py b/cinderclient/v3/volume_type_access.py
new file mode 100644
index 0000000..abdfa86
--- /dev/null
+++ b/cinderclient/v3/volume_type_access.py
@@ -0,0 +1,53 @@
+# Copyright 2014 OpenStack Foundation
+#
+# 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 access interface."""
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_base
+
+
+class VolumeTypeAccess(base.Resource):
+ def __repr__(self):
+ return "<VolumeTypeAccess: %s>" % self.project_id
+
+
+class VolumeTypeAccessManager(base.ManagerWithFind):
+ """
+ Manage :class:`VolumeTypeAccess` resources.
+ """
+ resource_class = VolumeTypeAccess
+
+ def list(self, volume_type):
+ return self._list(
+ '/types/%s/os-volume-type-access' % base.getid(volume_type),
+ 'volume_type_access')
+
+ def add_project_access(self, volume_type, project):
+ """Add a project to the given volume type access list."""
+ info = {'project': project}
+ return self._action('addProjectAccess', volume_type, info)
+
+ def remove_project_access(self, volume_type, project):
+ """Remove a project from the given volume type access list."""
+ info = {'project': project}
+ return self._action('removeProjectAccess', volume_type, info)
+
+ def _action(self, action, volume_type, info, **kwargs):
+ """Perform a volume type action."""
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/types/%s/action' % base.getid(volume_type)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
diff --git a/cinderclient/v3/volume_types.py b/cinderclient/v3/volume_types.py
new file mode 100644
index 0000000..251e75b
--- /dev/null
+++ b/cinderclient/v3/volume_types.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2013 OpenStack Foundation
+#
+# 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 "<VolumeType: %s>" % self.name
+
+ @property
+ def is_public(self):
+ """
+ Provide a user-friendly accessor to os-volume-type-access:is_public
+ """
+ return self._info.get("os-volume-type-access:is_public",
+ self._info.get("is_public", 'N/A'))
+
+ def get_keys(self):
+ """Get extra specs from a volume type.
+
+ :param vol_type: The :class:`VolumeType` to get extra specs from
+ """
+ _resp, body = self.manager.api.client.get(
+ "/types/%s/extra_specs" %
+ base.getid(self))
+ return body["extra_specs"]
+
+ def set_keys(self, metadata):
+ """Set extra specs on a volume type.
+
+ :param type : The :class:`VolumeType` to set extra spec on
+ :param metadata: A dict of key/value pairs to be set
+ """
+ body = {'extra_specs': metadata}
+ return self.manager._create(
+ "/types/%s/extra_specs" % base.getid(self),
+ body,
+ "extra_specs",
+ return_raw=True)
+
+ def unset_keys(self, keys):
+ """Unset extra specs on a volue type.
+
+ :param type_id: The :class:`VolumeType` to unset extra spec on
+ :param keys: A list of keys to be unset
+ """
+
+ # NOTE(jdg): This wasn't actually doing all of the keys before
+ # the return in the loop resulted in ony ONE key being unset.
+ # since on success the return was NONE, we'll only interrupt the loop
+ # and return if there's an error
+ for k in keys:
+ resp = self.manager._delete(
+ "/types/%s/extra_specs/%s" % (
+ base.getid(self), k))
+ if resp is not None:
+ return resp
+
+
+class VolumeTypeManager(base.ManagerWithFind):
+ """Manage :class:`VolumeType` resources."""
+ resource_class = VolumeType
+
+ def list(self, search_opts=None, is_public=None):
+ """Lists all volume types.
+
+ :rtype: list of :class:`VolumeType`.
+ """
+ query_string = ''
+ if not is_public:
+ query_string = '?is_public=%s' % is_public
+ return self._list("/types%s" % (query_string), "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 default(self):
+ """Get the default volume type.
+
+ :rtype: :class:`VolumeType`
+ """
+ return self._get("/types/default", "volume_type")
+
+ def delete(self, volume_type):
+ """Deletes a specific volume_type.
+
+ :param volume_type: The name or ID of the :class:`VolumeType` to get.
+ """
+ return self._delete("/types/%s" % base.getid(volume_type))
+
+ def create(self, name, description=None, is_public=True):
+ """Creates a volume type.
+
+ :param name: Descriptive name of the volume type
+ :param description: Description of the the volume type
+ :param is_public: Volume type visibility
+ :rtype: :class:`VolumeType`
+ """
+
+ body = {
+ "volume_type": {
+ "name": name,
+ "description": description,
+ "os-volume-type-access:is_public": is_public,
+ }
+ }
+
+ return self._create("/types", body, "volume_type")
+
+ def update(self, volume_type, name=None, description=None, is_public=None):
+ """Update the name and/or description for a volume type.
+
+ :param volume_type: The ID of the :class:`VolumeType` to update.
+ :param name: Descriptive name of the volume type.
+ :param description: Description of the the volume type.
+ :rtype: :class:`VolumeType`
+ """
+
+ body = {
+ "volume_type": {
+ "name": name,
+ "description": description
+ }
+ }
+ if is_public is not None:
+ body["volume_type"]["is_public"] = is_public
+
+ return self._update("/types/%s" % base.getid(volume_type),
+ body, response_key="volume_type")
diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py
new file mode 100644
index 0000000..f8d2432
--- /dev/null
+++ b/cinderclient/v3/volumes.py
@@ -0,0 +1,604 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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 (v3 extension)."""
+
+from cinderclient import base
+from cinderclient.openstack.common.apiclient import base as common_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, cascade=False):
+ """Delete this volume."""
+ return self.manager.delete(self, cascade=cascade)
+
+ def update(self, **kwargs):
+ """Update the name or description for this volume."""
+ return self.manager.update(self, **kwargs)
+
+ def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None):
+ """Set attachment metadata.
+
+ :param instance_uuid: uuid of the attaching instance.
+ :param mountpoint: mountpoint on the attaching instance or host.
+ :param mode: the access mode.
+ :param host_name: name of the attaching host.
+ """
+ return self.manager.attach(self, instance_uuid, mountpoint, mode,
+ host_name)
+
+ def detach(self):
+ """Clear attachment metadata."""
+ return self.manager.detach(self)
+
+ def reserve(self, volume):
+ """Reserve this volume."""
+ return self.manager.reserve(self)
+
+ def unreserve(self, volume):
+ """Unreserve this volume."""
+ return self.manager.unreserve(self)
+
+ def begin_detaching(self, volume):
+ """Begin detaching volume."""
+ return self.manager.begin_detaching(self)
+
+ def roll_detaching(self, volume):
+ """Roll detaching volume."""
+ return self.manager.roll_detaching(self)
+
+ def initialize_connection(self, volume, connector):
+ """Initialize a volume connection.
+
+ :param connector: connector dict from nova.
+ """
+ return self.manager.initialize_connection(self, connector)
+
+ def terminate_connection(self, volume, connector):
+ """Terminate a volume connection.
+
+ :param connector: connector dict from nova.
+ """
+ return self.manager.terminate_connection(self, connector)
+
+ def set_metadata(self, volume, metadata):
+ """Set or Append metadata to a volume.
+
+ :param volume : The :class: `Volume` to set metadata on
+ :param metadata: A dict of key/value pairs to set
+ """
+ return self.manager.set_metadata(self, metadata)
+
+ def set_image_metadata(self, volume, metadata):
+ """Set a volume's image metadata.
+
+ :param volume : The :class: `Volume` to set metadata on
+ :param metadata: A dict of key/value pairs to set
+ """
+ return self.manager.set_image_metadata(self, volume, metadata)
+
+ def delete_image_metadata(self, volume, keys):
+ """Delete specified keys from volume's image metadata.
+
+ :param volume: The :class:`Volume`.
+ :param keys: A list of keys to be removed.
+ """
+ return self.manager.delete_image_metadata(self, volume, keys)
+
+ def show_image_metadata(self, volume):
+ """Show a volume's image metadata.
+
+ :param volume : The :class: `Volume` where the image metadata
+ associated.
+ """
+ return self.manager.show_image_metadata(self)
+
+ def upload_to_image(self, force, image_name, container_format,
+ disk_format):
+ """Upload a volume to image service as an image."""
+ return self.manager.upload_to_image(self, force, image_name,
+ container_format, disk_format)
+
+ def force_delete(self):
+ """Delete the specified volume ignoring its current state.
+
+ :param volume: The UUID of the volume to force-delete.
+ """
+ return self.manager.force_delete(self)
+
+ def reset_state(self, state, attach_status=None, migration_status=None):
+ """Update the volume with the provided state.
+
+ :param state: The state of the volume to set.
+ :param attach_status: The attach_status of the volume to be set,
+ or None to keep the current status.
+ :param migration_status: The migration_status of the volume to be set,
+ or None to keep the current status.
+ """
+ return self.manager.reset_state(self, state, attach_status,
+ migration_status)
+
+ def extend(self, volume, new_size):
+ """Extend the size of the specified volume.
+
+ :param volume: The UUID of the volume to extend
+ :param new_size: The desired size to extend volume to.
+ """
+ return self.manager.extend(self, new_size)
+
+ def migrate_volume(self, host, force_host_copy, lock_volume):
+ """Migrate the volume to a new host."""
+ return self.manager.migrate_volume(self, host, force_host_copy,
+ lock_volume)
+
+ def retype(self, volume_type, policy):
+ """Change a volume's type."""
+ return self.manager.retype(self, volume_type, policy)
+
+ def update_all_metadata(self, metadata):
+ """Update all metadata of this volume."""
+ return self.manager.update_all_metadata(self, metadata)
+
+ def update_readonly_flag(self, volume, read_only):
+ """Update the read-only access mode flag of the specified volume.
+
+ :param volume: The UUID of the volume to update.
+ :param read_only: The value to indicate whether to update volume to
+ read-only access mode.
+ """
+ return self.manager.update_readonly_flag(self, read_only)
+
+ def manage(self, host, ref, name=None, description=None,
+ volume_type=None, availability_zone=None, metadata=None,
+ bootable=False):
+ """Manage an existing volume."""
+ return self.manager.manage(host=host, ref=ref, name=name,
+ description=description,
+ volume_type=volume_type,
+ availability_zone=availability_zone,
+ metadata=metadata, bootable=bootable)
+
+ def unmanage(self, volume):
+ """Unmanage a volume."""
+ return self.manager.unmanage(volume)
+
+ def promote(self, volume):
+ """Promote secondary to be primary in relationship."""
+ return self.manager.promote(volume)
+
+ def reenable(self, volume):
+ """Sync the secondary volume with primary for a relationship."""
+ return self.manager.reenable(volume)
+
+ def get_pools(self, detail):
+ """Show pool information for backends."""
+ return self.manager.get_pools(detail)
+
+
+class VolumeManager(base.ManagerWithFind):
+ """Manage :class:`Volume` resources."""
+ resource_class = Volume
+
+ def create(self, size, consistencygroup_id=None, snapshot_id=None,
+ source_volid=None, name=None, description=None,
+ volume_type=None, user_id=None,
+ project_id=None, availability_zone=None,
+ metadata=None, imageRef=None, scheduler_hints=None,
+ source_replica=None, multiattach=False):
+ """Create a volume.
+
+ :param size: Size of volume in GB
+ :param consistencygroup_id: ID of the consistencygroup
+ :param snapshot_id: ID of the snapshot
+ :param name: Name of the volume
+ :param description: Description of the volume
+ :param volume_type: Type of 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
+ :param source_replica: ID of source volume to clone replica
+ :param scheduler_hints: (optional extension) arbitrary key-value pairs
+ specified by the client to help boot an instance
+ :param multiattach: Allow the volume to be attached to more than
+ one instance
+ :rtype: :class:`Volume`
+ """
+ if metadata is None:
+ volume_metadata = {}
+ else:
+ volume_metadata = metadata
+
+ body = {'volume': {'size': size,
+ 'consistencygroup_id': consistencygroup_id,
+ 'snapshot_id': snapshot_id,
+ 'name': name,
+ 'description': description,
+ 'volume_type': volume_type,
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'availability_zone': availability_zone,
+ 'status': "creating",
+ 'attach_status': "detached",
+ 'metadata': volume_metadata,
+ 'imageRef': imageRef,
+ 'source_volid': source_volid,
+ 'source_replica': source_replica,
+ 'multiattach': multiattach,
+ }}
+
+ if scheduler_hints:
+ body['OS-SCH-HNT:scheduler_hints'] = scheduler_hints
+
+ return self._create('/volumes', body, 'volume')
+
+ def get(self, volume_id):
+ """Get a volume.
+
+ :param volume_id: The ID of the volume to get.
+ :rtype: :class:`Volume`
+ """
+ return self._get("/volumes/%s" % volume_id, "volume")
+
+ def list(self, detailed=True, search_opts=None, marker=None, limit=None,
+ sort_key=None, sort_dir=None, sort=None):
+ """Lists all volumes.
+
+ :param detailed: Whether to return detailed volume info.
+ :param search_opts: Search options to filter out volumes.
+ :param marker: Begin returning volumes that appear later in the volume
+ list than that represented by this volume id.
+ :param limit: Maximum number of volumes to return.
+ :param sort_key: Key to be sorted; deprecated in kilo
+ :param sort_dir: Sort direction, should be 'desc' or 'asc'; deprecated
+ in kilo
+ :param sort: Sort information
+ :rtype: list of :class:`Volume`
+ """
+
+ resource_type = "volumes"
+ url = self._build_list_url(resource_type, detailed=detailed,
+ search_opts=search_opts, marker=marker,
+ limit=limit, sort_key=sort_key,
+ sort_dir=sort_dir, sort=sort)
+ return self._list(url, resource_type, limit=limit)
+
+ def delete(self, volume, cascade=False):
+ """Delete a volume.
+
+ :param volume: The :class:`Volume` to delete.
+ :param cascade: Also delete dependent snapshots.
+ """
+
+ loc = "/volumes/%s" % base.getid(volume)
+
+ if cascade:
+ loc += '?cascade=True'
+
+ return self._delete(loc)
+
+ def update(self, volume, **kwargs):
+ """Update the name or description for a volume.
+
+ :param volume: The :class:`Volume` to update.
+ """
+ if not kwargs:
+ return
+
+ body = {"volume": kwargs}
+
+ return self._update("/volumes/%s" % base.getid(volume), body)
+
+ def _action(self, action, volume, info=None, **kwargs):
+ """Perform a volume "action."
+ """
+ body = {action: info}
+ self.run_hooks('modify_body_for_action', body, **kwargs)
+ url = '/volumes/%s/action' % base.getid(volume)
+ resp, body = self.api.client.post(url, body=body)
+ return common_base.TupleWithMeta((resp, body), resp)
+
+ def attach(self, volume, instance_uuid, mountpoint, mode='rw',
+ host_name=None):
+ """Set attachment metadata.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to attach.
+ :param instance_uuid: uuid of the attaching instance.
+ :param mountpoint: mountpoint on the attaching instance or host.
+ :param mode: the access mode.
+ :param host_name: name of the attaching host.
+ """
+ body = {'mountpoint': mountpoint, 'mode': mode}
+ if instance_uuid is not None:
+ body.update({'instance_uuid': instance_uuid})
+ if host_name is not None:
+ body.update({'host_name': host_name})
+ return self._action('os-attach', volume, body)
+
+ def detach(self, volume, attachment_uuid=None):
+ """Clear attachment metadata.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to detach.
+ :param attachment_uuid: The uuid of the volume attachment.
+ """
+ return self._action('os-detach', volume,
+ {'attachment_id': attachment_uuid})
+
+ def reserve(self, volume):
+ """Reserve this volume.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to reserve.
+ """
+ return self._action('os-reserve', volume)
+
+ def unreserve(self, volume):
+ """Unreserve this volume.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to unreserve.
+ """
+ return self._action('os-unreserve', volume)
+
+ def begin_detaching(self, volume):
+ """Begin detaching this volume.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to detach.
+ """
+ return self._action('os-begin_detaching', volume)
+
+ def roll_detaching(self, volume):
+ """Roll detaching this volume.
+
+ :param volume: The :class:`Volume` (or its ID)
+ you would like to roll detaching.
+ """
+ return self._action('os-roll_detaching', volume)
+
+ def initialize_connection(self, volume, connector):
+ """Initialize a volume connection.
+
+ :param volume: The :class:`Volume` (or its ID).
+ :param connector: connector dict from nova.
+ """
+ resp, body = self._action('os-initialize_connection', volume,
+ {'connector': connector})
+ return common_base.DictWithMeta(body['connection_info'], resp)
+
+ def terminate_connection(self, volume, connector):
+ """Terminate a volume connection.
+
+ :param volume: The :class:`Volume` (or its ID).
+ :param connector: connector dict from nova.
+ """
+ return self._action('os-terminate_connection', volume,
+ {'connector': connector})
+
+ def set_metadata(self, volume, metadata):
+ """Update/Set a volumes metadata.
+
+ :param volume: The :class:`Volume`.
+ :param metadata: A list of keys to be set.
+ """
+ body = {'metadata': metadata}
+ return self._create("/volumes/%s/metadata" % base.getid(volume),
+ body, "metadata")
+
+ def delete_metadata(self, volume, keys):
+ """Delete specified keys from volumes metadata.
+
+ :param volume: The :class:`Volume`.
+ :param keys: A list of keys to be removed.
+ """
+ response_list = []
+ for k in keys:
+ resp, body = self._delete("/volumes/%s/metadata/%s" %
+ (base.getid(volume), k))
+ response_list.append(resp)
+
+ return common_base.ListWithMeta([], response_list)
+
+ def set_image_metadata(self, volume, metadata):
+ """Set a volume's image metadata.
+
+ :param volume: The :class:`Volume`.
+ :param metadata: keys and the values to be set with.
+ :type metadata: dict
+ """
+ return self._action("os-set_image_metadata", volume,
+ {'metadata': metadata})
+
+ def delete_image_metadata(self, volume, keys):
+ """Delete specified keys from volume's image metadata.
+
+ :param volume: The :class:`Volume`.
+ :param keys: A list of keys to be removed.
+ """
+ response_list = []
+ for key in keys:
+ resp, body = self._action("os-unset_image_metadata", volume,
+ {'key': key})
+ response_list.append(resp)
+
+ return common_base.ListWithMeta([], response_list)
+
+ def show_image_metadata(self, volume):
+ """Show a volume's image metadata.
+
+ :param volume : The :class: `Volume` where the image metadata
+ associated.
+ """
+ return self._action("os-show_image_metadata", volume)
+
+ def upload_to_image(self, volume, force, image_name, container_format,
+ disk_format):
+ """Upload volume to image service as image.
+
+ :param volume: The :class:`Volume` to upload.
+ """
+ return self._action('os-volume_upload_image',
+ volume,
+ {'force': force,
+ 'image_name': image_name,
+ 'container_format': container_format,
+ 'disk_format': disk_format})
+
+ def force_delete(self, volume):
+ """Delete the specified volume ignoring its current state.
+
+ :param volume: The :class:`Volume` to force-delete.
+ """
+ return self._action('os-force_delete', base.getid(volume))
+
+ def reset_state(self, volume, state, attach_status=None,
+ migration_status=None):
+ """Update the provided volume with the provided state.
+
+ :param volume: The :class:`Volume` to set the state.
+ :param state: The state of the volume to be set.
+ :param attach_status: The attach_status of the volume to be set,
+ or None to keep the current status.
+ :param migration_status: The migration_status of the volume to be set,
+ or None to keep the current status.
+ """
+ body = {'status': state}
+ if attach_status:
+ body.update({'attach_status': attach_status})
+ if migration_status:
+ body.update({'migration_status': migration_status})
+ return self._action('os-reset_status', volume, body)
+
+ def extend(self, volume, new_size):
+ """Extend the size of the specified volume.
+
+ :param volume: The UUID of the volume to extend.
+ :param new_size: The requested size to extend volume to.
+ """
+ return self._action('os-extend',
+ base.getid(volume),
+ {'new_size': new_size})
+
+ def get_encryption_metadata(self, volume_id):
+ """
+ Retrieve the encryption metadata from the desired volume.
+
+ :param volume_id: the id of the volume to query
+ :return: a dictionary of volume encryption metadata
+ """
+ metadata = self._get("/volumes/%s/encryption" % volume_id)
+ return common_base.DictWithMeta(metadata._info, metadata.request_ids)
+
+ def migrate_volume(self, volume, host, force_host_copy, lock_volume):
+ """Migrate volume to new host.
+
+ :param volume: The :class:`Volume` to migrate
+ :param host: The destination host
+ :param force_host_copy: Skip driver optimizations
+ :param lock_volume: Lock the volume and guarantee the migration
+ to finish
+ """
+ return self._action('os-migrate_volume',
+ volume,
+ {'host': host, 'force_host_copy': force_host_copy,
+ 'lock_volume': lock_volume})
+
+ def migrate_volume_completion(self, old_volume, new_volume, error):
+ """Complete the migration from the old volume to the temp new one.
+
+ :param old_volume: The original :class:`Volume` in the migration
+ :param new_volume: The new temporary :class:`Volume` in the migration
+ :param error: Inform of an error to cause migration cleanup
+ """
+ new_volume_id = base.getid(new_volume)
+ resp, body = self._action('os-migrate_volume_completion', old_volume,
+ {'new_volume': new_volume_id,
+ 'error': error})
+ return common_base.DictWithMeta(body, resp)
+
+ def update_all_metadata(self, volume, metadata):
+ """Update all metadata of a volume.
+
+ :param volume: The :class:`Volume`.
+ :param metadata: A list of keys to be updated.
+ """
+ body = {'metadata': metadata}
+ return self._update("/volumes/%s/metadata" % base.getid(volume),
+ body)
+
+ def update_readonly_flag(self, volume, flag):
+ return self._action('os-update_readonly_flag',
+ base.getid(volume),
+ {'readonly': flag})
+
+ def retype(self, volume, volume_type, policy):
+ """Change a volume's type.
+
+ :param volume: The :class:`Volume` to retype
+ :param volume_type: New volume type
+ :param policy: Policy for migration during the retype
+ """
+ return self._action('os-retype',
+ volume,
+ {'new_type': volume_type,
+ 'migration_policy': policy})
+
+ def set_bootable(self, volume, flag):
+ return self._action('os-set_bootable',
+ base.getid(volume),
+ {'bootable': flag})
+
+ def manage(self, host, ref, name=None, description=None,
+ volume_type=None, availability_zone=None, metadata=None,
+ bootable=False):
+ """Manage an existing volume."""
+ body = {'volume': {'host': host,
+ 'ref': ref,
+ 'name': name,
+ 'description': description,
+ 'volume_type': volume_type,
+ 'availability_zone': availability_zone,
+ 'metadata': metadata,
+ 'bootable': bootable
+ }}
+ return self._create('/os-volume-manage', body, 'volume')
+
+ def unmanage(self, volume):
+ """Unmanage a volume."""
+ return self._action('os-unmanage', volume, None)
+
+ def promote(self, volume):
+ """Promote secondary to be primary in relationship."""
+ return self._action('os-promote-replica', volume, None)
+
+ def reenable(self, volume):
+ """Sync the secondary volume with primary for a relationship."""
+ return self._action('os-reenable-replica', volume, None)
+
+ def get_pools(self, detail):
+ """Show pool information for backends."""
+ query_string = ""
+ if detail:
+ query_string = "?detail=True"
+
+ return self._get('/scheduler-stats/get_pools%s' % query_string, None)