Implement OSC share type commands

In this patch we add openstack commands for:
share type create
share type delete
share type set
share type unset
share type list
share type show
share type access create
share type access list
share type access delete

These commands can be used to replace all “manila type-” commands.
“openstack share type set” combines “manila type-key” with
“manila type-update” commands and can be used to set name, description,
visibility and extra specs.

Change-Id: I10cb6ea800908ebbe48eae7ba8c18680249ff1d2
Partially-implements: bp openstack-client-support
This commit is contained in:
Maari Tamm 2020-01-06 13:06:04 +00:00
parent 9fc3723de8
commit e3652b9c1c
8 changed files with 1476 additions and 1 deletions

View File

@ -97,3 +97,15 @@ STATUS_MANAGE_ERROR = 'manage_error'
STATUS_UNMANAGE_ERROR = 'unmanage_error'
STATUS_DELETING = 'deleting'
STATUS_CREATING = 'creating'
SNAPSHOT_SUPPORT = 'snapshot_support'
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = 'create_share_from_snapshot_support'
REVERT_TO_SNAPSHOT_SUPPORT = 'revert_to_snapshot_support'
MOUNT_SNAPSHOT_SUPPORT = 'mount_snapshot_support'
BOOL_SPECS = (
SNAPSHOT_SUPPORT,
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
REVERT_TO_SNAPSHOT_SUPPORT,
MOUNT_SNAPSHOT_SUPPORT
)

View File

@ -11,8 +11,11 @@
# 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 six
from oslo_utils import strutils
from manilaclient.common import constants
from manilaclient import exceptions
@ -37,6 +40,7 @@ def extract_key_value_options(pairs):
def format_properties(properties):
formatted_data = []
for item in properties:
formatted_data.append("%s : %s" % (item, properties[item]))
return "\n".join(formatted_data)
@ -57,3 +61,22 @@ def extract_properties(properties):
"Parsing error, expected format 'key=value' for " + item
)
return result_dict
def extract_extra_specs(extra_specs, specs_to_add):
for item in specs_to_add:
(key, value) = item.split('=', 1)
if key in extra_specs:
msg = ("Argument '%s' value specified twice." % key)
raise exceptions.CommandError(msg)
elif key in constants.BOOL_SPECS:
if strutils.is_valid_boolstr(value):
extra_specs[key] = value.capitalize()
else:
msg = ("Argument '%s' is of boolean "
"type and has invalid value: %s"
% (key, six.text_type(value)))
raise exceptions.CommandError(msg)
else:
extra_specs[key] = value
return extra_specs

View File

@ -0,0 +1,121 @@
# 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 logging
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils as oscutils
from manilaclient.common._i18n import _
from manilaclient.common.apiclient import utils as apiutils
LOG = logging.getLogger(__name__)
class ShareTypeAccessAllow(command.Command):
"""Add access for share type."""
_description = _("Add access for share type")
def get_parser(self, prog_name):
parser = super(ShareTypeAccessAllow, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Share type name or ID to add access to")
)
parser.add_argument(
'project_id',
metavar="<project_id>",
help=_("Project ID to add share type access for")
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types, parsed_args.share_type)
try:
share_client.share_type_access.add_project_access(
share_type,
parsed_args.project_id)
except Exception as e:
raise exceptions.CommandError(
"Failed to add access to share type : %s" % e)
class ListShareTypeAccess(command.Lister):
"""Get access list for share type."""
_description = _("Get access list for share type")
def get_parser(self, prog_name):
parser = super(ListShareTypeAccess, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Share type name or ID to get access list for")
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types, parsed_args.share_type)
if share_type._info.get('share_type_access:is_public'):
raise exceptions.CommandError(
'Forbidden to get access list for public share type.')
data = share_client.share_type_access.list(share_type)
columns = ['Project_ID']
values = (oscutils.get_item_properties(s, columns) for s in data)
return (columns, values)
class ShareTypeAccessDeny(command.Command):
"""Delete access from share type."""
_description = _("Delete access from share type")
def get_parser(self, prog_name):
parser = super(ShareTypeAccessDeny, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Share type name or ID to delete access from")
)
parser.add_argument(
'project_id',
metavar="<project_id>",
help=_("Project ID to delete share type access for")
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types, parsed_args.share_type)
try:
share_client.share_type_access.remove_project_access(
share_type,
parsed_args.project_id)
except Exception as e:
raise exceptions.CommandError(
"Failed to remove access from share type : %s" % e)

View File

@ -0,0 +1,468 @@
# 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 logging
import six
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils as oscutils
from oslo_utils import strutils
from manilaclient import api_versions
from manilaclient.common._i18n import _
from manilaclient.common.apiclient import utils as apiutils
from manilaclient.common import constants
from manilaclient.osc import utils
LOG = logging.getLogger(__name__)
ATTRIBUTES = [
'id',
'name',
'visibility',
'is_default',
'required_extra_specs',
'optional_extra_specs',
'description'
]
def format_share_type(share_type):
# share_type_access:is_public (true/false) --> visibility (public/private)
is_public = 'share_type_access:is_public'
visibility = 'public' if share_type._info.get(is_public) else 'private'
share_type._info.pop(is_public, None)
# optional_extra_specs --> extra_specs without required_extra_specs
# required_extra_specs are displayed separately
optional_extra_specs = share_type.extra_specs
for key in share_type.required_extra_specs.keys():
optional_extra_specs.pop(key, None)
share_type._info.update(
{
'visibility': visibility,
'required_extra_specs': utils.format_properties(
share_type.required_extra_specs),
'optional_extra_specs': utils.format_properties(
optional_extra_specs),
}
)
return share_type
class CreateShareType(command.ShowOne):
"""Create new share type."""
_description = _(
"Create new share type")
log = logging.getLogger(__name__ + ".CreateShareType")
def get_parser(self, prog_name):
parser = super(CreateShareType, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar="<name>",
default=None,
help=_('Share type name')
)
parser.add_argument(
'spec_driver_handles_share_servers',
metavar="<spec_driver_handles_share_servers>",
default=None,
help=_("Required extra specification. "
"Valid values are 'true' and 'false'")
)
parser.add_argument(
"--description",
metavar="<description>",
default=None,
help=_("Share type description. "
"Available only for microversion >= 2.41."),
)
parser.add_argument(
"--snapshot-support",
metavar="<snapshot_support>",
default=None,
help=_("Boolean extra spec used for filtering of back ends "
"by their capability to create share snapshots."),
)
parser.add_argument(
"--create-share-from-snapshot-support",
metavar="<create_share_from_snapshot_support>",
default=None,
help=_("Boolean extra spec used for filtering of back ends "
"by their capability to create shares from snapshots."),
)
parser.add_argument(
"--revert-to-snapshot-support",
metavar="<revert_to_snapshot_support>",
default=False,
help=_("Boolean extra spec used for filtering of back ends "
"by their capability to revert shares to snapshots. "
"(Default is False)."),
)
parser.add_argument(
"--mount-snapshot-support",
metavar="<mount_snapshot_support>",
default=False,
help=_("Boolean extra spec used for filtering of back ends "
"by their capability to mount share snapshots. "
"(Default is False)."),
)
parser.add_argument(
"--extra-specs",
type=str,
nargs='*',
metavar='<key=value>',
default=None,
help=_("Extra specs key and value of share type that will be"
" used for share type creation. OPTIONAL: Default=None."
" example --extra-specs thin_provisioning='<is> True', "
"replication_type=readable."),
)
parser.add_argument(
'--public',
metavar="<public>",
default=True,
help=_('Make type accessible to the public (default true).')
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
kwargs = {
'name': parsed_args.name
}
try:
kwargs['spec_driver_handles_share_servers'] = (
strutils.bool_from_string(
parsed_args.spec_driver_handles_share_servers,
strict=True))
except ValueError as e:
msg = ("Argument spec_driver_handles_share_servers "
"argument is not valid: %s" % six.text_type(e))
raise exceptions.CommandError(msg)
if parsed_args.description:
if share_client.api_version.matches(
api_versions.APIVersion("2.41"),
api_versions.APIVersion()):
kwargs['description'] = parsed_args.description
else:
raise exceptions.CommandError(
"Adding description to share type "
"is only available with API microversion >= 2.41")
if parsed_args.public:
kwargs['is_public'] = strutils.bool_from_string(
parsed_args.public, default=True)
extra_specs = {}
if parsed_args.extra_specs:
for item in parsed_args.extra_specs:
(key, value) = item.split('=', 1)
if key == 'driver_handles_share_servers':
msg = ("'driver_handles_share_servers' "
"is already set via positional argument.")
raise exceptions.CommandError(msg)
else:
extra_specs = utils.extract_extra_specs(
extra_specs, [item])
for key in constants.BOOL_SPECS:
value = getattr(parsed_args, key)
if value:
extra_specs = utils.extract_extra_specs(
extra_specs, [key + '=' + value])
kwargs['extra_specs'] = extra_specs
share_type = share_client.share_types.create(**kwargs)
formatted_type = format_share_type(share_type)
return (ATTRIBUTES, oscutils.get_dict_properties(
formatted_type._info, ATTRIBUTES))
class DeleteShareType(command.Command):
"""Delete a share type."""
_description = _("Delete a share type")
log = logging.getLogger(__name__ + ".DeleteShareType")
def get_parser(self, prog_name):
parser = super(DeleteShareType, self).get_parser(prog_name)
parser.add_argument(
'share_types',
metavar="<share_types>",
nargs="+",
help=_("Name or ID of the share type(s) to delete")
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
result = 0
for share_type in parsed_args.share_types:
try:
share_type_obj = apiutils.find_resource(
share_client.share_types,
share_type)
share_client.share_types.delete(share_type_obj)
except Exception as e:
result += 1
LOG.error(_(
"Failed to delete share type with "
"name or ID '%(share_type)s': %(e)s"),
{'share_type': share_type, 'e': e})
if result > 0:
total = len(parsed_args.share_types)
msg = (_("%(result)s of %(total)s share types failed "
"to delete.") % {'result': result, 'total': total})
raise exceptions.CommandError(msg)
class SetShareType(command.Command):
"""Set share type properties."""
_description = _("Set share type properties")
log = logging.getLogger(__name__ + ".SetShareType")
def get_parser(self, prog_name):
parser = super(SetShareType, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Name or ID of the share type to modify")
)
parser.add_argument(
"--extra-specs",
type=str,
nargs='*',
metavar='<key=value>',
default=None,
help=_("Extra specs key and value of share type that will be"
" used for share type creation. OPTIONAL: Default=None."
" example --extra-specs thin_provisioning='<is> True', "
"replication_type=readable."),
)
parser.add_argument(
'--public',
metavar="<public>",
default=None,
help=_('New visibility of the share type. If set to True, '
'share type will be available to all projects '
'in the cloud. '
'Available only for microversion >= 2.50')
)
parser.add_argument(
"--description",
metavar="<description>",
default=None,
help=_("New description of share type. "
"Available only for microversion >= 2.50"),
)
parser.add_argument(
'--name',
metavar="<name>",
default=None,
help=_('New name of share type. '
'Available only for microversion >= 2.50')
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types, parsed_args.share_type)
can_update = (
share_client.api_version >= api_versions.APIVersion('2.50'))
kwargs = {}
if parsed_args.name is not None:
if can_update:
kwargs['name'] = parsed_args.name
else:
raise exceptions.CommandError(
"Setting (new) name to share type "
"is only available with API microversion >= 2.50")
if parsed_args.description is not None:
if can_update:
kwargs['description'] = parsed_args.description
else:
raise exceptions.CommandError(
"Setting (new) description to share type "
"is only available with API microversion >= 2.50")
if parsed_args.public is not None:
if can_update:
kwargs['is_public'] = strutils.bool_from_string(
parsed_args.public, default=True)
else:
raise exceptions.CommandError(
"Setting visibility to share type "
"is only available with API microversion >= 2.50")
if kwargs:
share_type.update(**kwargs)
if parsed_args.extra_specs:
extra_specs = utils.extract_extra_specs(
extra_specs={},
specs_to_add=parsed_args.extra_specs)
try:
share_type.set_keys(extra_specs)
except Exception as e:
raise exceptions.CommandError(
"Failed to set share type key: %s" % e)
class UnsetShareType(command.Command):
"""Unset share type extra specs."""
_description = _("Unset share type extra specs")
log = logging.getLogger(__name__ + ".UnsetShareType")
def get_parser(self, prog_name):
parser = super(UnsetShareType, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Name or ID of the share type to modify")
)
parser.add_argument(
'extra_specs',
metavar='<key>',
nargs='+',
help=_('Remove extra_specs from this share type'),
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types, parsed_args.share_type)
if parsed_args.extra_specs:
try:
share_type.unset_keys(parsed_args.extra_specs)
except Exception as e:
raise exceptions.CommandError(
"Failed to remove share type extra spec: %s" % e)
class ListShareType(command.Lister):
"""List Share Types."""
_description = _("List share types")
log = logging.getLogger(__name__ + ".ListShareType")
def get_parser(self, prog_name):
parser = super(ListShareType, self).get_parser(prog_name)
parser.add_argument(
'--all',
action='store_true',
default=False,
help=_('Display all share types whatever public or private. '
'Default=False. (Admin only)'),
)
parser.add_argument(
'--extra-specs',
type=str,
nargs='*',
metavar='<key=value>',
default=None,
help=_('Filter share types with extra specs (key=value). '
'Available only for API microversion >= 2.43. '
'OPTIONAL: Default=None.'),
)
parser.add_argument(
'--columns',
metavar='<columns>',
help=_('Comma separated list of columns to be displayed '
'example --columns "id,name".'),
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
search_opts = {}
if parsed_args.extra_specs:
if share_client.api_version < api_versions.APIVersion("2.43"):
raise exceptions.CommandError(
"Filtering by 'extra_specs' is available only with "
"API microversion '2.43' and above.")
search_opts = {
'extra_specs': utils.extract_extra_specs(
extra_specs={},
specs_to_add=parsed_args.extra_specs)
}
share_types = share_client.share_types.list(
search_opts=search_opts,
show_all=parsed_args.all)
if parsed_args.columns:
columns = parsed_args.columns.split(',')
for column in columns:
if column not in ATTRIBUTES:
msg = ("No column named '%s'. "
"Possible columns are: 'id', 'name', 'visibility', "
"is_default', 'required_extra_specs', "
"'optional_extra_specs', 'description'." % column)
raise exceptions.CommandError(msg)
else:
columns = ATTRIBUTES
formatted_types = []
for share_type in share_types:
formatted_types.append(format_share_type(share_type))
values = (oscutils.get_dict_properties(
s._info, columns) for s in formatted_types)
return (columns, values)
class ShowShareType(command.ShowOne):
"""Show a share type."""
_description = _("Display share type details")
def get_parser(self, prog_name):
parser = super(ShowShareType, self).get_parser(prog_name)
parser.add_argument(
'share_type',
metavar="<share_type>",
help=_("Share type to display (name or ID)")
)
return parser
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share_type = apiutils.find_resource(
share_client.share_types,
parsed_args.share_type)
formatted_type = format_share_type(share_type)
return (ATTRIBUTES, oscutils.get_dict_properties(
formatted_type._info, ATTRIBUTES))

View File

@ -31,6 +31,8 @@ class FakeShareClient(object):
self.management_url = kwargs['endpoint']
self.shares = mock.Mock()
self.share_access_rules = mock.Mock()
self.share_types = mock.Mock()
self.share_type_access = mock.Mock()
self.shares.resource_class = osc_fakes.FakeResource(None, {})
self.share_export_locations = mock.Mock()
self.share_export_locations.resource_class = (
@ -199,7 +201,7 @@ class FakeShareType(object):
"""Fake one or more share types"""
@staticmethod
def create_one_sharetype(attrs=None):
def create_one_sharetype(attrs=None, methods=None):
"""Create a fake share type
:param Dictionary attrs:
@ -209,6 +211,7 @@ class FakeShareType(object):
"""
attrs = attrs or {}
methods = methods or {}
share_type_info = {
"required_extra_specs": {
@ -232,9 +235,49 @@ class FakeShareType(object):
share_type_info.update(attrs)
share_type = osc_fakes.FakeResource(info=copy.deepcopy(
share_type_info),
methods=methods,
loaded=True)
return share_type
@staticmethod
def create_share_types(attrs=None, count=2):
"""Create multiple fake share types.
:param Dictionary attrs:
A dictionary with all attributes
:param Integer count:
The number of share types to be faked
:return:
A list of FakeResource objects
"""
share_types = []
for n in range(0, count):
share_types.append(FakeShareType.create_one_sharetype(attrs))
return share_types
@staticmethod
def get_share_types(share_types=None, count=2):
"""Get an iterable MagicMock object with a list of faked types.
If types list is provided, then initialize the Mock object with the
list. Otherwise create one.
:param List types:
A list of FakeResource objects faking types
:param Integer count:
The number of types to be faked
:return
An iterable Mock object with side_effect set to a list of faked
types
"""
if share_types is None:
share_types = FakeShareType.create_share_types(count)
return mock.Mock(side_effect=share_types)
class FakeShareExportLocation(object):
"""Fake one or more export locations"""

View File

@ -0,0 +1,605 @@
# 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 mock import call
from osc_lib import exceptions
from osc_lib import utils as oscutils
from manilaclient import api_versions
from manilaclient.common.apiclient.exceptions import BadRequest
from manilaclient.common.apiclient.exceptions import NotFound
from manilaclient.osc.v2 import share_types as osc_share_types
from manilaclient.tests.unit.osc import osc_utils
from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes
COLUMNS = [
'id',
'name',
'visibility',
'is_default',
'required_extra_specs',
'optional_extra_specs',
'description'
]
class TestShareType(manila_fakes.TestShare):
def setUp(self):
super(TestShareType, self).setUp()
self.shares_mock = self.app.client_manager.share.share_types
self.shares_mock.reset_mock()
self.app.client_manager.share.api_version = api_versions.APIVersion(
"2.51")
class TestShareTypeCreate(TestShareType):
def setUp(self):
super(TestShareTypeCreate, self).setUp()
self.new_share_type = manila_fakes.FakeShareType.create_one_sharetype()
self.shares_mock.create.return_value = self.new_share_type
# Get the command object to test
self.cmd = osc_share_types.CreateShareType(self.app, None)
self.data = [
self.new_share_type.id,
self.new_share_type.name,
'public',
self.new_share_type.is_default,
'driver_handles_share_servers : True',
('replication_type : readable\n'
'mount_snapshot_support : False\n'
'revert_to_snapshot_support : False\n'
'create_share_from_snapshot_support : True\n'
'snapshot_support : True'),
self.new_share_type.description,
]
def test_share_type_create_required_args(self):
"""Verifies required arguments."""
arglist = [
self.new_share_type.name,
'True'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.create.assert_called_with(
extra_specs={},
is_public=True,
name=self.new_share_type.name,
spec_driver_handles_share_servers=True
)
self.assertCountEqual(COLUMNS, columns)
self.assertCountEqual(self.data, data)
def test_share_type_create_missing_required_arg(self):
"""Verifies missing required arguments."""
arglist = [
self.new_share_type.name
]
verifylist = [
('name', self.new_share_type.name)
]
self.assertRaises(osc_utils.ParserException,
self.check_parser, self.cmd, arglist, verifylist)
def test_share_type_create_private(self):
arglist = [
self.new_share_type.name,
'True',
'--public', 'False'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True'),
('public', 'False')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.create.assert_called_with(
extra_specs={},
is_public=False,
name=self.new_share_type.name,
spec_driver_handles_share_servers=True
)
self.assertCountEqual(COLUMNS, columns)
self.assertCountEqual(self.data, data)
def test_share_type_create_extra_specs(self):
arglist = [
self.new_share_type.name,
'True',
'--extra-specs', 'snapshot_support=true'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True'),
('extra_specs', ['snapshot_support=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.create.assert_called_with(
extra_specs={'snapshot_support': 'True'},
is_public=True,
name=self.new_share_type.name,
spec_driver_handles_share_servers=True
)
self.assertCountEqual(COLUMNS, columns)
self.assertCountEqual(self.data, data)
def test_share_type_create_dhss_invalid_value(self):
arglist = [
self.new_share_type.name,
'non_bool_value'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'non_bool_value')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_share_type_create_api_version_exception(self):
self.app.client_manager.share.api_version = api_versions.APIVersion(
"2.40")
arglist = [
self.new_share_type.name,
'True',
'--description', 'Description'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True'),
('description', 'Description')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_share_type_create_dhss_defined_twice(self):
arglist = [
self.new_share_type.name,
'True',
'--extra-specs', 'driver_handles_share_servers=true'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True'),
('extra_specs', ['driver_handles_share_servers=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_share_type_create_bool_args(self):
arglist = [
self.new_share_type.name,
'True',
'--snapshot-support', 'true'
]
verifylist = [
('name', self.new_share_type.name),
('spec_driver_handles_share_servers', 'True'),
('snapshot_support', 'true')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.create.assert_called_with(
extra_specs={'snapshot_support': 'True'},
is_public=True,
name=self.new_share_type.name,
spec_driver_handles_share_servers=True
)
self.assertCountEqual(COLUMNS, columns)
self.assertCountEqual(self.data, data)
class TestShareTypeDelete(TestShareType):
share_types = manila_fakes.FakeShareType.create_share_types(count=2)
def setUp(self):
super(TestShareTypeDelete, self).setUp()
self.shares_mock.get = manila_fakes.FakeShareType.get_share_types(
self.share_types)
self.shares_mock.delete.return_value = None
# Get the command object to test
self.cmd = osc_share_types.DeleteShareType(self.app, None)
def test_share_type_delete_one(self):
arglist = [
self.share_types[0].id
]
verifylist = [
('share_types', [self.share_types[0].id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.shares_mock.delete.assert_called_with(self.share_types[0])
self.assertIsNone(result)
def test_share_type_delete_multiple(self):
arglist = []
for t in self.share_types:
arglist.append(t.id)
verifylist = [
('share_types', arglist),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
calls = []
for t in self.share_types:
calls.append(call(t))
self.shares_mock.delete.assert_has_calls(calls)
self.assertIsNone(result)
def test_delete_share_type_with_exception(self):
arglist = [
'non_existing_type',
]
verifylist = [
('share_types', arglist),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.shares_mock.delete.side_effect = exceptions.CommandError()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeSet(TestShareType):
def setUp(self):
super(TestShareTypeSet, self).setUp()
self.share_type = manila_fakes.FakeShareType.create_one_sharetype(
methods={'set_keys': None, 'update': None})
self.shares_mock.get.return_value = self.share_type
# Get the command object to test
self.cmd = osc_share_types.SetShareType(self.app, None)
def test_share_type_set_extra_specs(self):
arglist = [
self.share_type.id,
'--extra-specs', 'snapshot_support=true'
]
verifylist = [
('share_type', self.share_type.id),
('extra_specs', ['snapshot_support=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.share_type.set_keys.assert_called_with(
{'snapshot_support': 'True'})
self.assertIsNone(result)
def test_share_type_set_name(self):
arglist = [
self.share_type.id,
'--name', 'new name'
]
verifylist = [
('share_type', self.share_type.id),
('name', 'new name')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.share_type.update.assert_called_with(
name='new name')
self.assertIsNone(result)
def test_share_type_set_description(self):
arglist = [
self.share_type.id,
'--description', 'new description'
]
verifylist = [
('share_type', self.share_type.id),
('description', 'new description')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.share_type.update.assert_called_with(
description='new description')
self.assertIsNone(result)
def test_share_type_set_visibility(self):
arglist = [
self.share_type.id,
'--public', 'false'
]
verifylist = [
('share_type', self.share_type.id),
('public', 'false')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.share_type.update.assert_called_with(
is_public=False)
self.assertIsNone(result)
def test_share_type_set_extra_specs_exception(self):
arglist = [
self.share_type.id,
'--extra-specs', 'snapshot_support=true'
]
verifylist = [
('share_type', self.share_type.id),
('extra_specs', ['snapshot_support=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.share_type.set_keys.side_effect = BadRequest()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_share_type_set_api_version_exception(self):
self.app.client_manager.share.api_version = api_versions.APIVersion(
"2.49")
arglist = [
self.share_type.id,
'--name', 'new name',
]
verifylist = [
('share_type', self.share_type.id),
('name', 'new name'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeUnset(TestShareType):
def setUp(self):
super(TestShareTypeUnset, self).setUp()
self.share_type = manila_fakes.FakeShareType.create_one_sharetype(
methods={'unset_keys': None})
self.shares_mock.get.return_value = self.share_type
# Get the command object to test
self.cmd = osc_share_types.UnsetShareType(self.app, None)
def test_share_type_unset_extra_specs(self):
arglist = [
self.share_type.id,
'snapshot_support'
]
verifylist = [
('share_type', self.share_type.id),
('extra_specs', ['snapshot_support'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.share_type.unset_keys.assert_called_with(['snapshot_support'])
self.assertIsNone(result)
def test_share_type_unset_exception(self):
arglist = [
self.share_type.id,
'snapshot_support'
]
verifylist = [
('share_type', self.share_type.id),
('extra_specs', ['snapshot_support'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.share_type.unset_keys.side_effect = NotFound()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeList(TestShareType):
share_types = manila_fakes.FakeShareType.create_share_types()
def setUp(self):
super(TestShareTypeList, self).setUp()
self.shares_mock.list.return_value = self.share_types
# Get the command object to test
self.cmd = osc_share_types.ListShareType(self.app, None)
self.values = (oscutils.get_dict_properties(
s._info, COLUMNS) for s in self.share_types)
def test_share_type_list_no_options(self):
arglist = []
verifylist = [
('all', False)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.list.assert_called_once_with(
search_opts={},
show_all=False
)
self.assertEqual(COLUMNS, columns)
self.assertEqual(list(self.values), list(data))
def test_share_type_list_all(self):
arglist = [
'--all',
]
verifylist = [
('all', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.list.assert_called_once_with(
search_opts={},
show_all=True)
self.assertEqual(COLUMNS, columns)
self.assertEqual(list(self.values), list(data))
def test_share_type_list_extra_specs(self):
arglist = [
'--extra-specs', 'snapshot_support=true'
]
verifylist = [
('extra_specs', ['snapshot_support=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.list.assert_called_once_with(
search_opts={'extra_specs': {'snapshot_support': 'True'}},
show_all=False)
self.assertEqual(COLUMNS, columns)
self.assertEqual(list(self.values), list(data))
def test_share_type_list_columns(self):
arglist = [
'--columns', 'id,name'
]
verifylist = [
('columns', 'id,name')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
arg_columns = [
'id',
'name'
]
arg_columns_data = (oscutils.get_item_properties(
s, arg_columns) for s in self.share_types)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.list.assert_called_once_with(
search_opts={},
show_all=False)
self.assertEqual(arg_columns, columns)
self.assertEqual(list(arg_columns_data), list(data))
def test_share_type_list_api_versions_exception(self):
self.app.client_manager.share.api_version = api_versions.APIVersion(
"2.42")
arglist = [
'--extra-specs', 'snapshot_support=true'
]
verifylist = [
('extra_specs', ['snapshot_support=true'])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
def test_share_type_list_columns_invalid_value(self):
arglist = [
'--columns', 'invalid_column_name'
]
verifylist = [
('columns', 'invalid_column_name')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeShow(TestShareType):
def setUp(self):
super(TestShareTypeShow, self).setUp()
self.share_type = manila_fakes.FakeShareType.create_one_sharetype()
self.shares_mock.get.return_value = self.share_type
# Get the command object to test
self.cmd = osc_share_types.ShowShareType(self.app, None)
self.data = [
self.share_type.id,
self.share_type.name,
'public',
self.share_type.is_default,
'driver_handles_share_servers : True',
('replication_type : readable\n'
'mount_snapshot_support : False\n'
'revert_to_snapshot_support : False\n'
'create_share_from_snapshot_support : True\n'
'snapshot_support : True'),
self.share_type.description,
]
def test_share_type_show(self):
arglist = [
self.share_type.id
]
verifylist = [
("share_type", self.share_type.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.get.assert_called_with(self.share_type.id)
self.assertCountEqual(COLUMNS, columns)
self.assertCountEqual(self.data, data)

View File

@ -0,0 +1,194 @@
# 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 osc_lib import exceptions
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
from manilaclient.common.apiclient.exceptions import BadRequest
from manilaclient.osc.v2 import share_type_access as osc_share_type_access
from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes
class TestShareTypeAccess(manila_fakes.TestShare):
def setUp(self):
super(TestShareTypeAccess, self).setUp()
self.type_access_mock = (
self.app.client_manager.share.share_type_access)
self.type_access_mock.reset_mock()
self.share_types_mock = self.app.client_manager.share.share_types
self.share_types_mock.reset_mock()
self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
class TestShareTypeAccessAllow(TestShareTypeAccess):
def setUp(self):
super(TestShareTypeAccessAllow, self).setUp()
self.project = identity_fakes.FakeProject.create_one_project()
self.share_type = manila_fakes.FakeShareType.create_one_sharetype(
attrs={'share_type_access:is_public': False})
self.share_types_mock.get.return_value = self.share_type
self.type_access_mock.add_project_access.return_value = None
# Get the command object to test
self.cmd = osc_share_type_access.ShareTypeAccessAllow(self.app, None)
def test_share_type_access_create(self):
arglist = [
self.share_type.id,
self.project.id
]
verifylist = [
('share_type', self.share_type.id),
('project_id', self.project.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.type_access_mock.add_project_access.assert_called_with(
self.share_type,
self.project.id)
self.assertIsNone(result)
def test_share_type_access_create_throws_exception(self):
arglist = [
self.share_type.id,
'invalid_project_format'
]
verifylist = [
('share_type', self.share_type.id),
('project_id', 'invalid_project_format')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.type_access_mock.add_project_access.side_effect = BadRequest()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeAccessList(TestShareTypeAccess):
columns = ['Project_ID']
data = (('',), ('',))
def setUp(self):
super(TestShareTypeAccessList, self).setUp()
self.type_access_mock.list.return_value = (
self.columns, self.data)
# Get the command object to test
self.cmd = osc_share_type_access.ListShareTypeAccess(self.app, None)
def test_share_type_access_list(self):
share_type = manila_fakes.FakeShareType.create_one_sharetype(
attrs={'share_type_access:is_public': False})
self.share_types_mock.get.return_value = share_type
arglist = [
share_type.id,
]
verifylist = [
('share_type', share_type.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.type_access_mock.list.assert_called_once_with(
share_type)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, tuple(data))
def test_share_type_access_list_public_type(self):
share_type = manila_fakes.FakeShareType.create_one_sharetype(
attrs={'share_type_access:is_public': True})
self.share_types_mock.get.return_value = share_type
arglist = [
share_type.id,
]
verifylist = [
('share_type', share_type.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareTypeAccessDeny(TestShareTypeAccess):
def setUp(self):
super(TestShareTypeAccessDeny, self).setUp()
self.project = identity_fakes.FakeProject.create_one_project()
self.share_type = manila_fakes.FakeShareType.create_one_sharetype(
attrs={'share_type_access:is_public': False})
self.share_types_mock.get.return_value = self.share_type
self.type_access_mock.remove_project_access.return_value = None
# Get the command object to test
self.cmd = osc_share_type_access.ShareTypeAccessDeny(self.app, None)
def test_share_type_access_delete(self):
arglist = [
self.share_type.id,
self.project.id
]
verifylist = [
('share_type', self.share_type.id),
('project_id', self.project.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.type_access_mock.remove_project_access.assert_called_with(
self.share_type,
self.project.id)
self.assertIsNone(result)
def test_share_type_access_delete_exception(self):
arglist = [
self.share_type.id,
'invalid_project_format'
]
verifylist = [
('share_type', self.share_type.id),
('project_id', 'invalid_project_format')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.type_access_mock.remove_project_access.side_effect = BadRequest()
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)

View File

@ -50,6 +50,15 @@ openstack.share.v2 =
share_access_show = manilaclient.osc.v2.share_access_rules:ShowShareAccess
share_access_set = manilaclient.osc.v2.share_access_rules:SetShareAccess
share_access_unset = manilaclient.osc.v2.share_access_rules:UnsetShareAccess
share_type_create = manilaclient.osc.v2.share_types:CreateShareType
share_type_delete = manilaclient.osc.v2.share_types:DeleteShareType
share_type_set = manilaclient.osc.v2.share_types:SetShareType
share_type_unset = manilaclient.osc.v2.share_types:UnsetShareType
share_type_list = manilaclient.osc.v2.share_types:ListShareType
share_type_show = manilaclient.osc.v2.share_types:ShowShareType
share_type_access_create = manilaclient.osc.v2.share_type_access:ShareTypeAccessAllow
share_type_access_list = manilaclient.osc.v2.share_type_access:ListShareTypeAccess
share_type_access_delete = manilaclient.osc.v2.share_type_access:ShareTypeAccessDeny
[wheel]
universal = 1