Merge "Refactor v2 and v3 APIs support"

This commit is contained in:
Jenkins
2016-12-21 22:24:47 +00:00
committed by Gerrit Code Review
45 changed files with 4597 additions and 4010 deletions

247
cinderclient/shell_utils.py Normal file
View File

@@ -0,0 +1,247 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import time
from cinderclient import utils
_quota_resources = ['volumes', 'snapshots', 'gigabytes',
'backups', 'backup_gigabytes',
'consistencygroups', 'per_volume_gigabytes',
'groups', ]
_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
def print_volume_image(image):
utils.print_dict(image[1]['os-volume_upload_image'])
def poll_for_status(poll_fn, obj_id, action, final_ok_states,
poll_period=5, show_progress=True):
"""Blocks while an action occurs. Periodically shows progress."""
def print_progress(progress):
if show_progress:
msg = ('\rInstance %(action)s... %(progress)s%% complete'
% dict(action=action, progress=progress))
else:
msg = '\rInstance %(action)s...' % dict(action=action)
sys.stdout.write(msg)
sys.stdout.flush()
print()
while True:
obj = poll_fn(obj_id)
status = obj.status.lower()
progress = getattr(obj, 'progress', None) or 0
if status in final_ok_states:
print_progress(100)
print("\nFinished")
break
elif status == "error":
print("\nError %(action)s instance" % {'action': action})
break
else:
print_progress(progress)
time.sleep(poll_period)
def find_volume_snapshot(cs, snapshot):
"""Gets a volume snapshot by name or ID."""
return utils.find_resource(cs.volume_snapshots, snapshot)
def find_vtype(cs, vtype):
"""Gets a volume type by name or ID."""
return utils.find_resource(cs.volume_types, vtype)
def find_gtype(cs, gtype):
"""Gets a group type by name or ID."""
return utils.find_resource(cs.group_types, gtype)
def find_backup(cs, backup):
"""Gets a backup by name or ID."""
return utils.find_resource(cs.backups, backup)
def find_consistencygroup(cs, consistencygroup):
"""Gets a consistencygroup by name or ID."""
return utils.find_resource(cs.consistencygroups, consistencygroup)
def find_group(cs, group):
"""Gets a group by name or ID."""
return utils.find_resource(cs.groups, group)
def find_cgsnapshot(cs, cgsnapshot):
"""Gets a cgsnapshot by name or ID."""
return utils.find_resource(cs.cgsnapshots, cgsnapshot)
def find_group_snapshot(cs, group_snapshot):
"""Gets a group_snapshot by name or ID."""
return utils.find_resource(cs.group_snapshots, group_snapshot)
def find_transfer(cs, transfer):
"""Gets a transfer by name or ID."""
return utils.find_resource(cs.transfers, transfer)
def find_qos_specs(cs, qos_specs):
"""Gets a qos specs by ID."""
return utils.find_resource(cs.qos_specs, qos_specs)
def find_message(cs, message):
"""Gets a message by ID."""
return utils.find_resource(cs.messages, message)
def print_volume_snapshot(snapshot):
utils.print_dict(snapshot._info)
def translate_keys(collection, convert):
for item in collection:
keys = item.__dict__
for from_key, to_key in convert:
if from_key in keys and to_key not in keys:
setattr(item, to_key, item._info[from_key])
def translate_volume_keys(collection):
convert = [('volumeType', 'volume_type'),
('os-vol-tenant-attr:tenant_id', 'tenant_id')]
translate_keys(collection, convert)
def translate_volume_snapshot_keys(collection):
convert = [('volumeId', 'volume_id')]
translate_keys(collection, convert)
def translate_availability_zone_keys(collection):
convert = [('zoneName', 'name'), ('zoneState', 'status')]
translate_keys(collection, convert)
def extract_metadata(args, type='user_metadata'):
metadata = {}
if type == 'image_metadata':
args_metadata = args.image_metadata
else:
args_metadata = args.metadata
for metadatum in args_metadata:
# unset doesn't require a val, so we have the if/else
if '=' in metadatum:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
return metadata
def print_volume_type_list(vtypes):
utils.print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public'])
def print_group_type_list(gtypes):
utils.print_list(gtypes, ['ID', 'Name', 'Description'])
def quota_show(quotas):
quota_dict = {}
for resource in quotas._info:
good_name = False
for name in _quota_resources:
if resource.startswith(name):
good_name = True
if not good_name:
continue
quota_dict[resource] = getattr(quotas, resource, None)
utils.print_dict(quota_dict)
def quota_usage_show(quotas):
quota_list = []
for resource in quotas._info.keys():
good_name = False
for name in _quota_resources:
if resource.startswith(name):
good_name = True
if not good_name:
continue
quota_info = getattr(quotas, resource, None)
quota_info['Type'] = resource
quota_info = dict((k.capitalize(), v) for k, v in quota_info.items())
quota_list.append(quota_info)
utils.print_list(quota_list, _quota_infos)
def quota_update(manager, identifier, args):
updates = {}
for resource in _quota_resources:
val = getattr(args, resource, None)
if val is not None:
if args.volume_type:
resource = resource + '_%s' % args.volume_type
updates[resource] = val
if updates:
quota_show(manager.update(identifier, **updates))
def find_volume_type(cs, vtype):
"""Gets a volume type by name or ID."""
return utils.find_resource(cs.volume_types, vtype)
def find_group_type(cs, gtype):
"""Gets a group type by name or ID."""
return utils.find_resource(cs.group_types, gtype)
def print_volume_encryption_type_list(encryption_types):
"""
Lists volume encryption types.
:param encryption_types: a list of :class: VolumeEncryptionType instances
"""
utils.print_list(encryption_types, ['Volume Type ID', 'Provider',
'Cipher', 'Key Size',
'Control Location'])
def print_qos_specs(qos_specs):
# formatters defines field to be converted from unicode to string
utils.print_dict(qos_specs._info, formatters=['specs'])
def print_qos_specs_list(q_specs):
utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
def print_qos_specs_and_associations_list(q_specs):
utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
def print_associations_list(associations):
utils.print_list(associations, ['Association_Type', 'Name', 'ID'])

View File

@@ -46,8 +46,8 @@ class AvailabilityZoneTest(utils.FixturedTestCase):
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('zone-2'), six.u('not available')]
z0 = shell._treeizeAvailabilityZone(zones[0])
z1 = shell._treeizeAvailabilityZone(zones[1])
z0 = shell.treeizeAvailabilityZone(zones[0])
z1 = shell.treeizeAvailabilityZone(zones[1])
self.assertEqual((1, 1), (len(z0), len(z1)))
@@ -75,9 +75,9 @@ class AvailabilityZoneTest(utils.FixturedTestCase):
six.u('enabled :-) 2012-12-26 14:45:24')]
l6 = [six.u('zone-2'), six.u('not available')]
z0 = shell._treeizeAvailabilityZone(zones[0])
z1 = shell._treeizeAvailabilityZone(zones[1])
z2 = shell._treeizeAvailabilityZone(zones[2])
z0 = shell.treeizeAvailabilityZone(zones[0])
z1 = shell.treeizeAvailabilityZone(zones[1])
z2 = shell.treeizeAvailabilityZone(zones[2])
self.assertEqual((3, 3, 1), (len(z0), len(z1), len(z2)))

View File

@@ -83,8 +83,3 @@ class ServicesTest(utils.TestCase):
self.assertIsInstance(s, services.Service)
self.assertEqual('disabled', s.status)
self._assert_request_id(s)
def test_api_version(self):
client = fakes.FakeClient(version_header='3.0')
svs = client.services.server_api_version()
[self.assertIsInstance(s, services.Service) for s in svs]

View File

@@ -455,14 +455,14 @@ class ShellTest(utils.TestCase):
'restore_vol')
@ddt.data('backup_name', '1234')
@mock.patch('cinderclient.v3.shell._find_backup')
@mock.patch('cinderclient.shell_utils.find_backup')
@mock.patch('cinderclient.utils.print_dict')
@mock.patch('cinderclient.utils.find_volume')
def test_do_backup_restore_with_name(self,
value,
mock_find_volume,
mock_print_dict,
mock_find_backup):
value,
mock_find_volume,
mock_print_dict,
mock_find_backup):
backup_id = '1234'
volume_id = '5678'
name = None
@@ -477,11 +477,11 @@ class ShellTest(utils.TestCase):
'restore') as mocked_restore:
mock_find_volume.return_value = volumes.Volume(self,
{'id': volume_id},
loaded = True)
loaded=True)
mock_find_backup.return_value = volume_backups.VolumeBackup(
self,
{'id': backup_id},
loaded = True)
loaded=True)
test_shell.do_backup_restore(self.cs, args)
mock_find_backup.assert_called_once_with(
self.cs,

View File

@@ -178,12 +178,9 @@ class VolumesTest(utils.TestCase):
self._assert_request_id(vol)
def test_delete_metadata(self):
volume = Volume(self, {'id': '1234', 'metadata': {
'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}})
keys = ['k1', 'k3']
vol = cs.volumes.delete_metadata(volume, keys)
cs.assert_called('PUT', '/volumes/1234/metadata',
{'metadata': {'k2': 'v2'}})
keys = ['key1']
vol = cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')
self._assert_request_id(vol)
def test_extend(self):

View File

@@ -21,6 +21,11 @@ from cinderclient import api_versions
class ServicesTest(utils.TestCase):
def test_api_version(self):
client = fakes.FakeClient(version_header='3.0')
svs = client.services.server_api_version()
[self.assertIsInstance(s, services.Service) for s in svs]
def test_list_services_with_cluster_info(self):
cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.7'))
services_list = cs.services.list()

View File

@@ -14,4 +14,4 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2.client import Client # noqa
from cinderclient.v2.client import Client # noqa

View File

@@ -15,5 +15,27 @@
# under the License.
"""Availability Zone interface (v2 extension)"""
from cinderclient.v3.availability_zones import * # flake8: noqa
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")

View File

@@ -15,5 +15,24 @@
"""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._info['namespace']
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)

View File

@@ -15,5 +15,98 @@
"""cgsnapshot interface (v2 extension)."""
from cinderclient.v3.cgsnapshots import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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)

View File

@@ -15,5 +15,135 @@
"""Consistencygroup interface (v2 extension)."""
from cinderclient.v3.consistencygroups import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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)

View File

@@ -37,11 +37,15 @@ class ListExtManager(base.Manager):
return self._list("/extensions", 'extensions')
@utils.service_type('volumev2')
def do_list_extensions(client, _args):
def 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)
@utils.service_type('volumev2')
def do_list_extensions(client, _args):
return list_extensions(client, _args)

View File

@@ -14,5 +14,87 @@
# limitations under the License.
"""Limits interface (v2 extension)"""
from cinderclient.v3.limits import * # flake8: noqa
from six.moves.urllib import parse
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, tenant_id=None):
"""Get a specific extension.
:rtype: :class:`Limits`
"""
opts = {}
if tenant_id:
opts['tenant_id'] = tenant_id
query_string = "?%s" % parse.urlencode(opts) if opts else ""
return self._get("/limits%s" % query_string, "limits")

View File

@@ -15,5 +15,46 @@
"""Pools interface (v2 extension)"""
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 pool.capabilities.items():
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

View File

@@ -18,5 +18,138 @@
QoS Specs interface.
"""
from cinderclient.v3.qos_specs import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import 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)

View File

@@ -13,5 +13,34 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v3.quota_classes import * # flake8: noqa
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)

View File

@@ -13,5 +13,44 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v3.quotas import * # flake8: noqa
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)

View File

@@ -17,5 +17,64 @@
service interface
"""
from cinderclient.v3.services import * # flake8: noqa
from cinderclient import base
class Service(base.Resource):
def __repr__(self):
return "<Service: binary=%s host=%s>" % (self.binary, self.host)
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

View File

@@ -17,5 +17,117 @@
Volume Backups interface (v2 extension).
"""
from cinderclient.v3.volume_backups import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import 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, force=False):
"""Delete this volume backup."""
return self.manager.delete(self, force)
def reset_state(self, state):
return self.manager.reset_state(self, state)
def update(self, **kwargs):
"""Update the name or description for this backup."""
return self.manager.update(self, **kwargs)
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, force=False):
"""Delete a volume backup.
:param backup: The :class:`VolumeBackup` to delete.
:param force: Allow delete in state other than error or available.
"""
if force:
return self._action('os-force_delete', backup)
else:
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)

View File

@@ -18,5 +18,27 @@
This is part of the Volume Backups interface.
"""
from cinderclient.v3.volume_backups_restore import * # flake8: noqa
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, name=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.
:param name : The name for new volume creation to restore.
:rtype: :class:`Restore`
"""
body = {'restore': {'volume_id': volume_id, 'name': name}}
return self._create("/backups/%s/restore" % backup_id,
body, "restore")

View File

@@ -17,5 +17,87 @@
Volume Encryption Type interface
"""
from cinderclient.v3.volume_encryption_types import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import 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.encryption_id
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))

View File

@@ -17,5 +17,72 @@
Volume transfer interface (v2 extension).
"""
from cinderclient.v3.volume_transfers import * # flake8: noqa
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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))

View File

@@ -14,5 +14,40 @@
"""Volume type access interface."""
from cinderclient.v3.volume_type_access import * # flake8: noqa
from cinderclient.apiclient import base as common_base
from cinderclient import 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)

View File

@@ -15,5 +15,139 @@
"""Volume Type interface."""
from cinderclient.v3.volume_types import * # flake8: noqa
from cinderclient.apiclient import base as common_base
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 only ONE key being unset,
# since on success the return was ListWithMeta class, we'll only
# interrupt the loop and if an exception is raised.
response_list = []
for k in keys:
resp, body = self.manager._delete(
"/types/%s/extra_specs/%s" % (
base.getid(self), k))
response_list.append(resp)
return common_base.ListWithMeta([], response_list)
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 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 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")

View File

@@ -15,22 +15,460 @@
"""Volume interface (v2 extension)."""
from cinderclient import api_versions
from cinderclient.v3 import volumes
from cinderclient.apiclient import base as common_base
from cinderclient import base
class Volume(volumes.Volume):
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."""
disk_format, visibility=None,
protected=None):
"""Upload a volume to image service as an image.
:param force: Boolean to enables or disables upload of a volume that
is attached to an instance.
:param image_name: The new image name.
:param container_format: Container format type.
:param disk_format: Disk format type.
:param visibility: The accessibility of image (allowed for
3.1-latest).
:param protected: Boolean to decide whether prevents image from being
deleted (allowed for 3.1-latest).
"""
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.
class VolumeManager(volumes.VolumeManager):
: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 list_manageable(self, host, detailed=True, marker=None, limit=None,
offset=None, sort=None):
return self.manager.list_manageable(host, detailed=detailed,
marker=marker, limit=limit,
offset=offset, sort=sort)
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
@api_versions.wraps("2.0")
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.
@@ -44,10 +482,148 @@ class VolumeManager(volumes.VolumeManager):
'container_format': container_format,
'disk_format': disk_format})
@api_versions.wraps("2.0")
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 state else {}
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 list_manageable(self, host, detailed=True, marker=None, limit=None,
offset=None, sort=None):
url = self._build_list_url("os-volume-manage", detailed=detailed,
search_opts={'host': host}, marker=marker,
limit=limit, offset=offset, sort=sort)
return self._list(url, "manageable-volumes")
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)

View File

@@ -14,4 +14,4 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v3.client import Client # noqa
from cinderclient.v3.client import Client # noqa

View File

@@ -16,27 +16,4 @@
"""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")
from cinderclient.v2.availability_zones import * # flake8: noqa

View File

@@ -16,24 +16,4 @@
"""Capabilities interface (v3 extension)"""
from cinderclient import base
class Capabilities(base.Resource):
NAME_ATTR = 'name'
def __repr__(self):
return "<Capabilities: %s>" % self._info['namespace']
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)
from cinderclient.v2.capabilities import * # flake8: noqa

View File

@@ -15,99 +15,4 @@
"""cgsnapshot interface (v3 extension)."""
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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)
from cinderclient.v2.cgsnapshots import * # flake8: noqa

View File

@@ -15,135 +15,4 @@
"""Consistencygroup interface (v3 extension)."""
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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)
from cinderclient.v2.consistencygroups import * # flake8: noqa

View File

@@ -13,28 +13,8 @@
# 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')
from cinderclient.v2.contrib.list_extensions import * # flake8: noqa
@utils.service_type('volumev3')
@@ -42,6 +22,4 @@ 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)
return list_extensions(client, _args)

View File

@@ -13,87 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from six.moves.urllib import parse
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, tenant_id=None):
"""Get a specific extension.
:rtype: :class:`Limits`
"""
opts = {}
if tenant_id:
opts['tenant_id'] = tenant_id
query_string = "?%s" % parse.urlencode(opts) if opts else ""
return self._get("/limits%s" % query_string, "limits")
from cinderclient.v2.limits import * # flake8: noqa

View File

@@ -15,46 +15,4 @@
"""Pools interface (v3 extension)"""
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 pool.capabilities.items():
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
from cinderclient.v2.pools import * # flake8: noqa

View File

@@ -19,138 +19,4 @@
QoS Specs interface.
"""
from cinderclient.apiclient import base as common_base
from cinderclient import 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)
from cinderclient.v2.qos_specs import * # flake8: noqa

View File

@@ -13,34 +13,4 @@
# 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)
from cinderclient.v2.quota_classes import * # flake8: noqa

View File

@@ -13,44 +13,4 @@
# 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)
from cinderclient.v2.quotas import * # flake8: noqa

View File

@@ -16,69 +16,14 @@
"""
service interface
"""
from cinderclient import api_versions
from cinderclient import base
from cinderclient.v2 import services
Service = services.Service
class Service(base.Resource):
def __repr__(self):
return "<Service: binary=%s host=%s>" % (self.binary, self.host)
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)
class ServiceManager(services.ServiceManager):
@api_versions.wraps("3.0")
def server_api_version(self, url_append=""):
"""Returns the API Version supported by the server.

File diff suppressed because it is too large Load Diff

View File

@@ -16,122 +16,16 @@
"""
Volume Backups interface (v3 extension).
"""
from cinderclient import api_versions
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient.v2 import volume_backups
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, force=False):
"""Delete this volume backup."""
return self.manager.delete(self, force)
def reset_state(self, state):
return self.manager.reset_state(self, state)
def update(self, **kwargs):
"""Update the name or description for this backup."""
return self.manager.update(self, **kwargs)
VolumeBackup = volume_backups.VolumeBackup
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, force=False):
"""Delete a volume backup.
:param backup: The :class:`VolumeBackup` to delete.
:param force: Allow delete in state other than error or available.
"""
if force:
return self._action('os-force_delete', backup)
else:
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)
class VolumeBackupManager(volume_backups.VolumeBackupManager):
@api_versions.wraps("3.9")
def update(self, backup, **kwargs):
"""Update the name or description for a backup.

View File

@@ -18,27 +18,4 @@
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, name=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.
:param name : The name for new volume creation to restore.
:rtype: :class:`Restore`
"""
body = {'restore': {'volume_id': volume_id, 'name': name}}
return self._create("/backups/%s/restore" % backup_id,
body, "restore")
from cinderclient.v2.volume_backups_restore import * # flake8: noqa

View File

@@ -18,87 +18,4 @@
Volume Encryption Type interface
"""
from cinderclient.apiclient import base as common_base
from cinderclient import 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.encryption_id
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))
from cinderclient.v2.volume_encryption_types import * # flake8: noqa

View File

@@ -17,72 +17,4 @@
Volume transfer interface (v3 extension).
"""
from cinderclient import base
from cinderclient import utils
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`
"""
query_string = utils.build_query_param(search_opts)
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))
from cinderclient.v2.volume_transfers import * # flake8: noqa

View File

@@ -14,40 +14,4 @@
"""Volume type access interface."""
from cinderclient.apiclient import base as common_base
from cinderclient import 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)
from cinderclient.v2.volume_type_access import * # flake8: noqa

View File

@@ -16,99 +16,11 @@
"""Volume interface (v3 extension)."""
from cinderclient import api_versions
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient.v2 import volumes
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)
class Volume(volumes.Volume):
def upload_to_image(self, force, image_name, container_format,
disk_format, visibility=None,
@@ -130,94 +42,11 @@ class Volume(base.Resource):
return self.manager.upload_to_image(self, force, image_name,
container_format, disk_format,
visibility, protected)
else:
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 list_manageable(self, host, detailed=True, marker=None, limit=None,
offset=None, sort=None):
return self.manager.list_manageable(host, detailed=detailed,
marker=marker, limit=limit,
offset=offset, sort=sort)
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)
return self.manager.upload_to_image(self, force, image_name,
container_format, disk_format)
class VolumeManager(base.ManagerWithFind):
"""Manage :class:`Volume` resources."""
class VolumeManager(volumes.VolumeManager):
resource_class = Volume
def create(self, size, consistencygroup_id=None,
@@ -280,176 +109,6 @@ class VolumeManager(base.ManagerWithFind):
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")
@api_versions.wraps("2.0")
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)
@api_versions.wraps("3.15")
def delete_metadata(self, volume, keys):
"""Delete specified keys from volumes metadata.
@@ -467,186 +126,33 @@ class VolumeManager(base.ManagerWithFind):
return self._update("/volumes/%s/metadata" % base.getid(volume),
body, **kwargs)
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)
@api_versions.wraps("3.0")
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})
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format})
@api_versions.wraps("3.1")
def upload_to_image(self, volume, force, image_name, container_format,
disk_format, visibility, protected):
"""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,
'visibility': visibility,
'protected': protected})
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 state else {}
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')
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format,
'visibility': visibility,
'protected': protected})
@api_versions.wraps("3.8")
def list_manageable(self, host, detailed=True, marker=None, limit=None,
@@ -655,23 +161,3 @@ class VolumeManager(base.ManagerWithFind):
search_opts={'host': host}, marker=marker,
limit=limit, offset=offset, sort=sort)
return self._list(url, "manageable-volumes")
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)