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
This commit is contained in:
parent
6e0b5f451c
commit
27e6f6f7f8
@ -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)]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
0
cinderclient/tests/unit/v3/__init__.py
Normal file
0
cinderclient/tests/unit/v3/__init__.py
Normal file
36
cinderclient/tests/unit/v3/fakes.py
Normal file
36
cinderclient/tests/unit/v3/fakes.py
Normal file
@ -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'
|
73
cinderclient/tests/unit/v3/test_shell.py
Normal file
73
cinderclient/tests/unit/v3/test_shell.py
Normal file
@ -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')
|
@ -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)
|
||||
|
@ -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
|
||||
from cinderclient.v3.availability_zones import * # flake8: noqa
|
||||
|
||||
|
||||
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")
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
from cinderclient.v3.qos_specs import * # flake8: noqa
|
||||
|
||||
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
from cinderclient.v3.volume_encryption_types import * # flake8: noqa
|
||||
|
||||
|
||||
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))
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
from cinderclient.v3.volume_types import * # flake8: noqa
|
||||
|
||||
|
||||
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")
|
||||
|
@ -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)
|
||||
|
17
cinderclient/v3/__init__.py
Normal file
17
cinderclient/v3/__init__.py
Normal file
@ -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
|
42
cinderclient/v3/availability_zones.py
Normal file
42
cinderclient/v3/availability_zones.py
Normal file
@ -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")
|
39
cinderclient/v3/capabilities.py
Normal file
39
cinderclient/v3/capabilities.py
Normal file
@ -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)
|
126
cinderclient/v3/cgsnapshots.py
Normal file
126
cinderclient/v3/cgsnapshots.py
Normal file
@ -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)
|
131
cinderclient/v3/client.py
Normal file
131
cinderclient/v3/client.py
Normal file
@ -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()
|
162
cinderclient/v3/consistencygroups.py
Normal file
162
cinderclient/v3/consistencygroups.py
Normal file
@ -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)
|
0
cinderclient/v3/contrib/__init__.py
Normal file
0
cinderclient/v3/contrib/__init__.py
Normal file
47
cinderclient/v3/contrib/list_extensions.py
Normal file
47
cinderclient/v3/contrib/list_extensions.py
Normal file
@ -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)
|
91
cinderclient/v3/limits.py
Normal file
91
cinderclient/v3/limits.py
Normal file
@ -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")
|
62
cinderclient/v3/pools.py
Normal file
62
cinderclient/v3/pools.py
Normal file
@ -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
|
156
cinderclient/v3/qos_specs.py
Normal file
156
cinderclient/v3/qos_specs.py
Normal file
@ -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)
|
46
cinderclient/v3/quota_classes.py
Normal file
46
cinderclient/v3/quota_classes.py
Normal file
@ -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)
|
56
cinderclient/v3/quotas.py
Normal file
56
cinderclient/v3/quotas.py
Normal file
@ -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)
|
79
cinderclient/v3/services.py
Normal file
79
cinderclient/v3/services.py
Normal file
@ -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)
|
2655
cinderclient/v3/shell.py
Normal file
2655
cinderclient/v3/shell.py
Normal file
File diff suppressed because it is too large
Load Diff
124
cinderclient/v3/volume_backups.py
Normal file
124
cinderclient/v3/volume_backups.py
Normal file
@ -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)
|
43
cinderclient/v3/volume_backups_restore.py
Normal file
43
cinderclient/v3/volume_backups_restore.py
Normal file
@ -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")
|
104
cinderclient/v3/volume_encryption_types.py
Normal file
104
cinderclient/v3/volume_encryption_types.py
Normal file
@ -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))
|
205
cinderclient/v3/volume_snapshots.py
Normal file
205
cinderclient/v3/volume_snapshots.py
Normal file
@ -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)
|
101
cinderclient/v3/volume_transfers.py
Normal file
101
cinderclient/v3/volume_transfers.py
Normal file
@ -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))
|
53
cinderclient/v3/volume_type_access.py
Normal file
53
cinderclient/v3/volume_type_access.py
Normal file
@ -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)
|
151
cinderclient/v3/volume_types.py
Normal file
151
cinderclient/v3/volume_types.py
Normal file
@ -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")
|
604
cinderclient/v3/volumes.py
Normal file
604
cinderclient/v3/volumes.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user