Add share group support to Manila client
Manila needs a grouping construct that, like shares, is a 1st-class atomic data type. Our experience with CGs has demonstrated the complexity of adding a grouping capability, yet there are other use cases such as migration, replication, and backup in which some storage controllers could only offer such features on share groups. CGs also highlighted the poor optics of an advanced feature with comparatively little potential for vendor support. And adding new grouping constructs for each new feature is not technically feasible. All of the above may be addressed by generic groups, which we think is a clean extension to the original architecture of Manila. Implements: blueprint manila-share-groups Change-Id: I8e29baed62355fc31caeec9c7a66eaebfcbdf184
This commit is contained in:
parent
ff9963a712
commit
b4250866ea
@ -27,7 +27,7 @@ from manilaclient import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
MAX_VERSION = '2.29'
|
||||
MAX_VERSION = '2.31'
|
||||
MIN_VERSION = '2.0'
|
||||
DEPRECATED_VERSION = '1.0'
|
||||
_VERSIONED_METHOD_MAP = {}
|
||||
|
@ -22,6 +22,7 @@ Base utilities to build API operation managers and objects on top of.
|
||||
import contextlib
|
||||
import hashlib
|
||||
import os
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient.common import cliutils
|
||||
from manilaclient import exceptions
|
||||
@ -174,6 +175,11 @@ class Manager(utils.HookableMixin):
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _build_query_string(self, search_opts):
|
||||
q_string = parse.urlencode(
|
||||
sorted([(k, v) for (k, v) in search_opts.items() if v]))
|
||||
return "?%s" % q_string if q_string else q_string
|
||||
|
||||
|
||||
class ManagerWithFind(Manager):
|
||||
"""Like a `Manager`, but with additional `find()`/`findall()` methods."""
|
||||
|
@ -41,7 +41,7 @@ SNAPSHOT_SORT_KEY_VALUES = (
|
||||
'display_name',
|
||||
)
|
||||
|
||||
CONSISTENCY_GROUP_SORT_KEY_VALUES = (
|
||||
SHARE_GROUP_SORT_KEY_VALUES = (
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
@ -49,10 +49,15 @@ CONSISTENCY_GROUP_SORT_KEY_VALUES = (
|
||||
'user_id',
|
||||
'project_id',
|
||||
'created_at',
|
||||
'source_cgsnapshot_id',
|
||||
'availability_zone',
|
||||
'share_network',
|
||||
'share_network_id',
|
||||
'share_group_type',
|
||||
'share_group_type_id',
|
||||
'source_share_group_snapshot_id',
|
||||
)
|
||||
|
||||
CG_SNAPSHOT_SORT_KEY_VALUES = (
|
||||
SHARE_GROUP_SNAPSHOT_SORT_KEY_VALUES = (
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
@ -60,17 +65,7 @@ CG_SNAPSHOT_SORT_KEY_VALUES = (
|
||||
'user_id',
|
||||
'project_id',
|
||||
'created_at',
|
||||
)
|
||||
|
||||
CG_SNAPSHOT_MEMBERS_SORT_KEY_VALUES = (
|
||||
'id',
|
||||
'name',
|
||||
'created_at',
|
||||
'size',
|
||||
'share_protocol',
|
||||
'project_id',
|
||||
'share_type_id',
|
||||
'cgsnapshot_id',
|
||||
'share_group_id',
|
||||
)
|
||||
|
||||
TASK_STATE_MIGRATION_SUCCESS = 'migration_success'
|
||||
|
@ -654,96 +654,85 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
||||
}
|
||||
return (200, {}, pools)
|
||||
|
||||
def get_consistency_groups_detail(self, **kw):
|
||||
consistency_groups = {
|
||||
'consistency_groups': [
|
||||
{
|
||||
'id': 1234,
|
||||
'status': 'available',
|
||||
'name': 'cgname',
|
||||
'description': 'my cg'
|
||||
}
|
||||
]
|
||||
}
|
||||
return (200, {}, consistency_groups)
|
||||
fake_share_group = {
|
||||
'id': '1234',
|
||||
'availability_zone': 'nova',
|
||||
'share_network_id': None,
|
||||
'status': 'available',
|
||||
'name': 'share group name',
|
||||
'description': 'my share group',
|
||||
}
|
||||
|
||||
def delete_consistency_groups_1234(self, **kw):
|
||||
return (202, {}, None)
|
||||
def get_share_groups_detail(self, **kw):
|
||||
share_groups = {'share_groups': [self.fake_share_group]}
|
||||
return 200, {}, share_groups
|
||||
|
||||
def post_consistency_groups_1234_action(self, **kw):
|
||||
return (202, {}, None)
|
||||
def get_share_groups_1234(self, **kw):
|
||||
share_group = {'share_group': self.fake_share_group}
|
||||
return 200, {}, share_group
|
||||
|
||||
def post_consistency_groups(self, body, **kw):
|
||||
return (202, {}, {
|
||||
'consistency_group': {
|
||||
'id': 'fake-cg-id',
|
||||
'name': 'fake_name'
|
||||
def put_share_groups_1234(self, **kwargs):
|
||||
share_group = {'share_group': self.fake_share_group}
|
||||
return 200, {}, share_group
|
||||
|
||||
def delete_share_groups_1234(self, **kw):
|
||||
return 202, {}, None
|
||||
|
||||
def post_share_groups_1234_action(self, **kw):
|
||||
return 202, {}, None
|
||||
|
||||
def post_share_groups(self, body, **kw):
|
||||
share_group = {
|
||||
'share_group': {
|
||||
'id': 'fake-sg-id',
|
||||
'name': 'fake_name',
|
||||
}
|
||||
})
|
||||
|
||||
def get_cgsnapshots_fake_cg_id_members(self, **kw):
|
||||
members = {
|
||||
'cgsnapshot_members': [
|
||||
{
|
||||
'id': 1234,
|
||||
'name': 'fake name',
|
||||
'created_at': '05050505',
|
||||
'size': '50PB',
|
||||
'share_protocol': 'NFS',
|
||||
'project_id': '2221234',
|
||||
'share_type_id': '3331234',
|
||||
},
|
||||
{
|
||||
'id': 4321,
|
||||
'name': 'fake name 2',
|
||||
'created_at': '03030303',
|
||||
'size': '50PB',
|
||||
'share_protocol': 'NFS',
|
||||
'project_id': '2224321',
|
||||
'share_type_id': '3334321',
|
||||
}
|
||||
]
|
||||
}
|
||||
return(200, {}, members)
|
||||
return 202, {}, share_group
|
||||
|
||||
def get_cgsnapshots(self, **kw):
|
||||
cg_snapshots = {
|
||||
'cgsnapshots': [
|
||||
{
|
||||
'id': 1234,
|
||||
'status': 'available',
|
||||
'name': 'cgsnapshotname',
|
||||
}
|
||||
]
|
||||
fake_share_group_snapshot = {
|
||||
'id': '1234',
|
||||
'status': 'available',
|
||||
'name': 'share group name',
|
||||
'description': 'my share group',
|
||||
}
|
||||
|
||||
def get_share_group_snapshots(self, **kw):
|
||||
sg_snapshots = {
|
||||
'share_group_snapshots': [self.fake_share_group_snapshot],
|
||||
}
|
||||
return (200, {}, cg_snapshots)
|
||||
return 200, {}, sg_snapshots
|
||||
|
||||
def get_cgsnapshots_detail(self, **kw):
|
||||
cg_snapshots = {
|
||||
'cgsnapshots': [
|
||||
{
|
||||
'id': 1234,
|
||||
'status': 'available',
|
||||
'name': 'cgsnapshotname',
|
||||
'description': 'my cgsnapshot'
|
||||
}
|
||||
]
|
||||
def get_share_group_snapshots_detail(self, **kw):
|
||||
sg_snapshots = {
|
||||
'share_group_snapshots': [self.fake_share_group_snapshot],
|
||||
}
|
||||
return (200, {}, cg_snapshots)
|
||||
return 200, {}, sg_snapshots
|
||||
|
||||
def delete_cgsnapshots_1234(self, **kw):
|
||||
return (202, {}, None)
|
||||
def get_share_group_snapshots_1234(self, **kw):
|
||||
sg_snapshot = {'share_group_snapshot': self.fake_share_group_snapshot}
|
||||
return 200, {}, sg_snapshot
|
||||
|
||||
def post_cgsnapshots_1234_action(self, **kw):
|
||||
return (202, {}, None)
|
||||
def put_share_group_snapshots_1234(self, **kwargs):
|
||||
sg_snapshot = {
|
||||
'share_group_snapshot': self.fake_share_group_snapshot,
|
||||
}
|
||||
return 200, {}, sg_snapshot
|
||||
|
||||
def post_cgsnapshots(self, body, **kw):
|
||||
return (202, {}, {
|
||||
'cgsnapshot': {
|
||||
def delete_share_group_snapshots_1234(self, **kw):
|
||||
return 202, {}, None
|
||||
|
||||
def post_share_group_snapshots_1234_action(self, **kw):
|
||||
return 202, {}, None
|
||||
|
||||
def post_share_group_snapshots(self, body, **kw):
|
||||
sg_snapshot = {
|
||||
'share_group_snapshot': {
|
||||
'id': 3,
|
||||
'name': 'cust_snapshot',
|
||||
}
|
||||
})
|
||||
}
|
||||
return 202, {}, sg_snapshot
|
||||
|
||||
fake_share_replica = {
|
||||
"id": "5678",
|
||||
@ -953,6 +942,114 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
||||
raise AssertionError("Unexpected share action: %s" % action)
|
||||
return (resp, {}, _body)
|
||||
|
||||
def get_share_group_types_default(self, **kw):
|
||||
return self.get_share_group_types_1(**kw)
|
||||
|
||||
def get_share_group_types(self, **kw):
|
||||
share_group_types = {
|
||||
'share_group_types': [
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'test-group-type-1',
|
||||
'group_specs': {
|
||||
'key1': 'value1',
|
||||
},
|
||||
'share_types': [
|
||||
'type1',
|
||||
'type2',
|
||||
],
|
||||
'is_public': True,
|
||||
}, {
|
||||
'id': 2,
|
||||
'name': 'test-type-2',
|
||||
'group_specs': {
|
||||
'key2': 'value2',
|
||||
},
|
||||
'share_types': [
|
||||
'type3',
|
||||
'type4',
|
||||
],
|
||||
'is_public': False,
|
||||
},
|
||||
],
|
||||
}
|
||||
return 200, {}, share_group_types
|
||||
|
||||
def get_share_group_types_1(self, **kw):
|
||||
share_group_type = {
|
||||
'share_group_type': {
|
||||
'id': 1,
|
||||
'name': 'test-group-type-1',
|
||||
'group_specs': {
|
||||
'key1': 'value1',
|
||||
},
|
||||
'share_types': [
|
||||
'type1',
|
||||
'type2',
|
||||
],
|
||||
'is_public': True,
|
||||
},
|
||||
}
|
||||
return 200, {}, share_group_type
|
||||
|
||||
def get_share_group_types_2(self, **kw):
|
||||
share_group_type = {
|
||||
'share_type': {
|
||||
'id': 2,
|
||||
'name': 'test-group-type-2',
|
||||
'group_specs': {
|
||||
'key2': 'value2',
|
||||
},
|
||||
'share_types': [
|
||||
'type3',
|
||||
'type4',
|
||||
],
|
||||
'is_public': True,
|
||||
},
|
||||
}
|
||||
return 200, {}, share_group_type
|
||||
|
||||
def post_share_group_types(self, body, **kw):
|
||||
share_group_type = {
|
||||
'share_group_type': {
|
||||
'id': 1,
|
||||
'name': 'test-group-type-1',
|
||||
'share_types': body['share_group_type']['share_types'],
|
||||
'is_public': True,
|
||||
},
|
||||
}
|
||||
return 202, {}, share_group_type
|
||||
|
||||
def post_share_group_types_1234_action(self, body, **kw):
|
||||
assert len(list(body)) == 1
|
||||
action = list(body)[0]
|
||||
if action == 'addProjectAccess':
|
||||
assert 'project' in body['addProjectAccess']
|
||||
elif action == 'removeProjectAccess':
|
||||
assert 'project' in body['removeProjectAccess']
|
||||
else:
|
||||
raise AssertionError('Unexpected action: %s' % action)
|
||||
return 202, {}, None
|
||||
|
||||
def post_share_group_types_1_specs(self, body, **kw):
|
||||
assert list(body) == ['group_specs']
|
||||
return 200, {}, {'group_specs': {'k': 'v'}}
|
||||
|
||||
def delete_share_group_types_1_specs_k(self, **kw):
|
||||
return 204, {}, None
|
||||
|
||||
def delete_share_group_types_1234(self, **kw):
|
||||
return 202, {}, None
|
||||
|
||||
def get_share_group_types_1234_access(self, **kw):
|
||||
sg_type_access = {
|
||||
'share_group_type_access': [{
|
||||
'group_type_id': '11111111-1111-1111-1111-111111111111',
|
||||
'project_id': '00000000-0000-0000-0000-000000000000',
|
||||
}],
|
||||
}
|
||||
return 200, {}, sg_type_access
|
||||
|
||||
|
||||
def fake_create(url, body, response_key):
|
||||
return {'url': url, 'body': body, 'resp_key': response_key}
|
||||
@ -969,3 +1066,43 @@ class FakeQuotaSet(object):
|
||||
|
||||
def to_dict(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
class ShareNetwork(object):
|
||||
id = 'fake share network id'
|
||||
name = 'fake share network name'
|
||||
|
||||
|
||||
class ShareType(object):
|
||||
id = 'fake share type id'
|
||||
name = 'fake share type name'
|
||||
|
||||
|
||||
class ShareGroupType(object):
|
||||
id = 'fake group type id'
|
||||
name = 'fake group type name'
|
||||
share_types = [ShareType().id]
|
||||
is_public = False
|
||||
|
||||
|
||||
class ShareGroupTypeAccess(object):
|
||||
id = 'fake group type access id'
|
||||
name = 'fake group type access name'
|
||||
|
||||
|
||||
class ShareGroup(object):
|
||||
id = 'fake group id'
|
||||
share_types = [ShareType().id]
|
||||
group_type_id = ShareGroupType().id
|
||||
share_network_id = ShareNetwork().id
|
||||
name = 'fake name'
|
||||
description = 'fake description'
|
||||
availability_zone = 'fake az'
|
||||
|
||||
|
||||
class ShareGroupSnapshot(object):
|
||||
id = 'fake group snapshot id'
|
||||
share_group_id = ShareGroup().id,
|
||||
share_network_id = ShareNetwork().id
|
||||
name = 'fake name'
|
||||
description = 'fake description'
|
||||
|
@ -1,183 +0,0 @@
|
||||
# Copyright 2015 Chuck Fouts.
|
||||
# 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 mock
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import consistency_group_snapshots as cg_snapshots
|
||||
|
||||
FAKE_CG = 'fake cg snapshot'
|
||||
FAKE_CG_ID = 'fake-cg-id'
|
||||
|
||||
|
||||
class ConsistencyGroupSnapshotsTest(utils.TestCase):
|
||||
|
||||
class _FakeConsistencyGroupSnapshot(object):
|
||||
id = 'fake_cg_snapshot_id'
|
||||
|
||||
def setUp(self):
|
||||
super(ConsistencyGroupSnapshotsTest, self).setUp()
|
||||
self.manager = cg_snapshots.ConsistencyGroupSnapshotManager(
|
||||
api=fakes.FakeClient())
|
||||
self.values = {
|
||||
'consistency_group_id': 'fake_cg_id',
|
||||
'name': 'fake snapshot name',
|
||||
'description': 'new cg snapshot',
|
||||
}
|
||||
|
||||
def test_snapshot_create(self):
|
||||
body_expected = {cg_snapshots.RESOURCE_NAME: self.values}
|
||||
|
||||
mock_create = mock.Mock()
|
||||
mock_create.side_effect = fakes.fake_create
|
||||
with mock.patch.object(self.manager, '_create', mock_create):
|
||||
result = self.manager.create(**self.values)
|
||||
self.manager._create.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH,
|
||||
body_expected,
|
||||
cg_snapshots.RESOURCE_NAME)
|
||||
|
||||
self.assertEqual(result['url'], cg_snapshots.RESOURCES_PATH)
|
||||
self.assertEqual(result['resp_key'], cg_snapshots.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_snapshot_create_invalid_version(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion, self.manager.create, **self.values)
|
||||
|
||||
def test_snapshot_get(self):
|
||||
with mock.patch.object(self.manager, '_get', mock.Mock()):
|
||||
self.manager.get(FAKE_CG_ID)
|
||||
self.manager._get.assert_called_once_with(
|
||||
cg_snapshots.RESOURCE_PATH % FAKE_CG_ID,
|
||||
cg_snapshots.RESOURCE_NAME)
|
||||
|
||||
def test_snapshot_update_str(self):
|
||||
body_expected = {cg_snapshots.RESOURCE_NAME: self.values}
|
||||
|
||||
mock_update = mock.Mock()
|
||||
mock_update.side_effect = fakes.fake_update
|
||||
with mock.patch.object(self.manager, '_update', mock_update):
|
||||
|
||||
result = self.manager.update(FAKE_CG, **self.values)
|
||||
self.manager._update.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/' + FAKE_CG,
|
||||
body_expected,
|
||||
cg_snapshots.RESOURCE_NAME
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result['url'],
|
||||
cg_snapshots.RESOURCE_PATH % FAKE_CG)
|
||||
self.assertEqual(
|
||||
result['resp_key'],
|
||||
cg_snapshots.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_snapshot_update_obj(self):
|
||||
cg_snapshot = self._FakeConsistencyGroupSnapshot()
|
||||
body_expected = {cg_snapshots.RESOURCE_NAME: self.values}
|
||||
mock_update = mock.Mock()
|
||||
mock_update.side_effect = fakes.fake_update
|
||||
|
||||
with mock.patch.object(self.manager, '_update', mock_update):
|
||||
result = self.manager.update(cg_snapshot, **self.values)
|
||||
self.manager._update.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/' + cg_snapshot.id,
|
||||
body_expected,
|
||||
cg_snapshots.RESOURCE_NAME
|
||||
)
|
||||
self.assertEqual(
|
||||
result['url'],
|
||||
cg_snapshots.RESOURCE_PATH % cg_snapshot.id)
|
||||
self.assertEqual(
|
||||
result['resp_key'],
|
||||
cg_snapshots.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_snapshot_list_not_detailed(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list(detailed=False)
|
||||
self.manager._list.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH,
|
||||
cg_snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_snapshot_list(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list()
|
||||
self.manager._list.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/detail',
|
||||
cg_snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_snapshot_list_with_filters(self):
|
||||
filters = {'all_tenants': 1, 'status': 'ERROR'}
|
||||
expected_path = ("%s/detail?all_tenants=1&status="
|
||||
"ERROR" % cg_snapshots.RESOURCES_PATH)
|
||||
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list(search_opts=filters)
|
||||
self.manager._list.assert_called_once_with(
|
||||
expected_path,
|
||||
cg_snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_snapshot_members(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.members(FAKE_CG_ID)
|
||||
self.manager._list.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/' + FAKE_CG_ID + '/members',
|
||||
'cgsnapshot_members')
|
||||
|
||||
def test_snapshot_members_with_filters(self):
|
||||
search_opts = {'fake_str': 'fake_str_value', 'fake_int': 1}
|
||||
query_str = FAKE_CG_ID + '/members?fake_int=1&fake_str=fake_str_value'
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.members(FAKE_CG_ID, search_opts=search_opts)
|
||||
self.manager._list.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/' + query_str,
|
||||
cg_snapshots.MEMBERS_RESOURCE_NAME)
|
||||
|
||||
def test_snapshot_members_with_offset(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
keys = {'offset': 22}
|
||||
|
||||
url = FAKE_CG_ID + '/members?offset=22'
|
||||
self.manager.members(FAKE_CG_ID, keys)
|
||||
self.manager._list.assert_called_once_with(
|
||||
cg_snapshots.RESOURCES_PATH + '/' + url,
|
||||
cg_snapshots.MEMBERS_RESOURCE_NAME)
|
||||
|
||||
def test_snapshot_delete_str(self):
|
||||
fake_cg_snapshot = 'fake cgsnapshot'
|
||||
with mock.patch.object(self.manager, '_delete', mock.Mock()):
|
||||
self.manager.delete(fake_cg_snapshot)
|
||||
self.manager._delete.assert_called_once_with(
|
||||
cg_snapshots.RESOURCE_PATH % fake_cg_snapshot)
|
||||
|
||||
def test_snapshot_delete_obj(self):
|
||||
cg_snapshot = self._FakeConsistencyGroupSnapshot()
|
||||
with mock.patch.object(self.manager, '_delete', mock.Mock()):
|
||||
self.manager.delete(cg_snapshot)
|
||||
self.manager._delete.assert_called_once_with(
|
||||
cg_snapshots.RESOURCE_PATH % cg_snapshot.id)
|
@ -1,161 +0,0 @@
|
||||
# Copyright 2015 Chuck Fouts.
|
||||
# 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 mock
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import consistency_groups
|
||||
|
||||
FAKE_CG = 'fake consistency group'
|
||||
|
||||
|
||||
class ConsistencyGroupsTest(utils.TestCase):
|
||||
|
||||
class _FakeConsistencyGroupSnapshot(object):
|
||||
id = 'fake_cg_snapshot_id'
|
||||
|
||||
def setUp(self):
|
||||
super(ConsistencyGroupsTest, self).setUp()
|
||||
self.manager = consistency_groups.ConsistencyGroupManager(
|
||||
api=fakes.FakeClient())
|
||||
self.values = {'name': 'fake name', 'description': 'new cg'}
|
||||
|
||||
def test_create(self):
|
||||
body_expected = {consistency_groups.RESOURCE_NAME: self.values}
|
||||
|
||||
mock_create = mock.Mock()
|
||||
mock_create.side_effect = fakes.fake_create
|
||||
with mock.patch.object(self.manager, '_create', mock_create):
|
||||
result = self.manager.create(**self.values)
|
||||
self.manager._create.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH,
|
||||
body_expected,
|
||||
consistency_groups.RESOURCE_NAME
|
||||
)
|
||||
|
||||
self.assertEqual(result['url'], consistency_groups.RESOURCES_PATH)
|
||||
self.assertEqual(result['resp_key'],
|
||||
consistency_groups.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_invalid_create(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion, self.manager.create, **self.values)
|
||||
|
||||
def test_create_with_share_network_id(self):
|
||||
resource_name = consistency_groups.RESOURCE_NAME
|
||||
body_expected = {resource_name: dict(list(self.values.items()))}
|
||||
body_expected[resource_name]['share_network_id'] = '050505050505'
|
||||
self.values['share_network'] = '050505050505'
|
||||
|
||||
mock_create = mock.Mock()
|
||||
mock_create.side_effect = fakes.fake_create
|
||||
with mock.patch.object(self.manager, '_create', mock_create):
|
||||
result = self.manager.create(**self.values)
|
||||
self.manager._create.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH,
|
||||
body_expected,
|
||||
consistency_groups.RESOURCE_NAME
|
||||
)
|
||||
self.assertEqual(result['url'], consistency_groups.RESOURCES_PATH)
|
||||
self.assertEqual(result['resp_key'], resource_name)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_list_not_detailed(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list(detailed=False)
|
||||
self.manager._list.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH,
|
||||
consistency_groups.RESOURCES_NAME)
|
||||
|
||||
def test_list(self):
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list()
|
||||
self.manager._list.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH + '/detail',
|
||||
consistency_groups.RESOURCES_NAME)
|
||||
|
||||
def test_list_with_filters(self):
|
||||
filters = {'all_tenants': 1, 'status': 'ERROR'}
|
||||
expected_path = ("%s/detail?all_tenants=1&status="
|
||||
"ERROR" % consistency_groups.RESOURCES_PATH)
|
||||
|
||||
with mock.patch.object(self.manager, '_list',
|
||||
mock.Mock(return_value=None)):
|
||||
self.manager.list(search_opts=filters)
|
||||
self.manager._list.assert_called_once_with(
|
||||
expected_path,
|
||||
consistency_groups.RESOURCES_NAME)
|
||||
|
||||
def test_delete_str(self):
|
||||
with mock.patch.object(self.manager, '_delete', mock.Mock()):
|
||||
self.manager.delete(FAKE_CG)
|
||||
self.manager._delete.assert_called_once_with(
|
||||
consistency_groups.RESOURCE_PATH % FAKE_CG)
|
||||
|
||||
def test_get(self):
|
||||
with mock.patch.object(self.manager, '_get', mock.Mock()):
|
||||
self.manager.get(FAKE_CG)
|
||||
self.manager._get.assert_called_once_with(
|
||||
consistency_groups.RESOURCE_PATH % FAKE_CG,
|
||||
consistency_groups.RESOURCE_NAME)
|
||||
|
||||
def test_update_str(self):
|
||||
body_expected = {
|
||||
consistency_groups.RESOURCE_NAME: self.values}
|
||||
|
||||
mock_update = mock.Mock()
|
||||
mock_update.side_effect = fakes.fake_update
|
||||
with mock.patch.object(self.manager, '_update', mock_update):
|
||||
result = self.manager.update(FAKE_CG, **self.values)
|
||||
self.manager._update.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH + '/' + FAKE_CG,
|
||||
body_expected,
|
||||
consistency_groups.RESOURCE_NAME
|
||||
)
|
||||
self.assertEqual(
|
||||
result['url'],
|
||||
consistency_groups.RESOURCE_PATH % FAKE_CG)
|
||||
self.assertEqual(
|
||||
result['resp_key'],
|
||||
consistency_groups.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_snapshot_update_obj(self):
|
||||
cg_snapshot = self._FakeConsistencyGroupSnapshot()
|
||||
body_expected = {consistency_groups.RESOURCE_NAME: self.values}
|
||||
|
||||
mock_update = mock.Mock()
|
||||
mock_update.side_effect = fakes.fake_update
|
||||
with mock.patch.object(self.manager, '_update', mock_update):
|
||||
result = self.manager.update(cg_snapshot, **self.values)
|
||||
self.manager._update.assert_called_once_with(
|
||||
consistency_groups.RESOURCES_PATH + '/' + cg_snapshot.id,
|
||||
body_expected,
|
||||
consistency_groups.RESOURCE_NAME
|
||||
)
|
||||
self.assertEqual(
|
||||
result['url'],
|
||||
consistency_groups.RESOURCE_PATH % cg_snapshot.id)
|
||||
self.assertEqual(
|
||||
result['resp_key'],
|
||||
consistency_groups.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
265
manilaclient/tests/unit/v2/test_share_group_snapshots.py
Normal file
265
manilaclient/tests/unit/v2/test_share_group_snapshots.py
Normal file
@ -0,0 +1,265 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
# 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 mock
|
||||
|
||||
import ddt
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes as fake
|
||||
from manilaclient.v2 import share_group_snapshots as snapshots
|
||||
|
||||
|
||||
class ShareGroupSnapshotTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupSnapshotTest, self).setUp()
|
||||
self.manager = snapshots.ShareGroupSnapshotManager(fake.FakeClient())
|
||||
self.share_group_snapshot = snapshots.ShareGroupSnapshot(
|
||||
self.manager, {'id': 'fake_id'})
|
||||
self.fake_kwargs = {'key': 'value'}
|
||||
|
||||
def test_repr(self):
|
||||
result = six.text_type(self.share_group_snapshot)
|
||||
|
||||
self.assertEqual('<Share Group Snapshot: fake_id>', result)
|
||||
|
||||
def test_update(self):
|
||||
mock_manager_update = self.mock_object(self.manager, 'update')
|
||||
|
||||
self.share_group_snapshot.update(**self.fake_kwargs)
|
||||
|
||||
mock_manager_update.assert_called_once_with(
|
||||
self.share_group_snapshot, **self.fake_kwargs)
|
||||
|
||||
def test_delete(self):
|
||||
mock_manager_delete = self.mock_object(self.manager, 'delete')
|
||||
|
||||
self.share_group_snapshot.delete()
|
||||
|
||||
mock_manager_delete.assert_called_once_with(self.share_group_snapshot)
|
||||
|
||||
def test_reset_state(self):
|
||||
mock_manager_reset_state = self.mock_object(
|
||||
self.manager, 'reset_state')
|
||||
|
||||
self.share_group_snapshot.reset_state('fake_state')
|
||||
|
||||
mock_manager_reset_state.assert_called_once_with(
|
||||
self.share_group_snapshot, 'fake_state')
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupSnapshotManagerTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupSnapshotManagerTest, self).setUp()
|
||||
self.manager = snapshots.ShareGroupSnapshotManager(fake.FakeClient())
|
||||
|
||||
def test_create(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
create_args = {
|
||||
'name': fake.ShareGroupSnapshot.name,
|
||||
'description': fake.ShareGroupSnapshot.description,
|
||||
}
|
||||
|
||||
result = self.manager.create(fake.ShareGroupSnapshot, **create_args)
|
||||
|
||||
self.assertIs(fake_share_group_snapshot, result)
|
||||
expected_body = {
|
||||
snapshots.RESOURCE_NAME: {
|
||||
'name': fake.ShareGroupSnapshot.name,
|
||||
'description': fake.ShareGroupSnapshot.description,
|
||||
'share_group_id': fake.ShareGroupSnapshot().id,
|
||||
},
|
||||
}
|
||||
mock_create.assert_called_once_with(
|
||||
snapshots.RESOURCES_PATH, expected_body, snapshots.RESOURCE_NAME)
|
||||
|
||||
def test_create_minimal_args(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
|
||||
result = self.manager.create(fake.ShareGroupSnapshot)
|
||||
|
||||
self.assertIs(fake_share_group_snapshot, result)
|
||||
expected_body = {
|
||||
snapshots.RESOURCE_NAME: {
|
||||
'share_group_id': fake.ShareGroupSnapshot().id,
|
||||
},
|
||||
}
|
||||
mock_create.assert_called_once_with(
|
||||
snapshots.RESOURCES_PATH, expected_body, snapshots.RESOURCE_NAME)
|
||||
|
||||
def test_create_using_unsupported_microversion(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion,
|
||||
self.manager.create, fake.ShareGroupSnapshot)
|
||||
|
||||
def test_get(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
|
||||
result = self.manager.get(fake.ShareGroupSnapshot.id)
|
||||
|
||||
self.assertIs(fake_share_group_snapshot, result)
|
||||
mock_get.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH % fake.ShareGroupSnapshot.id,
|
||||
snapshots.RESOURCE_NAME)
|
||||
|
||||
def test_list(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_snapshot]))
|
||||
|
||||
result = self.manager.list()
|
||||
|
||||
self.assertEqual([fake_share_group_snapshot], result)
|
||||
mock_list.assert_called_once_with(
|
||||
snapshots.RESOURCES_PATH + '/detail',
|
||||
snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_list_no_detail(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_snapshot]))
|
||||
|
||||
result = self.manager.list(detailed=False)
|
||||
|
||||
self.assertEqual([fake_share_group_snapshot], result)
|
||||
mock_list.assert_called_once_with(
|
||||
snapshots.RESOURCES_PATH, snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_list_with_filters(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_snapshot]))
|
||||
filters = {'all_tenants': 1, 'status': 'ERROR'}
|
||||
|
||||
result = self.manager.list(detailed=False, search_opts=filters)
|
||||
|
||||
self.assertEqual([fake_share_group_snapshot], result)
|
||||
expected_path = (snapshots.RESOURCES_PATH +
|
||||
'?all_tenants=1&status=ERROR')
|
||||
mock_list.assert_called_once_with(
|
||||
expected_path, snapshots.RESOURCES_NAME)
|
||||
|
||||
def test_list_with_sorting(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_snapshot]))
|
||||
|
||||
result = self.manager.list(
|
||||
detailed=False, sort_dir='asc', sort_key='name')
|
||||
|
||||
self.assertEqual([fake_share_group_snapshot], result)
|
||||
expected_path = (
|
||||
snapshots.RESOURCES_PATH + '?sort_dir=asc&sort_key=name')
|
||||
mock_list.assert_called_once_with(
|
||||
expected_path, snapshots.RESOURCES_NAME)
|
||||
|
||||
@ddt.data({'sort_key': 'name', 'sort_dir': 'invalid'},
|
||||
{'sort_key': 'invalid', 'sort_dir': 'asc'})
|
||||
@ddt.unpack
|
||||
def test_list_with_invalid_sorting(self, sort_key, sort_dir):
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.manager.list, sort_dir=sort_dir, sort_key=sort_key)
|
||||
|
||||
def test_update(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
mock_update = self.mock_object(
|
||||
self.manager, '_update',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
update_args = {
|
||||
'name': fake.ShareGroupSnapshot.name,
|
||||
'description': fake.ShareGroupSnapshot.description,
|
||||
}
|
||||
|
||||
result = self.manager.update(fake.ShareGroupSnapshot(), **update_args)
|
||||
|
||||
self.assertIs(fake_share_group_snapshot, result)
|
||||
self.assertFalse(mock_get.called)
|
||||
mock_update.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH % fake.ShareGroupSnapshot.id,
|
||||
{snapshots.RESOURCE_NAME: update_args},
|
||||
snapshots.RESOURCE_NAME)
|
||||
|
||||
def test_update_no_data(self):
|
||||
fake_share_group_snapshot = fake.ShareGroupSnapshot()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
mock_update = self.mock_object(
|
||||
self.manager, '_update',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
update_args = {}
|
||||
|
||||
result = self.manager.update(fake.ShareGroupSnapshot(), **update_args)
|
||||
|
||||
self.assertIs(fake_share_group_snapshot, result)
|
||||
mock_get.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH % fake.ShareGroupSnapshot.id,
|
||||
snapshots.RESOURCE_NAME)
|
||||
self.assertFalse(mock_update.called)
|
||||
|
||||
def test_delete(self):
|
||||
mock_delete = self.mock_object(self.manager, '_delete')
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.delete(fake.ShareGroupSnapshot())
|
||||
|
||||
mock_delete.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH % fake.ShareGroupSnapshot.id)
|
||||
self.assertFalse(mock_post.called)
|
||||
|
||||
def test_delete_force(self):
|
||||
mock_delete = self.mock_object(self.manager, '_delete')
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.delete(fake.ShareGroupSnapshot.id, force=True)
|
||||
|
||||
self.assertFalse(mock_delete.called)
|
||||
mock_post.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH_ACTION % fake.ShareGroupSnapshot.id,
|
||||
body={'force_delete': None})
|
||||
|
||||
def test_reset_state(self):
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.reset_state(fake.ShareGroupSnapshot(), 'fake_state')
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
snapshots.RESOURCE_PATH_ACTION % fake.ShareGroupSnapshot.id,
|
||||
body={'reset_status': {'status': 'fake_state'}})
|
116
manilaclient/tests/unit/v2/test_share_group_type_access.py
Normal file
116
manilaclient/tests/unit/v2/test_share_group_type_access.py
Normal file
@ -0,0 +1,116 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
# 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 mock
|
||||
|
||||
import ddt
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes as fake
|
||||
from manilaclient.v2 import share_group_type_access as type_access
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupTypeAccessTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupTypeAccessTest, self).setUp()
|
||||
self.manager = type_access.ShareGroupTypeAccessManager(
|
||||
fake.FakeClient())
|
||||
fake_group_type_access_info = {'id': fake.ShareGroupTypeAccess.id}
|
||||
self.share_group_type_access = type_access.ShareGroupTypeAccess(
|
||||
self.manager, fake_group_type_access_info, loaded=True)
|
||||
|
||||
def test_repr(self):
|
||||
result = six.text_type(self.share_group_type_access)
|
||||
|
||||
self.assertEqual(
|
||||
'<Share Group Type Access: %s>' % fake.ShareGroupTypeAccess.id,
|
||||
result)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupTypeAccessManagerTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupTypeAccessManagerTest, self).setUp()
|
||||
self.manager = type_access.ShareGroupTypeAccessManager(
|
||||
fake.FakeClient())
|
||||
|
||||
def test_list(self):
|
||||
fake_share_group_type_access = fake.ShareGroupTypeAccess()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_type_access]))
|
||||
|
||||
result = self.manager.list(fake.ShareGroupType())
|
||||
|
||||
self.assertEqual([fake_share_group_type_access], result)
|
||||
mock_list.assert_called_once_with(
|
||||
type_access.RESOURCE_PATH % fake.ShareGroupType.id,
|
||||
type_access.RESOURCE_NAME)
|
||||
|
||||
def test_list_public(self):
|
||||
fake_share_group_type_access = fake.ShareGroupTypeAccess()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_type_access]))
|
||||
fake_share_group_type = fake.ShareGroupType()
|
||||
fake_share_group_type.is_public = True
|
||||
|
||||
result = self.manager.list(fake_share_group_type)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertFalse(mock_list.called)
|
||||
|
||||
def test_list_using_unsupported_microversion(self):
|
||||
fake_share_group_type_access = fake.ShareGroupTypeAccess()
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion,
|
||||
self.manager.list, fake_share_group_type_access)
|
||||
|
||||
def test_add_project_access(self):
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.add_project_access(fake.ShareGroupType(), 'fake_project')
|
||||
|
||||
expected_body = {
|
||||
'addProjectAccess': {
|
||||
'project': 'fake_project',
|
||||
}
|
||||
}
|
||||
mock_post.assert_called_once_with(
|
||||
type_access.RESOURCE_PATH_ACTION % fake.ShareGroupType.id,
|
||||
body=expected_body)
|
||||
|
||||
def test_remove_project_access(self):
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.remove_project_access(
|
||||
fake.ShareGroupType(), 'fake_project')
|
||||
|
||||
expected_body = {
|
||||
'removeProjectAccess': {
|
||||
'project': 'fake_project',
|
||||
}
|
||||
}
|
||||
mock_post.assert_called_once_with(
|
||||
type_access.RESOURCE_PATH_ACTION % fake.ShareGroupType.id,
|
||||
body=expected_body)
|
215
manilaclient/tests/unit/v2/test_share_group_types.py
Normal file
215
manilaclient/tests/unit/v2/test_share_group_types.py
Normal file
@ -0,0 +1,215 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
# 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 mock
|
||||
|
||||
import ddt
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes as fake
|
||||
from manilaclient.v2 import share_group_types as types
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupTypeTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupTypeTest, self).setUp()
|
||||
self.manager = types.ShareGroupTypeManager(fake.FakeClient())
|
||||
self.fake_group_specs = {'key1': 'value1', 'key2': 'value2'}
|
||||
self.fake_share_group_type_info = {
|
||||
'id': fake.ShareGroupType.id,
|
||||
'share_types': [fake.ShareType.id],
|
||||
'name': fake.ShareGroupType.name,
|
||||
'is_public': fake.ShareGroupType.is_public,
|
||||
'group_specs': self.fake_group_specs,
|
||||
}
|
||||
self.share_group_type = types.ShareGroupType(
|
||||
self.manager, self.fake_share_group_type_info, loaded=True)
|
||||
|
||||
def test_repr(self):
|
||||
result = six.text_type(self.share_group_type)
|
||||
|
||||
self.assertEqual(
|
||||
'<Share Group Type: %s>' % fake.ShareGroupType.name, result)
|
||||
|
||||
@ddt.data((True, True), (False, False), (None, 'N/A'))
|
||||
@ddt.unpack
|
||||
def test_is_public(self, is_public, expected):
|
||||
fake_share_group_type_info = {'name': 'fake_name'}
|
||||
if is_public is not None:
|
||||
fake_share_group_type_info['is_public'] = is_public
|
||||
share_group_type = types.ShareGroupType(
|
||||
self.manager, fake_share_group_type_info, loaded=True)
|
||||
|
||||
result = share_group_type.is_public
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_keys(self):
|
||||
self.mock_object(self.manager.api.client, 'get')
|
||||
|
||||
result = self.share_group_type.get_keys()
|
||||
|
||||
self.assertEqual(self.fake_group_specs, result)
|
||||
self.assertFalse(self.manager.api.client.get.called)
|
||||
|
||||
def test_get_keys_force_api_call(self):
|
||||
share_group_type = types.ShareGroupType(
|
||||
self.manager, self.fake_share_group_type_info, loaded=True)
|
||||
share_group_type._group_specs = {}
|
||||
self.manager.api.client.get = mock.Mock(return_value=(
|
||||
None, self.fake_share_group_type_info))
|
||||
|
||||
result = share_group_type.get_keys(prefer_resource_data=False)
|
||||
|
||||
self.assertEqual(self.fake_group_specs, result)
|
||||
self.manager.api.client.get.assert_called_once_with(
|
||||
types.GROUP_SPECS_RESOURCES_PATH % fake.ShareGroupType.id)
|
||||
|
||||
def test_set_keys(self):
|
||||
mock_manager_create = self.mock_object(
|
||||
self.manager, '_create',
|
||||
mock.Mock(return_value=self.fake_group_specs))
|
||||
|
||||
result = self.share_group_type.set_keys(self.fake_group_specs)
|
||||
|
||||
self.assertEqual(result, self.fake_group_specs)
|
||||
expected_body = {'group_specs': self.fake_group_specs}
|
||||
mock_manager_create.assert_called_once_with(
|
||||
types.GROUP_SPECS_RESOURCES_PATH % fake.ShareGroupType.id,
|
||||
expected_body, types.GROUP_SPECS_RESOURCES_NAME, return_raw=True)
|
||||
|
||||
def test_unset_keys(self):
|
||||
mock_manager_delete = self.mock_object(
|
||||
self.manager, '_delete', mock.Mock(return_value=None))
|
||||
|
||||
result = self.share_group_type.unset_keys(self.fake_group_specs.keys())
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_manager_delete.assert_has_calls([
|
||||
mock.call(types.GROUP_SPECS_RESOURCE_PATH %
|
||||
(fake.ShareGroupType.id, 'key1')),
|
||||
mock.call(types.GROUP_SPECS_RESOURCE_PATH %
|
||||
(fake.ShareGroupType.id, 'key2')),
|
||||
], any_order=True)
|
||||
|
||||
def test_unset_keys_error(self):
|
||||
mock_manager_delete = self.mock_object(
|
||||
self.manager, '_delete', mock.Mock(return_value='error'))
|
||||
|
||||
result = self.share_group_type.unset_keys(
|
||||
sorted(self.fake_group_specs.keys()))
|
||||
|
||||
self.assertEqual('error', result)
|
||||
mock_manager_delete.assert_called_once_with(
|
||||
types.GROUP_SPECS_RESOURCE_PATH % (fake.ShareGroupType.id, 'key1'))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupTypeManagerTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupTypeManagerTest, self).setUp()
|
||||
self.manager = types.ShareGroupTypeManager(fake.FakeClient())
|
||||
self.fake_group_specs = {'key1': 'value1', 'key2': 'value2'}
|
||||
|
||||
def test_create(self):
|
||||
fake_share_group_type = fake.ShareGroupType()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
create_args = {
|
||||
'name': fake.ShareGroupType.name,
|
||||
'share_types': [fake.ShareType()],
|
||||
'is_public': False,
|
||||
'group_specs': self.fake_group_specs,
|
||||
}
|
||||
|
||||
result = self.manager.create(**create_args)
|
||||
|
||||
self.assertIs(fake_share_group_type, result)
|
||||
expected_body = {
|
||||
types.RESOURCE_NAME: {
|
||||
'name': fake.ShareGroupType.name,
|
||||
'share_types': [fake.ShareType().id],
|
||||
'is_public': False,
|
||||
'group_specs': self.fake_group_specs,
|
||||
},
|
||||
}
|
||||
mock_create.assert_called_once_with(
|
||||
types.RESOURCES_PATH, expected_body, types.RESOURCE_NAME)
|
||||
|
||||
def test_create_no_share_type(self):
|
||||
create_args = {
|
||||
'name': fake.ShareGroupType.name,
|
||||
'share_types': [],
|
||||
'is_public': False,
|
||||
'group_specs': self.fake_group_specs,
|
||||
}
|
||||
|
||||
self.assertRaises(ValueError, self.manager.create, **create_args)
|
||||
|
||||
def test_create_using_unsupported_microversion(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion, self.manager.create)
|
||||
|
||||
def test_get(self):
|
||||
fake_share_group_type = fake.ShareGroupType()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
result = self.manager.get(fake.ShareGroupType.id)
|
||||
|
||||
self.assertIs(fake_share_group_type, result)
|
||||
mock_get.assert_called_once_with(
|
||||
types.RESOURCE_PATH % fake.ShareGroupType.id, types.RESOURCE_NAME)
|
||||
|
||||
def test_list(self):
|
||||
fake_share_group_type = fake.ShareGroupType()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_type]))
|
||||
|
||||
result = self.manager.list()
|
||||
|
||||
self.assertEqual([fake_share_group_type], result)
|
||||
mock_list.assert_called_once_with(
|
||||
types.RESOURCES_PATH + '?is_public=all', types.RESOURCES_NAME)
|
||||
|
||||
def test_list_no_public(self):
|
||||
fake_share_group_type = fake.ShareGroupType()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list',
|
||||
mock.Mock(return_value=[fake_share_group_type]))
|
||||
|
||||
result = self.manager.list(show_all=False)
|
||||
|
||||
self.assertEqual([fake_share_group_type], result)
|
||||
mock_list.assert_called_once_with(
|
||||
types.RESOURCES_PATH, types.RESOURCES_NAME)
|
||||
|
||||
def test_delete(self):
|
||||
mock_delete = self.mock_object(self.manager, '_delete')
|
||||
|
||||
self.manager.delete(fake.ShareGroupType())
|
||||
|
||||
mock_delete.assert_called_once_with(
|
||||
types.RESOURCE_PATH % fake.ShareGroupType.id)
|
320
manilaclient/tests/unit/v2/test_share_groups.py
Normal file
320
manilaclient/tests/unit/v2/test_share_groups.py
Normal file
@ -0,0 +1,320 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
# 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 mock
|
||||
|
||||
import ddt
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v2 import fakes as fake
|
||||
from manilaclient.v2 import share_groups
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupTest, self).setUp()
|
||||
self.manager = share_groups.ShareGroupManager(fake.FakeClient())
|
||||
self.share_group = share_groups.ShareGroup(
|
||||
self.manager, {'id': 'fake_id'})
|
||||
self.fake_kwargs = {'key': 'value'}
|
||||
|
||||
def test_repr(self):
|
||||
result = six.text_type(self.share_group)
|
||||
|
||||
self.assertEqual('<Share Group: fake_id>', result)
|
||||
|
||||
def test_update(self):
|
||||
mock_manager_update = self.mock_object(self.manager, 'update')
|
||||
|
||||
self.share_group.update(**self.fake_kwargs)
|
||||
|
||||
mock_manager_update.assert_called_once_with(
|
||||
self.share_group, **self.fake_kwargs)
|
||||
|
||||
def test_delete(self):
|
||||
mock_manager_delete = self.mock_object(self.manager, 'delete')
|
||||
|
||||
self.share_group.delete()
|
||||
|
||||
mock_manager_delete.assert_called_once_with(
|
||||
self.share_group, force=False)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_delete_force(self, force):
|
||||
mock_manager_delete = self.mock_object(self.manager, 'delete')
|
||||
|
||||
self.share_group.delete(force=force)
|
||||
|
||||
mock_manager_delete.assert_called_once_with(
|
||||
self.share_group, force=force)
|
||||
|
||||
def test_reset_state(self):
|
||||
mock_manager_reset_state = self.mock_object(
|
||||
self.manager, 'reset_state')
|
||||
|
||||
self.share_group.reset_state('fake_state')
|
||||
|
||||
mock_manager_reset_state.assert_called_once_with(
|
||||
self.share_group, 'fake_state')
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareGroupManagerTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ShareGroupManagerTest, self).setUp()
|
||||
self.manager = share_groups.ShareGroupManager(fake.FakeClient())
|
||||
|
||||
def test_create(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create', mock.Mock(return_value=fake_share_group))
|
||||
create_args = {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'availability_zone': fake.ShareGroup.availability_zone,
|
||||
'share_group_type': fake.ShareGroupType(),
|
||||
'share_types': [fake.ShareType()],
|
||||
'share_network': fake.ShareNetwork(),
|
||||
}
|
||||
|
||||
result = self.manager.create(**create_args)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
expected_body = {
|
||||
share_groups.RESOURCE_NAME: {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'share_group_type_id': fake.ShareGroupType().id,
|
||||
'share_network_id': fake.ShareNetwork().id,
|
||||
'share_types': [fake.ShareType().id],
|
||||
'availability_zone': fake.ShareGroup.availability_zone,
|
||||
},
|
||||
}
|
||||
mock_create.assert_called_once_with(
|
||||
share_groups.RESOURCES_PATH,
|
||||
expected_body,
|
||||
share_groups.RESOURCE_NAME)
|
||||
|
||||
def test_create_default_type(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create', mock.Mock(return_value=fake_share_group))
|
||||
create_args = {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'availability_zone': fake.ShareGroup.availability_zone,
|
||||
}
|
||||
|
||||
result = self.manager.create(**create_args)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
expected_body = {share_groups.RESOURCE_NAME: create_args}
|
||||
mock_create.assert_called_once_with(
|
||||
share_groups.RESOURCES_PATH,
|
||||
expected_body,
|
||||
share_groups.RESOURCE_NAME)
|
||||
|
||||
def test_create_from_snapshot(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_create = self.mock_object(
|
||||
self.manager, '_create', mock.Mock(return_value=fake_share_group))
|
||||
create_args = {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'availability_zone': fake.ShareGroup.availability_zone,
|
||||
'source_share_group_snapshot': fake.ShareGroupSnapshot(),
|
||||
}
|
||||
|
||||
result = self.manager.create(**create_args)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
expected_body = {
|
||||
share_groups.RESOURCE_NAME: {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'availability_zone': fake.ShareGroup.availability_zone,
|
||||
'source_share_group_snapshot_id': fake.ShareGroupSnapshot().id,
|
||||
},
|
||||
}
|
||||
mock_create.assert_called_once_with(
|
||||
share_groups.RESOURCES_PATH,
|
||||
expected_body,
|
||||
share_groups.RESOURCE_NAME)
|
||||
|
||||
def test_create_using_unsupported_microversion(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion, self.manager.create)
|
||||
|
||||
def test_create_invalid_arguments(self):
|
||||
create_args = {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
'share_types': [fake.ShareType().id],
|
||||
'source_share_group_snapshot': fake.ShareGroupSnapshot(),
|
||||
}
|
||||
|
||||
self.assertRaises(ValueError, self.manager.create, **create_args)
|
||||
|
||||
def test_get(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get', mock.Mock(return_value=fake_share_group))
|
||||
|
||||
result = self.manager.get(fake.ShareGroup.id)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
mock_get.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH % fake.ShareGroup.id,
|
||||
share_groups.RESOURCE_NAME)
|
||||
|
||||
def test_list(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list', mock.Mock(return_value=[fake_share_group]))
|
||||
|
||||
result = self.manager.list()
|
||||
|
||||
self.assertEqual([fake_share_group], result)
|
||||
mock_list.assert_called_once_with(
|
||||
share_groups.RESOURCES_PATH + '/detail',
|
||||
share_groups.RESOURCES_NAME)
|
||||
|
||||
def test_list_no_detail(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list', mock.Mock(return_value=[fake_share_group]))
|
||||
|
||||
result = self.manager.list(detailed=False)
|
||||
|
||||
self.assertEqual([fake_share_group], result)
|
||||
mock_list.assert_called_once_with(
|
||||
share_groups.RESOURCES_PATH, share_groups.RESOURCES_NAME)
|
||||
|
||||
def test_list_with_filters(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list', mock.Mock(return_value=[fake_share_group]))
|
||||
filters = {'all_tenants': 1}
|
||||
|
||||
result = self.manager.list(detailed=False, search_opts=filters)
|
||||
|
||||
self.assertEqual([fake_share_group], result)
|
||||
expected_path = (share_groups.RESOURCES_PATH + '?all_tenants=1')
|
||||
mock_list.assert_called_once_with(
|
||||
expected_path, share_groups.RESOURCES_NAME)
|
||||
|
||||
@ddt.data(
|
||||
('name', 'name'),
|
||||
('share_group_type', 'share_group_type_id'),
|
||||
('share_network', 'share_network_id'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_list_with_sorting(self, key, expected_key):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_list = self.mock_object(
|
||||
self.manager, '_list', mock.Mock(return_value=[fake_share_group]))
|
||||
|
||||
result = self.manager.list(
|
||||
detailed=False, sort_dir='asc', sort_key=key)
|
||||
|
||||
self.assertEqual([fake_share_group], result)
|
||||
expected_path = (
|
||||
share_groups.RESOURCES_PATH + '?sort_dir=asc&sort_key=' +
|
||||
expected_key)
|
||||
mock_list.assert_called_once_with(
|
||||
expected_path, share_groups.RESOURCES_NAME)
|
||||
|
||||
@ddt.data(
|
||||
('name', 'invalid'),
|
||||
('invalid', 'asc'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_list_with_invalid_sorting(self, sort_key, sort_dir):
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.manager.list, sort_dir=sort_dir, sort_key=sort_key)
|
||||
|
||||
def test_update(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get', mock.Mock(return_value=fake_share_group))
|
||||
mock_update = self.mock_object(
|
||||
self.manager, '_update', mock.Mock(return_value=fake_share_group))
|
||||
update_args = {
|
||||
'name': fake.ShareGroup.name,
|
||||
'description': fake.ShareGroup.description,
|
||||
}
|
||||
|
||||
result = self.manager.update(fake.ShareGroup(), **update_args)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
self.assertFalse(mock_get.called)
|
||||
mock_update.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH % fake.ShareGroup.id,
|
||||
{share_groups.RESOURCE_NAME: update_args},
|
||||
share_groups.RESOURCE_NAME)
|
||||
|
||||
def test_update_no_data(self):
|
||||
fake_share_group = fake.ShareGroup()
|
||||
mock_get = self.mock_object(
|
||||
self.manager, '_get', mock.Mock(return_value=fake_share_group))
|
||||
mock_update = self.mock_object(
|
||||
self.manager, '_update', mock.Mock(return_value=fake_share_group))
|
||||
update_args = {}
|
||||
|
||||
result = self.manager.update(fake.ShareGroup(), **update_args)
|
||||
|
||||
self.assertIs(fake_share_group, result)
|
||||
mock_get.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH % fake.ShareGroup.id,
|
||||
share_groups.RESOURCE_NAME)
|
||||
self.assertFalse(mock_update.called)
|
||||
|
||||
def test_delete(self):
|
||||
mock_delete = self.mock_object(self.manager, '_delete')
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.delete(fake.ShareGroup())
|
||||
|
||||
mock_delete.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH % fake.ShareGroup.id)
|
||||
self.assertFalse(mock_post.called)
|
||||
|
||||
def test_delete_force(self):
|
||||
mock_delete = self.mock_object(self.manager, '_delete')
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.delete(fake.ShareGroup.id, force=True)
|
||||
|
||||
self.assertFalse(mock_delete.called)
|
||||
mock_post.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH_ACTION % fake.ShareGroup.id,
|
||||
body={'force_delete': None})
|
||||
|
||||
def test_reset_state(self):
|
||||
mock_post = self.mock_object(self.manager.api.client, 'post')
|
||||
|
||||
self.manager.reset_state(fake.ShareGroup(), 'fake_state')
|
||||
|
||||
mock_post.assert_called_once_with(
|
||||
share_groups.RESOURCE_PATH_ACTION % fake.ShareGroup.id,
|
||||
body={'reset_status': {'status': 'fake_state'}})
|
@ -66,7 +66,6 @@ class SharesTest(utils.TestCase):
|
||||
'share_type': None,
|
||||
'is_public': False,
|
||||
'availability_zone': None,
|
||||
'consistency_group_id': None,
|
||||
}
|
||||
cs.shares.create(protocol, 1)
|
||||
cs.assert_called('POST', '/shares', {'share': expected})
|
||||
@ -87,7 +86,6 @@ class SharesTest(utils.TestCase):
|
||||
'share_type': None,
|
||||
'is_public': False,
|
||||
'availability_zone': None,
|
||||
'consistency_group_id': None,
|
||||
}
|
||||
cs.shares.create('nfs', 1, share_network=share_network)
|
||||
cs.assert_called('POST', '/shares', {'share': expected})
|
||||
@ -108,7 +106,6 @@ class SharesTest(utils.TestCase):
|
||||
'share_type': 'fake_st',
|
||||
'is_public': False,
|
||||
'availability_zone': None,
|
||||
'consistency_group_id': None,
|
||||
}
|
||||
cs.shares.create('nfs', 1, share_type=share_type)
|
||||
cs.assert_called('POST', '/shares', {'share': expected})
|
||||
@ -132,7 +129,6 @@ class SharesTest(utils.TestCase):
|
||||
'share_network_id': None,
|
||||
'size': 1,
|
||||
'availability_zone': availability_zone,
|
||||
'consistency_group_id': None,
|
||||
}
|
||||
}
|
||||
cs.shares.create('nfs', 1, is_public=is_public,
|
||||
|
@ -68,7 +68,6 @@ class ShellTest(test_utils.TestCase):
|
||||
self.separators = [' ', '=']
|
||||
self.create_share_body = {
|
||||
"share": {
|
||||
"consistency_group_id": None,
|
||||
"share_type": None,
|
||||
"name": None,
|
||||
"snapshot_id": None,
|
||||
@ -637,17 +636,16 @@ class ShellTest(test_utils.TestCase):
|
||||
self.assert_called('DELETE', '/shares/1234')
|
||||
|
||||
@ddt.data(
|
||||
'--cg 1234', '--consistency-group 1234', '--consistency_group 1234')
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_delete_with_cg(self, cg_cmd):
|
||||
fcg = type(
|
||||
'FakeConsistencyGroup', (object,), {'id': cg_cmd.split()[-1]})
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
'--group sg1313', '--share-group sg1313', '--share_group sg1313')
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_delete_with_share_group(self, sg_cmd):
|
||||
fake_sg = type('FakeShareGroup', (object,), {'id': sg_cmd.split()[-1]})
|
||||
shell_v2._find_share_group.return_value = fake_sg
|
||||
|
||||
self.run_command('delete 1234 %s' % cg_cmd)
|
||||
self.run_command('delete 1234 %s' % sg_cmd)
|
||||
|
||||
self.assert_called('DELETE', '/shares/1234?consistency_group_id=1234')
|
||||
self.assertTrue(shell_v2._find_consistency_group.called)
|
||||
self.assert_called('DELETE', '/shares/1234?share_group_id=sg1313')
|
||||
self.assertTrue(shell_v2._find_share_group.called)
|
||||
|
||||
def test_delete_not_found(self):
|
||||
self.assertRaises(
|
||||
@ -1763,152 +1761,448 @@ class ShellTest(test_utils.TestCase):
|
||||
field_labels=['ID', 'Status', 'Version', 'Minimum Version'])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
def test_cg_list(self):
|
||||
self.run_command('cg-list')
|
||||
self.assert_called('GET', '/consistency-groups/detail')
|
||||
def test_share_group_list(self):
|
||||
self.run_command('share-group-list')
|
||||
|
||||
self.assert_called('GET', '/share-groups/detail')
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['id', 'name', 'description', 'status'])
|
||||
mock.ANY, fields=('ID', 'Name', 'Status', 'Description'))
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
def test_cg_list_select_column(self):
|
||||
self.run_command('cg-list --columns id,name,description')
|
||||
self.assert_called('GET', '/consistency-groups/detail')
|
||||
def test_share_group_list_select_column(self):
|
||||
self.run_command('share-group-list --columns id,name,description')
|
||||
|
||||
self.assert_called('GET', '/share-groups/detail')
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['Id', 'Name', 'Description'])
|
||||
|
||||
def test_share_group_show(self):
|
||||
self.run_command('share-group-show 1234')
|
||||
|
||||
self.assert_called('GET', '/share-groups/1234')
|
||||
|
||||
def test_share_group_create(self):
|
||||
fake_share_type_1 = type('FakeShareType1', (object,), {'id': '1234'})
|
||||
fake_share_type_2 = type('FakeShareType2', (object,), {'id': '5678'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_type',
|
||||
mock.Mock(side_effect=[fake_share_type_1, fake_share_type_2]))
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,), {'id': '2345'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
fake_share_network = type(
|
||||
'FakeShareNetwork', (object,), {'id': '3456'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_network',
|
||||
mock.Mock(return_value=fake_share_network))
|
||||
|
||||
self.run_command(
|
||||
'share-group-create --name fake_sg '
|
||||
'--description my_group --share-types 1234,5678 '
|
||||
'--share-group-type fake_sg_type '
|
||||
'--share-network fake_share_network '
|
||||
'--availability-zone fake_az')
|
||||
|
||||
expected = {
|
||||
'share_group': {
|
||||
'name': 'fake_sg',
|
||||
'description': 'my_sg',
|
||||
'availability_zone': 'fake_az',
|
||||
'share_group_type_id': '2345',
|
||||
'share_network_id': '3456',
|
||||
'share_types': ['1234', '5678'],
|
||||
},
|
||||
}
|
||||
self.assert_called('POST', '/share-groups', body=expected)
|
||||
|
||||
@ddt.data(
|
||||
'--source-cgsnapshot-id fake-cg-id',
|
||||
'--name fake_name --source-cgsnapshot-id fake-cg-id',
|
||||
'--name fake_name --availability-zone fake_az',
|
||||
'--description my_fake_description --name fake_name',
|
||||
'--availability-zone fake_az',
|
||||
)
|
||||
def test_cg_create(self, data):
|
||||
cmd = 'cg-create' + ' ' + data
|
||||
def test_share_group_create_no_share_types(self, data):
|
||||
cmd = 'share-group-create' + ' ' + data
|
||||
|
||||
self.run_command(cmd)
|
||||
self.assert_called('POST', '/consistency-groups')
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_delete(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
self.assert_called('POST', '/share-groups')
|
||||
|
||||
self.run_command('cg-delete fake-cg')
|
||||
self.assert_called('DELETE', '/consistency-groups/1234')
|
||||
def test_share_group_create_invalid_args(self):
|
||||
fake_share_type_1 = type('FakeShareType1', (object,), {'id': '1234'})
|
||||
fake_share_type_2 = type('FakeShareType2', (object,), {'id': '5678'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_type',
|
||||
mock.Mock(side_effect=[fake_share_type_1, fake_share_type_2]))
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,), {'id': '2345'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
fake_share_group_snapshot = type(
|
||||
'FakeShareGroupSnapshot', (object,), {'id': '3456'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_snapshot',
|
||||
mock.Mock(return_value=fake_share_group_snapshot))
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_delete_force(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
self.run_command,
|
||||
'share-group-create --name fake_sg '
|
||||
'--description my_group --share-types 1234,5678 '
|
||||
'--share-group-type fake_sg_type '
|
||||
'--source-share-group-snapshot fake_share_group_snapshot '
|
||||
'--availability-zone fake_az')
|
||||
|
||||
self.run_command('cg-delete --force fake-cg')
|
||||
self.assert_called('POST', '/consistency-groups/1234/action',
|
||||
{'force_delete': None})
|
||||
@ddt.data(
|
||||
('--name new-name', {'name': 'new-name'}),
|
||||
('--description new-description', {'description': 'new-description'}),
|
||||
('--name new-name --description new-description',
|
||||
{'name': 'new-name', 'description': 'new-description'}),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_share_group_update(self, cmd, expected_body):
|
||||
self.run_command('share-group-update 1234 %s' % cmd)
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_reset_state_with_flag(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
expected = {'share_group': expected_body}
|
||||
self.assert_called('PUT', '/share-groups/1234', body=expected)
|
||||
|
||||
self.run_command('cg-reset-state --state error 1234')
|
||||
self.assert_called('POST', '/consistency-groups/1234/action',
|
||||
{'reset_status': {'status': 'error'}})
|
||||
def test_try_update_share_group_without_data(self):
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.run_command, 'share-group-update 1234')
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_reset_state(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_share_group_delete(self):
|
||||
fake_group = type('FakeShareGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group.return_value = fake_group
|
||||
|
||||
self.run_command('cg-snapshot-reset-state 1234')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
{'reset_status': {'status': 'available'}})
|
||||
self.run_command('share-group-delete fake-sg')
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_reset_state_with_flag(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
self.assert_called('DELETE', '/share-groups/1234')
|
||||
|
||||
self.run_command('cg-snapshot-reset-state --state creating 1234')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
{'reset_status': {'status': 'creating'}})
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_share_group_delete_force(self):
|
||||
fake_group = type('FakeShareGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group.return_value = fake_group
|
||||
|
||||
self.run_command('share-group-delete --force fake-group')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-groups/1234/action', {'force_delete': None})
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_share_group_delete_all_fail(self):
|
||||
shell_v2._find_share_group.side_effect = Exception
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.run_command, 'share-group-delete fake-group')
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_share_group_reset_state_with_flag(self):
|
||||
fake_group = type('FakeShareGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group.return_value = fake_group
|
||||
|
||||
self.run_command('share-group-reset-state --state error 1234')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-groups/1234/action',
|
||||
{'reset_status': {'status': 'error'}})
|
||||
|
||||
@ddt.data(
|
||||
'fake-sg-id',
|
||||
'--name fake_name fake-sg-id',
|
||||
'--description my_fake_description --name fake_name fake-sg-id',
|
||||
)
|
||||
@mock.patch.object(shell_v2, '_find_share_group', mock.Mock())
|
||||
def test_share_group_snapshot_create(self, data):
|
||||
fake_sg = type('FakeShareGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group.return_value = fake_sg
|
||||
|
||||
self.run_command('share-group-snapshot-create ' + data)
|
||||
|
||||
shell_v2._find_share_group.assert_called_with(mock.ANY, 'fake-sg-id')
|
||||
self.assert_called('POST', '/share-group-snapshots')
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
def test_cg_snapshot_list(self):
|
||||
self.run_command('cg-snapshot-list')
|
||||
self.assert_called('GET', '/cgsnapshots/detail')
|
||||
def test_share_group_snapshot_list(self):
|
||||
self.run_command('share-group-snapshot-list')
|
||||
|
||||
self.assert_called('GET', '/share-group-snapshots/detail')
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['id', 'name', 'description', 'status'])
|
||||
mock.ANY, fields=('id', 'name', 'status', 'description'))
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
def test_cg_snapshot_list_select_column(self):
|
||||
self.run_command('cg-snapshot-list --columns id,name')
|
||||
self.assert_called('GET', '/cgsnapshots/detail')
|
||||
def test_share_group_snapshot_list_select_column(self):
|
||||
self.run_command('share-group-snapshot-list --columns id,name')
|
||||
|
||||
self.assert_called('GET', '/share-group-snapshots/detail')
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['Id', 'Name'])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_members(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': 'fake-cg-id'})
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
def test_share_group_snapshot_list_all_tenants_only_key(self):
|
||||
self.run_command('share-group-snapshot-list --all-tenants')
|
||||
|
||||
self.run_command('cg-snapshot-members fake-cg-id')
|
||||
self.assert_called('GET', '/cgsnapshots/fake-cg-id/members')
|
||||
shell_v2._find_cg_snapshot.assert_called_with(mock.ANY, fcg.id)
|
||||
self.assert_called(
|
||||
'GET', '/share-group-snapshots/detail?all_tenants=1')
|
||||
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['Id', 'Size', 'Created_at',
|
||||
'Share_protocol', 'Share_id', 'Share_type_id'])
|
||||
|
||||
def test_cg_snapshot_list_all_tenants_only_key(self):
|
||||
self.run_command('cg-snapshot-list --all-tenants')
|
||||
self.assert_called('GET', '/cgsnapshots/detail?all_tenants=1')
|
||||
|
||||
def test_cg_snapshot_list_all_tenants_key_and_value_1(self):
|
||||
def test_share_group_snapshot_list_all_tenants_key_and_value_1(self):
|
||||
for separator in self.separators:
|
||||
self.run_command(
|
||||
'cg-snapshot-list --all-tenants' + separator + '1')
|
||||
self.assert_called('GET', '/cgsnapshots/detail?all_tenants=1')
|
||||
'share-group-snapshot-list --all-tenants' + separator + '1')
|
||||
|
||||
def test_cg_snapshot_list_with_filters(self):
|
||||
self.run_command('cg-snapshot-list --limit 10 --offset 0')
|
||||
self.assert_called('GET', '/cgsnapshots/detail?limit=10&offset=0')
|
||||
self.assert_called(
|
||||
'GET', '/share-group-snapshots/detail?all_tenants=1')
|
||||
|
||||
def test_share_group_snapshot_list_with_filters(self):
|
||||
self.run_command('share-group-snapshot-list --limit 10 --offset 0')
|
||||
|
||||
self.assert_called(
|
||||
'GET', '/share-group-snapshots/detail?limit=10&offset=0')
|
||||
|
||||
def test_share_group_snapshot_show(self):
|
||||
self.run_command('share-group-snapshot-show 1234')
|
||||
|
||||
self.assert_called('GET', '/share-group-snapshots/1234')
|
||||
|
||||
def test_share_group_snapshot_list_members(self):
|
||||
self.run_command('share-group-snapshot-list-members 1234')
|
||||
|
||||
self.assert_called('GET', '/share-group-snapshots/1234')
|
||||
|
||||
def test_share_group_snapshot_list_members_select_column(self):
|
||||
self.mock_object(cliutils, 'print_list')
|
||||
|
||||
self.run_command(
|
||||
'share-group-snapshot-list-members 1234 --columns id,size')
|
||||
|
||||
self.assert_called('GET', '/share-group-snapshots/1234')
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['Id', 'Size'])
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_share_group_snapshot', mock.Mock())
|
||||
def test_share_group_snapshot_reset_state(self):
|
||||
fake_sg_snapshot = type(
|
||||
'FakeShareGroupSnapshot', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group_snapshot.return_value = fake_sg_snapshot
|
||||
|
||||
self.run_command('share-group-snapshot-reset-state 1234')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-group-snapshots/1234/action',
|
||||
{'reset_status': {'status': 'available'}})
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_share_group_snapshot', mock.Mock())
|
||||
def test_share_group_snapshot_reset_state_with_flag(self):
|
||||
fake_sg_snapshot = type('FakeSGSnapshot', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group_snapshot.return_value = fake_sg_snapshot
|
||||
|
||||
self.run_command(
|
||||
'share-group-snapshot-reset-state --state creating 1234')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-group-snapshots/1234/action',
|
||||
{'reset_status': {'status': 'creating'}})
|
||||
|
||||
@ddt.data(
|
||||
'fake-cg-id',
|
||||
'--name fake_name fake-cg-id',
|
||||
"--description my_fake_description --name fake_name fake-cg-id",
|
||||
('--name new-name', {'name': 'new-name'}),
|
||||
('--description new-description', {'description': 'new-description'}),
|
||||
('--name new-name --description new-description',
|
||||
{'name': 'new-name', 'description': 'new-description'}),
|
||||
)
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_snapshot_create(self, data):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': 'fake-cg-id'})
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
@ddt.unpack
|
||||
def test_share_group_snapshot_update(self, cmd, expected_body):
|
||||
self.run_command('share-group-snapshot-update 1234 %s' % cmd)
|
||||
|
||||
cmd = 'cg-snapshot-create' + ' ' + data
|
||||
expected = {'share_group_snapshot': expected_body}
|
||||
self.assert_called('PUT', '/share-group-snapshots/1234', body=expected)
|
||||
|
||||
self.run_command(cmd)
|
||||
def test_try_update_share_group_snapshot_without_data(self):
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.run_command, 'share-group-snapshot-update 1234')
|
||||
|
||||
shell_v2._find_consistency_group.assert_called_with(mock.ANY, fcg.id)
|
||||
self.assert_called('POST', '/cgsnapshots')
|
||||
@mock.patch.object(shell_v2, '_find_share_group_snapshot', mock.Mock())
|
||||
def test_share_group_snapshot_delete(self):
|
||||
fake_sg_snapshot = type('FakeSGSnapshot', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group_snapshot.return_value = fake_sg_snapshot
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_delete(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
self.run_command('share-group-snapshot-delete fake-group-snapshot')
|
||||
|
||||
self.run_command('cg-snapshot-delete fake-cg')
|
||||
self.assert_called('DELETE', '/cgsnapshots/1234')
|
||||
self.assert_called('DELETE', '/share-group-snapshots/1234')
|
||||
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_delete_force(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
@mock.patch.object(shell_v2, '_find_share_group_snapshot', mock.Mock())
|
||||
def test_share_group_snapshot_delete_force(self):
|
||||
fake_sg_snapshot = type('FakeSGSnapshot', (object,), {'id': '1234'})
|
||||
shell_v2._find_share_group_snapshot.return_value = fake_sg_snapshot
|
||||
|
||||
self.run_command('cg-snapshot-delete --force fake-cg')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
{'force_delete': None})
|
||||
self.run_command(
|
||||
'share-group-snapshot-delete --force fake-sg-snapshot')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-group-snapshots/1234/action',
|
||||
{'force_delete': None})
|
||||
|
||||
def test_share_group_snapshot_delete_all_fail(self):
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_snapshot',
|
||||
mock.Mock(side_effect=Exception))
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.run_command, 'share-group-snapshot-delete fake-sg-snapshot')
|
||||
|
||||
def test_share_group_type_list(self):
|
||||
self.mock_object(shell_v2, '_print_share_group_type_list')
|
||||
|
||||
self.run_command('share-group-type-list')
|
||||
|
||||
self.assert_called('GET', '/share-group-types')
|
||||
shell_v2._print_share_group_type_list.assert_called_once_with(
|
||||
mock.ANY, default_share_group_type=mock.ANY, columns=mock.ANY)
|
||||
|
||||
def test_share_group_type_list_select_column(self):
|
||||
self.mock_object(shell_v2, '_print_share_group_type_list')
|
||||
|
||||
self.run_command('share-group-type-list --columns id,name')
|
||||
|
||||
self.assert_called('GET', '/share-group-types')
|
||||
shell_v2._print_share_group_type_list.assert_called_once_with(
|
||||
mock.ANY, default_share_group_type=mock.ANY, columns='id,name')
|
||||
|
||||
def test_share_group_type_list_default_share_type(self):
|
||||
self.run_command('share-group-type-list')
|
||||
|
||||
self.assert_called_anytime('GET', '/share-group-types/default')
|
||||
|
||||
def test_share_group_type_list_all(self):
|
||||
self.run_command('share-group-type-list --all')
|
||||
|
||||
self.assert_called_anytime('GET', '/share-group-types?is_public=all')
|
||||
|
||||
@ddt.data(('', mock.ANY), (' --columns id,name', 'id,name'))
|
||||
@ddt.unpack
|
||||
def test_share_group_specs_list(self, args_cmd, expected_columns):
|
||||
self.mock_object(shell_v2, '_print_type_and_extra_specs_list')
|
||||
|
||||
self.run_command('share-group-type-specs-list')
|
||||
|
||||
self.assert_called('GET', '/share-group-types?is_public=all')
|
||||
shell_v2._print_type_and_extra_specs_list.assert_called_once_with(
|
||||
mock.ANY, columns=mock.ANY)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_share_group_type_create_with_access(self, public):
|
||||
fake_share_type_1 = type('FakeShareType', (object,), {'id': '1234'})
|
||||
fake_share_type_2 = type('FakeShareType', (object,), {'id': '5678'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_type',
|
||||
mock.Mock(side_effect=[fake_share_type_1, fake_share_type_2]))
|
||||
expected = {
|
||||
'share_group_type': {
|
||||
'name': 'test-group-type-1',
|
||||
'share_types': ['1234', '5678'],
|
||||
'group_specs': {},
|
||||
'is_public': public,
|
||||
}
|
||||
}
|
||||
|
||||
self.run_command(
|
||||
'share-group-type-create test-group-type-1 type1,type2 '
|
||||
'--is-public %s' % six.text_type(public))
|
||||
|
||||
self.assert_called('POST', '/share-group-types', body=expected)
|
||||
|
||||
def test_share_group_type_delete(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,), {'id': '1234'})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
self.run_command('share-group-type-delete test-group-type-1')
|
||||
|
||||
self.assert_called('DELETE', '/share-group-types/1234')
|
||||
|
||||
def test_share_group_type_key_set(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': False, 'set_keys': mock.Mock(),
|
||||
'unset_keys': mock.Mock()})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
self.run_command('share-group-type-key fake_sg_type set key1=value1')
|
||||
|
||||
fake_share_group_type.set_keys.assert_called_with({'key1': 'value1'})
|
||||
|
||||
def test_share_group_type_key_unset(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': False, 'set_keys': mock.Mock(),
|
||||
'unset_keys': mock.Mock()})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
self.run_command('share-group-type-key fake_group_type unset key1')
|
||||
|
||||
fake_share_group_type.unset_keys.assert_called_with(['key1'])
|
||||
|
||||
def test_share_group_type_access_list(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': False})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
self.run_command('share-group-type-access-list 1234')
|
||||
|
||||
self.assert_called('GET', '/share-group-types/1234/access')
|
||||
|
||||
def test_share_group_type_access_list_public(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': True})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.run_command, 'share-group-type-access-list 1234')
|
||||
|
||||
def test_share_group_type_access_add_project(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': False})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
expected = {'addProjectAccess': {'project': '101'}}
|
||||
|
||||
self.run_command('share-group-type-access-add 1234 101')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-group-types/1234/action', body=expected)
|
||||
|
||||
def test_share_group_type_access_remove_project(self):
|
||||
fake_share_group_type = type(
|
||||
'FakeShareGroupType', (object,),
|
||||
{'id': '1234', 'is_public': False})
|
||||
self.mock_object(
|
||||
shell_v2, '_find_share_group_type',
|
||||
mock.Mock(return_value=fake_share_group_type))
|
||||
expected = {'removeProjectAccess': {'project': '101'}}
|
||||
|
||||
self.run_command('share-group-type-access-remove 1234 101')
|
||||
|
||||
self.assert_called(
|
||||
'POST', '/share-group-types/1234/action', body=expected)
|
||||
|
||||
@ddt.data(
|
||||
{'--shares': 5},
|
||||
|
@ -23,8 +23,6 @@ from manilaclient.common import constants
|
||||
from manilaclient.common import httpclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.v2 import availability_zones
|
||||
from manilaclient.v2 import consistency_group_snapshots as cg_snapshots
|
||||
from manilaclient.v2 import consistency_groups
|
||||
from manilaclient.v2 import limits
|
||||
from manilaclient.v2 import quota_classes
|
||||
from manilaclient.v2 import quotas
|
||||
@ -32,6 +30,10 @@ from manilaclient.v2 import scheduler_stats
|
||||
from manilaclient.v2 import security_services
|
||||
from manilaclient.v2 import services
|
||||
from manilaclient.v2 import share_export_locations
|
||||
from manilaclient.v2 import share_group_snapshots
|
||||
from manilaclient.v2 import share_group_type_access
|
||||
from manilaclient.v2 import share_group_types
|
||||
from manilaclient.v2 import share_groups
|
||||
from manilaclient.v2 import share_instance_export_locations
|
||||
from manilaclient.v2 import share_instances
|
||||
from manilaclient.v2 import share_networks
|
||||
@ -219,6 +221,12 @@ class Client(object):
|
||||
self.shares = shares.ShareManager(self)
|
||||
self.share_export_locations = (
|
||||
share_export_locations.ShareExportLocationManager(self))
|
||||
self.share_groups = share_groups.ShareGroupManager(self)
|
||||
self.share_group_snapshots = (
|
||||
share_group_snapshots.ShareGroupSnapshotManager(self))
|
||||
self.share_group_type_access = (
|
||||
share_group_type_access.ShareGroupTypeAccessManager(self))
|
||||
self.share_group_types = share_group_types.ShareGroupTypeManager(self)
|
||||
self.share_instances = share_instances.ShareInstanceManager(self)
|
||||
self.share_instance_export_locations = (
|
||||
share_instance_export_locations.ShareInstanceExportLocationManager(
|
||||
@ -232,10 +240,6 @@ class Client(object):
|
||||
self.share_servers = share_servers.ShareServerManager(self)
|
||||
self.share_replicas = share_replicas.ShareReplicaManager(self)
|
||||
self.pools = scheduler_stats.PoolManager(self)
|
||||
self.consistency_groups = (
|
||||
consistency_groups.ConsistencyGroupManager(self))
|
||||
self.cg_snapshots = (
|
||||
cg_snapshots.ConsistencyGroupSnapshotManager(self))
|
||||
|
||||
self._load_extensions(extensions)
|
||||
|
||||
|
@ -1,202 +0,0 @@
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# 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.
|
||||
"""Interface for consistency group snapshots extension."""
|
||||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/cgsnapshots'
|
||||
RESOURCE_PATH = '/cgsnapshots/%s'
|
||||
RESOURCE_PATH_ACTION = '/cgsnapshots/%s/action'
|
||||
RESOURCES_NAME = 'cgsnapshots'
|
||||
RESOURCE_NAME = 'cgsnapshot'
|
||||
MEMBERS_RESOURCE_NAME = 'cgsnapshot_members'
|
||||
|
||||
|
||||
class ConsistencyGroupSnapshot(common_base.Resource):
|
||||
"""A group of snapshots taken of multiple shares."""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Consistency Group Snapshot: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this consistency group snapshot."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this consistency group snapshot."""
|
||||
self.manager.delete(self)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the consistency group snapshot with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
|
||||
class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
||||
resource_class = ConsistencyGroupSnapshot
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def create(self, consistency_group_id, name=None, description=None):
|
||||
"""Create a consistency group snapshot.
|
||||
|
||||
:param name: text - name of the new cg snapshot
|
||||
:param description: text - description of the cg snapshot
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
body = {
|
||||
'consistency_group_id': consistency_group_id,
|
||||
'name': name,
|
||||
'description': description,
|
||||
}
|
||||
return self._create(RESOURCES_PATH,
|
||||
{RESOURCE_NAME: body},
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def get(self, cg_snapshot):
|
||||
"""Get a consistency group snapshot.
|
||||
|
||||
:param cg_snapshot: either cg snapshot object or text with
|
||||
its ID.
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
consistency_group_id = common_base.getid(cg_snapshot)
|
||||
return self._get(RESOURCE_PATH % consistency_group_id,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def update(self, cg_snapshot, **kwargs):
|
||||
"""Updates a consistency group snapshot.
|
||||
|
||||
:param cg_snapshot: either consistency group snapshot object or text
|
||||
with its ID.
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {RESOURCE_NAME: kwargs}
|
||||
cg_snapshot_id = common_base.getid(cg_snapshot)
|
||||
return self._update(RESOURCE_PATH % cg_snapshot_id,
|
||||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all consistency group snapshots.
|
||||
|
||||
:param detailed: Whether to return detailed snapshot info or not.
|
||||
:param search_opts: dict with search options to filter out snapshots.
|
||||
available keys are below (('name1', 'name2', ...), 'type'):
|
||||
- ('all_tenants', int)
|
||||
- ('offset', int)
|
||||
- ('limit', int)
|
||||
Note, that member context will have restricted set of
|
||||
available search options.
|
||||
:rtype: list of :class:`ConsistencyGroupSnapshot`
|
||||
"""
|
||||
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
query_string = self._query_string_helper(search_opts)
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + '/detail%s' % (query_string,)
|
||||
else:
|
||||
path = RESOURCES_PATH + '%s' % (query_string,)
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
|
||||
def _do_delete(self, cg_snapshot, force=False, action_name='force_delete'):
|
||||
"""Delete a consistency group snapshot.
|
||||
|
||||
:param cg_snapshot: either a cg snapshot object or text wit its ID.
|
||||
"""
|
||||
cg_id = common_base.getid(cg_snapshot)
|
||||
body = None
|
||||
|
||||
if force:
|
||||
body = {action_name: None}
|
||||
|
||||
if body:
|
||||
self.api.client.post(RESOURCE_PATH_ACTION % cg_id, body=body)
|
||||
else:
|
||||
self._delete(RESOURCE_PATH % cg_id)
|
||||
|
||||
@api_versions.wraps("2.4", "2.6")
|
||||
@api_versions.experimental_api
|
||||
def delete(self, cg_snapshot, force=False):
|
||||
return self._do_delete(cg_snapshot, force, 'os-force_delete')
|
||||
|
||||
@api_versions.wraps("2.7") # noqa
|
||||
@api_versions.experimental_api
|
||||
def delete(self, cg_snapshot, force=False):
|
||||
return self._do_delete(cg_snapshot, force, 'force_delete')
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def members(self, cg_snapshot, search_opts=None):
|
||||
"""Get a list of consistency group snapshot members.
|
||||
|
||||
:param search_opts: dict with search options to filter out members.
|
||||
- ('offset', int)
|
||||
- ('limit', int)
|
||||
:rtype: list of :class:`ConsistencyGroupSnapshot`
|
||||
"""
|
||||
|
||||
consistency_group_id = common_base.getid(cg_snapshot)
|
||||
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
query_string = self._query_string_helper(search_opts)
|
||||
path = RESOURCES_PATH + '/%s/members%s' % (consistency_group_id,
|
||||
query_string,)
|
||||
|
||||
return self._list(path, MEMBERS_RESOURCE_NAME)
|
||||
|
||||
def _do_reset_state(self, cg_snapshot, state, action_name):
|
||||
"""Update the specified consistency group with the provided state."""
|
||||
body = {action_name: {'status': state}}
|
||||
cg_id = common_base.getid(cg_snapshot)
|
||||
url = RESOURCE_PATH_ACTION % cg_id
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
@api_versions.wraps("2.4", "2.6")
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, cg_snapshot, state):
|
||||
return self._do_reset_state(cg_snapshot, state, 'os-reset_status')
|
||||
|
||||
@api_versions.wraps("2.7") # noqa
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, cg_snapshot, state):
|
||||
return self._do_reset_state(cg_snapshot, state, 'reset_status')
|
||||
|
||||
def _query_string_helper(self, search_opts):
|
||||
q_string = parse.urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if q_string:
|
||||
q_string = "?%s" % (q_string,)
|
||||
else:
|
||||
q_string = ''
|
||||
return q_string
|
@ -1,199 +0,0 @@
|
||||
# Copyright 2015 Andrew Kerr
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# 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.
|
||||
"""Interface for consistency groups extension."""
|
||||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/consistency-groups'
|
||||
RESOURCE_PATH = '/consistency-groups/%s'
|
||||
RESOURCE_PATH_ACTION = '/consistency-groups/%s/action'
|
||||
RESOURCES_NAME = 'consistency_groups'
|
||||
RESOURCE_NAME = 'consistency_group'
|
||||
|
||||
|
||||
class ConsistencyGroup(common_base.Resource):
|
||||
"""A consistency group is a logical grouping of shares."""
|
||||
def __repr__(self):
|
||||
return "<Consistency Group: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this consistency group."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this consistency group."""
|
||||
self.manager.delete(self)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the consistency group with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
|
||||
class ConsistencyGroupManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ConsistencyGroup` resources."""
|
||||
resource_class = ConsistencyGroup
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def create(self, share_network=None, name=None, description=None,
|
||||
source_cgsnapshot_id=None, share_types=None):
|
||||
"""Create a Consistency Group.
|
||||
|
||||
:param share_network: either the share network object or text of the
|
||||
uuid - represents the share network to use when creating a
|
||||
consistency group with multi-svm capabilities.
|
||||
:param name: text - name of the new consistency group
|
||||
:param description: text - description of the consistency group
|
||||
:param source_cgsnapshot_id: text - The uuid of the cgsnapshot from
|
||||
which this CG was created. Cannot be supplied when 'share_types'
|
||||
is provided.
|
||||
:param share_types: List of the share types that shares in the CG are
|
||||
allowed to be a part of. Cannot be supplied when
|
||||
'source_cgsnapshot_id' is provided.
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
if share_types:
|
||||
share_types = [common_base.getid(share_type)
|
||||
for share_type in share_types]
|
||||
|
||||
body = {'name': name, 'description': description}
|
||||
|
||||
share_network_id = None
|
||||
if share_network:
|
||||
share_network_id = common_base.getid(share_network)
|
||||
if share_network_id:
|
||||
body['share_network_id'] = share_network_id
|
||||
if source_cgsnapshot_id:
|
||||
body['source_cgsnapshot_id'] = source_cgsnapshot_id
|
||||
if share_types:
|
||||
body['share_types'] = share_types
|
||||
return self._create(RESOURCES_PATH,
|
||||
{RESOURCE_NAME: body}, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def get(self, consistency_group):
|
||||
"""Get a consistency group.
|
||||
|
||||
:param consistency_group: either consistency group object or text with
|
||||
its ID.
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
consistency_group_id = common_base.getid(consistency_group)
|
||||
return self._get(RESOURCE_PATH % consistency_group_id,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def update(self, consistency_group, **kwargs):
|
||||
"""Updates a consistency group.
|
||||
|
||||
:param consistency_group: either consistency group object or text
|
||||
with its ID.
|
||||
:rtype: :class:`ConsistencyGroup`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {RESOURCE_NAME: kwargs}
|
||||
consistency_group_id = common_base.getid(consistency_group)
|
||||
return self._update(RESOURCE_PATH % consistency_group_id,
|
||||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
@api_versions.experimental_api
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all shares.
|
||||
|
||||
:param detailed: Whether to return detailed share info or not.
|
||||
:param search_opts: dict with search options to filter out shares.
|
||||
available keys are below (('name1', 'name2', ...), 'type'):
|
||||
- ('offset', int)
|
||||
- ('limit', int)
|
||||
:rtype: list of :class:`ConsistencyGroup`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
query_string = self._query_string_helper(search_opts)
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + '/detail%s' % (query_string,)
|
||||
else:
|
||||
path = RESOURCES_PATH + '%s' % (query_string,)
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
|
||||
def _do_delete(self, consistency_group, force=False,
|
||||
action_name='force_delete'):
|
||||
"""Delete a consistency group.
|
||||
|
||||
:param consistency_group: either consistency group object or text with
|
||||
its ID.
|
||||
"""
|
||||
cg_id = common_base.getid(consistency_group)
|
||||
url = RESOURCE_PATH % cg_id
|
||||
body = None
|
||||
|
||||
if force:
|
||||
body = {action_name: None}
|
||||
|
||||
if body:
|
||||
self.api.client.post(url + '/action', body=body)
|
||||
else:
|
||||
self._delete(url)
|
||||
|
||||
@api_versions.wraps("2.4", "2.6")
|
||||
@api_versions.experimental_api
|
||||
def delete(self, consistency_group, force=False):
|
||||
return self._do_delete(consistency_group, force, 'os-force_delete')
|
||||
|
||||
@api_versions.wraps("2.7") # noqa
|
||||
@api_versions.experimental_api
|
||||
def delete(self, consistency_group, force=False):
|
||||
return self._do_delete(consistency_group, force, 'force_delete')
|
||||
|
||||
def _do_reset_state(self, consistency_group, state, action_name):
|
||||
"""Update the specified consistency group with the provided state."""
|
||||
body = {action_name: {'status': state}}
|
||||
url = RESOURCE_PATH_ACTION % common_base.getid(consistency_group)
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
@api_versions.wraps("2.4", "2.6")
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, cg, state):
|
||||
return self._do_reset_state(
|
||||
consistency_group, state, 'os-reset_status')
|
||||
|
||||
@api_versions.wraps("2.7") # noqa
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, consistency_group, state):
|
||||
return self._do_reset_state(consistency_group, state, 'reset_status')
|
||||
|
||||
def _query_string_helper(self, search_opts):
|
||||
q_string = parse.urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if q_string:
|
||||
q_string = "?%s" % (q_string,)
|
||||
else:
|
||||
q_string = ''
|
||||
return q_string
|
180
manilaclient/v2/share_group_snapshots.py
Normal file
180
manilaclient/v2/share_group_snapshots.py
Normal file
@ -0,0 +1,180 @@
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# Copyright 2016 Clinton Knight
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
from manilaclient.common import constants
|
||||
|
||||
RESOURCES_PATH = '/share-group-snapshots'
|
||||
RESOURCE_PATH = '/share-group-snapshots/%s'
|
||||
RESOURCE_PATH_ACTION = '/share-group-snapshots/%s/action'
|
||||
RESOURCES_NAME = 'share_group_snapshots'
|
||||
RESOURCE_NAME = 'share_group_snapshot'
|
||||
|
||||
|
||||
class ShareGroupSnapshot(common_base.Resource):
|
||||
"""A snapshot of a share group."""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Share Group Snapshot: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share group snapshot."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this share group snapshot."""
|
||||
self.manager.delete(self)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update this share group snapshot with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
|
||||
class ShareGroupSnapshotManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareGroupSnapshot` resources."""
|
||||
resource_class = ShareGroupSnapshot
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def create(self, share_group, name=None, description=None):
|
||||
"""Create a share group snapshot.
|
||||
|
||||
:param share_group: either ShareGroup object or text with its UUID
|
||||
:param name: text - name of the new group snapshot
|
||||
:param description: text - description of the group snapshot
|
||||
:rtype: :class:`ShareGroupSnapshot`
|
||||
"""
|
||||
share_group_id = common_base.getid(share_group)
|
||||
body = {'share_group_id': share_group_id}
|
||||
if name:
|
||||
body['name'] = name
|
||||
if description:
|
||||
body['description'] = description
|
||||
|
||||
return self._create(
|
||||
RESOURCES_PATH, {RESOURCE_NAME: body}, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def get(self, share_group_snapshot):
|
||||
"""Get a share group snapshot.
|
||||
|
||||
:param share_group_snapshot: either share group snapshot object or text
|
||||
with its UUID
|
||||
:rtype: :class:`ShareGroupSnapshot`
|
||||
"""
|
||||
share_group_snapshot_id = common_base.getid(share_group_snapshot)
|
||||
url = RESOURCE_PATH % share_group_snapshot_id
|
||||
return self._get(url, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all share group snapshots.
|
||||
|
||||
:param detailed: Whether to return detailed snapshot info or not.
|
||||
:param search_opts: dict with search options to filter out snapshots.
|
||||
available keys are below (('name1', 'name2', ...), 'type'):
|
||||
- ('all_tenants', int)
|
||||
- ('offset', int)
|
||||
- ('limit', int)
|
||||
- ('name', text)
|
||||
- ('status', text)
|
||||
- ('share_group_id', text)
|
||||
:param sort_key: Key to be sorted (i.e. 'created_at' or 'status').
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`ShareGroupSnapshot`
|
||||
"""
|
||||
|
||||
if not search_opts:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SHARE_GROUP_SNAPSHOT_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
else:
|
||||
msg = 'sort_key must be one of the following: %s.'
|
||||
msg_args = ', '.join(
|
||||
constants.SHARE_GROUP_SNAPSHOT_SORT_KEY_VALUES)
|
||||
raise ValueError(msg % msg_args)
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError('sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
query_string = self._build_query_string(search_opts)
|
||||
|
||||
if detailed:
|
||||
url = RESOURCES_PATH + '/detail' + query_string
|
||||
else:
|
||||
url = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(url, RESOURCES_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def update(self, share_group_snapshot, **kwargs):
|
||||
"""Updates a share group snapshot.
|
||||
|
||||
:param share_group_snapshot: either ShareGroupSnapshot object or text
|
||||
with its UUID
|
||||
:rtype: :class:`ShareGroupSnapshot`
|
||||
"""
|
||||
share_group_snapshot_id = common_base.getid(share_group_snapshot)
|
||||
url = RESOURCE_PATH % share_group_snapshot_id
|
||||
if not kwargs:
|
||||
return self._get(url, RESOURCE_NAME)
|
||||
else:
|
||||
body = {RESOURCE_NAME: kwargs}
|
||||
return self._update(url, body, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def delete(self, share_group_snapshot, force=False):
|
||||
"""Delete a share group snapshot.
|
||||
|
||||
:param share_group_snapshot: either ShareGroupSnapshot object or text
|
||||
with its UUID
|
||||
:param force: True to force the deletion
|
||||
"""
|
||||
share_group_snapshot_id = common_base.getid(share_group_snapshot)
|
||||
if force:
|
||||
url = RESOURCE_PATH_ACTION % share_group_snapshot_id
|
||||
body = {'force_delete': None}
|
||||
self.api.client.post(url, body=body)
|
||||
else:
|
||||
url = RESOURCE_PATH % share_group_snapshot_id
|
||||
self._delete(url)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, share_group_snapshot, state):
|
||||
"""Update the specified share group snapshot.
|
||||
|
||||
:param share_group_snapshot: either ShareGroupSnapshot object or text
|
||||
with its UUID
|
||||
:param state: The new state for the share group snapshot
|
||||
"""
|
||||
share_group_snapshot_id = common_base.getid(share_group_snapshot)
|
||||
url = RESOURCE_PATH_ACTION % share_group_snapshot_id
|
||||
body = {'reset_status': {'status': state}}
|
||||
self.api.client.post(url, body=body)
|
64
manilaclient/v2/share_group_type_access.py
Normal file
64
manilaclient/v2/share_group_type_access.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
#
|
||||
# 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.
|
||||
"""Share group type access interface."""
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/share-group-types'
|
||||
RESOURCE_PATH = '/share-group-types/%s/access'
|
||||
RESOURCE_PATH_ACTION = '/share-group-types/%s/action'
|
||||
RESOURCE_NAME = 'share_group_type_access'
|
||||
|
||||
|
||||
class ShareGroupTypeAccess(common_base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Share Group Type Access: %s>" % self.id
|
||||
|
||||
|
||||
class ShareGroupTypeAccessManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareGroupTypeAccess` resources."""
|
||||
resource_class = ShareGroupTypeAccess
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def list(self, share_group_type):
|
||||
if share_group_type.is_public:
|
||||
return None
|
||||
share_group_type_id = common_base.getid(share_group_type)
|
||||
url = RESOURCE_PATH % share_group_type_id
|
||||
return self._list(url, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def add_project_access(self, share_group_type, project):
|
||||
"""Add a project to the given share group type access list."""
|
||||
info = {'project': project}
|
||||
self._action('addProjectAccess', share_group_type, info)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def remove_project_access(self, share_group_type, project):
|
||||
"""Remove a project from the given share group type access list."""
|
||||
info = {'project': project}
|
||||
self._action('removeProjectAccess', share_group_type, info)
|
||||
|
||||
def _action(self, action, share_group_type, info, **kwargs):
|
||||
"""Perform a share group type action."""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
share_group_type_id = common_base.getid(share_group_type)
|
||||
url = RESOURCE_PATH_ACTION % share_group_type_id
|
||||
return self.api.client.post(url, body=body)
|
153
manilaclient/v2/share_group_types.py
Normal file
153
manilaclient/v2/share_group_types.py
Normal file
@ -0,0 +1,153 @@
|
||||
# Copyright 2016 Clinton Knight
|
||||
#
|
||||
# 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.
|
||||
"""Interface for share group types extension."""
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/share-group-types'
|
||||
RESOURCE_PATH = '/share-group-types/%s'
|
||||
RESOURCE_PATH_ACTION = '/share-group-types/%s/action'
|
||||
RESOURCES_NAME = 'share_group_types'
|
||||
RESOURCE_NAME = 'share_group_type'
|
||||
|
||||
GROUP_SPECS_RESOURCES_PATH = '/share-group-types/%s/group-specs'
|
||||
GROUP_SPECS_RESOURCE_PATH = '/share-group-types/%s/group-specs/%s'
|
||||
GROUP_SPECS_RESOURCES_NAME = 'group_specs'
|
||||
|
||||
|
||||
class ShareGroupType(common_base.Resource):
|
||||
"""A Share Group Type is the type of share group to be created."""
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
super(ShareGroupType, self).__init__(manager, info, loaded)
|
||||
self._group_specs = info.get(GROUP_SPECS_RESOURCES_NAME, {})
|
||||
|
||||
def __repr__(self):
|
||||
return "<Share Group Type: %s>" % self.name
|
||||
|
||||
@property
|
||||
def is_public(self):
|
||||
"""Provide a user-friendly accessor to share-type-access."""
|
||||
return self._info.get('is_public', 'N/A')
|
||||
|
||||
def get_keys(self, prefer_resource_data=True):
|
||||
"""Get group specs from a share group type.
|
||||
|
||||
:param prefer_resource_data: By default group_specs are retrieved from
|
||||
resource data, but user can force this method to make an API call
|
||||
and update the group specs in this object.
|
||||
:return: dict with group specs
|
||||
"""
|
||||
if prefer_resource_data:
|
||||
return self._group_specs
|
||||
else:
|
||||
share_group_type_id = common_base.getid(self)
|
||||
url = GROUP_SPECS_RESOURCES_PATH % share_group_type_id
|
||||
_resp, body = self.manager.api.client.get(url)
|
||||
self._group_specs = body.get(GROUP_SPECS_RESOURCES_NAME, {})
|
||||
return self._group_specs
|
||||
|
||||
def set_keys(self, group_specs):
|
||||
"""Set group specs on a share group type.
|
||||
|
||||
:param extra_specs: A dict of key/value pairs to be set on this object
|
||||
:return: dict with group specs
|
||||
"""
|
||||
share_group_type_id = common_base.getid(self)
|
||||
url = GROUP_SPECS_RESOURCES_PATH % share_group_type_id
|
||||
body = {GROUP_SPECS_RESOURCES_NAME: group_specs}
|
||||
return self.manager._create(
|
||||
url, body, GROUP_SPECS_RESOURCES_NAME, return_raw=True)
|
||||
|
||||
def unset_keys(self, keys):
|
||||
"""Unset group specs on a share group type.
|
||||
|
||||
:param keys: A list of keys on this object to be unset
|
||||
:return: None if successful, else API response on failure
|
||||
"""
|
||||
share_group_type_id = common_base.getid(self)
|
||||
for k in keys:
|
||||
url = GROUP_SPECS_RESOURCE_PATH % (share_group_type_id, k)
|
||||
resp = self.manager._delete(url)
|
||||
if resp is not None:
|
||||
return resp
|
||||
|
||||
|
||||
class ShareGroupTypeManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareGroupType` resources."""
|
||||
resource_class = ShareGroupType
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def create(self, name, share_types, is_public=False, group_specs=None):
|
||||
"""Create a share group type.
|
||||
|
||||
:param name: Descriptive name of the share group type
|
||||
:param share_types: list of either instances of ShareType or text
|
||||
with share type UUIDs
|
||||
:param is_public: True to create a public share group type
|
||||
:param group_specs: dict containing group spec key-value pairs
|
||||
:rtype: :class:`ShareGroupType`
|
||||
"""
|
||||
if not share_types:
|
||||
raise ValueError('At least one share type must be specified when '
|
||||
'creating a share group type.')
|
||||
body = {
|
||||
'name': name,
|
||||
'is_public': is_public,
|
||||
'group_specs': group_specs or {},
|
||||
'share_types': [common_base.getid(share_type)
|
||||
for share_type in share_types],
|
||||
}
|
||||
return self._create(
|
||||
RESOURCES_PATH, {RESOURCE_NAME: body}, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def get(self, share_group_type="default"):
|
||||
"""Get a specific share group type.
|
||||
|
||||
:param share_group_type: either instance of ShareGroupType, or text
|
||||
with UUID, or 'default'
|
||||
:rtype: :class:`ShareGroupType`
|
||||
"""
|
||||
share_group_type_id = common_base.getid(share_group_type)
|
||||
url = RESOURCE_PATH % share_group_type_id
|
||||
return self._get(url, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def list(self, show_all=True):
|
||||
"""Get a list of all share group types.
|
||||
|
||||
:rtype: list of :class:`ShareGroupType`.
|
||||
"""
|
||||
query_string = '?is_public=all' if show_all else ''
|
||||
url = RESOURCES_PATH + query_string
|
||||
return self._list(url, RESOURCES_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def delete(self, share_group_type):
|
||||
"""Delete a specific share group type.
|
||||
|
||||
:param share_group_type: either instance of ShareGroupType, or text
|
||||
with UUID
|
||||
"""
|
||||
share_group_type_id = common_base.getid(share_group_type)
|
||||
url = RESOURCE_PATH % share_group_type_id
|
||||
self._delete(url)
|
219
manilaclient/v2/share_groups.py
Normal file
219
manilaclient/v2/share_groups.py
Normal file
@ -0,0 +1,219 @@
|
||||
# Copyright 2015 Andrew Kerr
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# Copyright 2016 Clinton Knight
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.common.apiclient import base as common_base
|
||||
from manilaclient.common import constants
|
||||
|
||||
RESOURCES_PATH = '/share-groups'
|
||||
RESOURCE_PATH = '/share-groups/%s'
|
||||
RESOURCE_PATH_ACTION = '/share-groups/%s/action'
|
||||
RESOURCES_NAME = 'share_groups'
|
||||
RESOURCE_NAME = 'share_group'
|
||||
|
||||
|
||||
class ShareGroup(common_base.Resource):
|
||||
"""A share group is a logical grouping of shares on a single backend."""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Share Group: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share group."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self, force=False):
|
||||
"""Delete this share group."""
|
||||
self.manager.delete(self, force=force)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update this share group with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
|
||||
class ShareGroupManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareGroup` resources."""
|
||||
resource_class = ShareGroup
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def create(self, share_group_type=None, share_types=None,
|
||||
share_network=None, name=None, description=None,
|
||||
source_share_group_snapshot=None, availability_zone=None):
|
||||
"""Create a Share Group.
|
||||
|
||||
:param share_group_type: either instance of ShareGroupType or text
|
||||
with UUID
|
||||
:param share_types: list of the share types allowed in the group. May
|
||||
not be supplied when 'source_group_snapshot_id' is provided. These
|
||||
may be ShareType objects or UUIDs.
|
||||
:param share_network: either the share network object or text of the
|
||||
UUID - represents the share network to use when creating a
|
||||
share group when driver_handles_share_servers = True.
|
||||
:param name: text - name of the new share group
|
||||
:param description: text - description of the share group
|
||||
:param source_share_group_snapshot: text - either instance of
|
||||
ShareGroupSnapshot or text with UUID from which this shar_group is
|
||||
to be created. May not be supplied when 'share_types' is provided.
|
||||
:param availability_zone: name of the availability zone where the
|
||||
group is to be created
|
||||
:rtype: :class:`ShareGroup`
|
||||
"""
|
||||
|
||||
if share_types and source_share_group_snapshot:
|
||||
raise ValueError('Cannot specify a share group with both'
|
||||
'share_types and source_share_group_snapshot.')
|
||||
|
||||
body = {}
|
||||
|
||||
if name:
|
||||
body['name'] = name
|
||||
if description:
|
||||
body['description'] = description
|
||||
if availability_zone:
|
||||
body['availability_zone'] = availability_zone
|
||||
if share_group_type:
|
||||
body['share_group_type_id'] = common_base.getid(share_group_type)
|
||||
if share_network:
|
||||
body['share_network_id'] = common_base.getid(share_network)
|
||||
|
||||
if source_share_group_snapshot:
|
||||
body['source_share_group_snapshot_id'] = common_base.getid(
|
||||
source_share_group_snapshot)
|
||||
elif share_types:
|
||||
body['share_types'] = [common_base.getid(share_type)
|
||||
for share_type in share_types]
|
||||
|
||||
return self._create(
|
||||
RESOURCES_PATH, {RESOURCE_NAME: body}, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def get(self, share_group):
|
||||
"""Get a share group.
|
||||
|
||||
:param share_group: either ShareGroup object or text with its UUID
|
||||
:rtype: :class:`ShareGroup`
|
||||
"""
|
||||
share_group_id = common_base.getid(share_group)
|
||||
url = RESOURCE_PATH % share_group_id
|
||||
return self._get(url, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all share groups.
|
||||
|
||||
:param detailed: Whether to return detailed share group info or not.
|
||||
:param search_opts: dict with search options to filter out groups.
|
||||
available keys include (('name1', 'name2', ...), 'type'):
|
||||
- ('offset', int)
|
||||
- ('limit', int)
|
||||
- ('all_tenants', int)
|
||||
- ('name', text)
|
||||
- ('status', text)
|
||||
- ('share_server_id', text)
|
||||
- ('share_group_type_id', text)
|
||||
- ('source_share_group_snapshot_id', text)
|
||||
- ('host', text)
|
||||
- ('share_network_id', text)
|
||||
- ('project_id', text)
|
||||
:param sort_key: Key to be sorted (i.e. 'created_at' or 'status').
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`ShareGroup`
|
||||
"""
|
||||
|
||||
if not search_opts:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SHARE_GROUP_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
# NOTE(cknight): Replace aliases with appropriate keys
|
||||
if sort_key == 'share_group_type':
|
||||
search_opts['sort_key'] = 'share_group_type_id'
|
||||
elif sort_key == 'share_network':
|
||||
search_opts['sort_key'] = 'share_network_id'
|
||||
else:
|
||||
msg = 'sort_key must be one of the following: %s.'
|
||||
msg_args = ', '.join(constants.SHARE_GROUP_SORT_KEY_VALUES)
|
||||
raise ValueError(msg % msg_args)
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError('sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
query_string = self._build_query_string(search_opts)
|
||||
|
||||
if detailed:
|
||||
url = RESOURCES_PATH + '/detail' + query_string
|
||||
else:
|
||||
url = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(url, RESOURCES_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def update(self, share_group, **kwargs):
|
||||
"""Updates a share group.
|
||||
|
||||
:param share_group: either ShareGroup object or text with its UUID
|
||||
:rtype: :class:`ShareGroup`
|
||||
"""
|
||||
share_group_id = common_base.getid(share_group)
|
||||
url = RESOURCE_PATH % share_group_id
|
||||
if not kwargs:
|
||||
return self._get(url, RESOURCE_NAME)
|
||||
else:
|
||||
body = {RESOURCE_NAME: kwargs}
|
||||
return self._update(url, body, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def delete(self, share_group, force=False):
|
||||
"""Delete a share group.
|
||||
|
||||
:param share_group: either ShareGroup object or text with its UUID
|
||||
:param force: True to force the deletion
|
||||
"""
|
||||
share_group_id = common_base.getid(share_group)
|
||||
if force:
|
||||
url = RESOURCE_PATH_ACTION % share_group_id
|
||||
body = {'force_delete': None}
|
||||
self.api.client.post(url, body=body)
|
||||
else:
|
||||
url = RESOURCE_PATH % share_group_id
|
||||
self._delete(url)
|
||||
|
||||
@api_versions.wraps("2.31")
|
||||
@api_versions.experimental_api
|
||||
def reset_state(self, share_group, state):
|
||||
"""Update the specified share group with the provided state.
|
||||
|
||||
:param share_group: either ShareGroup object or text with its UUID
|
||||
:param state: The new state for the share group
|
||||
"""
|
||||
|
||||
share_group_id = common_base.getid(share_group)
|
||||
url = RESOURCE_PATH_ACTION % share_group_id
|
||||
body = {'reset_status': {'status': state}}
|
||||
self.api.client.post(url, body=body)
|
@ -69,9 +69,9 @@ class Share(common_base.Resource):
|
||||
"""Reset the task state of a given share."""
|
||||
self.manager.reset_task_state(self, task_state)
|
||||
|
||||
def delete(self, consistency_group_id=None):
|
||||
def delete(self, share_group_id=None):
|
||||
"""Delete this share."""
|
||||
self.manager.delete(self, consistency_group_id=consistency_group_id)
|
||||
self.manager.delete(self, share_group_id=share_group_id)
|
||||
|
||||
def force_delete(self):
|
||||
"""Delete the specified share ignoring its current state."""
|
||||
@ -121,7 +121,7 @@ class ShareManager(base.ManagerWithFind):
|
||||
def create(self, share_proto, size, snapshot_id=None, name=None,
|
||||
description=None, metadata=None, share_network=None,
|
||||
share_type=None, is_public=False, availability_zone=None,
|
||||
consistency_group_id=None):
|
||||
share_group_id=None):
|
||||
"""Create a share.
|
||||
|
||||
:param share_proto: text - share protocol for new share
|
||||
@ -134,8 +134,8 @@ class ShareManager(base.ManagerWithFind):
|
||||
:param share_network: either instance of ShareNetwork or text with ID
|
||||
:param share_type: either instance of ShareType or text with ID
|
||||
:param is_public: bool, whether to set share as public or not.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share should belong
|
||||
:param share_group_id: text - ID of the share group to which the share
|
||||
should belong
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
share_metadata = metadata if metadata is not None else dict()
|
||||
@ -150,8 +150,10 @@ class ShareManager(base.ManagerWithFind):
|
||||
'share_type': common_base.getid(share_type),
|
||||
'is_public': is_public,
|
||||
'availability_zone': availability_zone,
|
||||
'consistency_group_id': consistency_group_id,
|
||||
}
|
||||
if share_group_id:
|
||||
body['share_group_id'] = share_group_id
|
||||
|
||||
return self._create('/shares', {'share': body}, 'share')
|
||||
|
||||
@api_versions.wraps("2.29")
|
||||
@ -386,16 +388,16 @@ class ShareManager(base.ManagerWithFind):
|
||||
|
||||
return self._list(path, 'shares')
|
||||
|
||||
def delete(self, share, consistency_group_id=None):
|
||||
def delete(self, share, share_group_id=None):
|
||||
"""Delete a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share belongs to.
|
||||
:param share_group_id: text - ID of the share group to which the share
|
||||
belongs
|
||||
"""
|
||||
url = "/shares/%s" % common_base.getid(share)
|
||||
if consistency_group_id:
|
||||
url += "?consistency_group_id=%s" % consistency_group_id
|
||||
if share_group_id:
|
||||
url += "?share_group_id=%s" % share_group_id
|
||||
self._delete(url)
|
||||
|
||||
def _do_force_delete(self, share, action_name):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added share group support (replacing CG support).
|
||||
upgrade:
|
||||
- By adding share group support, the earlier support for CGs was removed.
|
||||
Preexisting CGs should be managed as share groups instead.
|
Loading…
Reference in New Issue
Block a user