Support metadata for access rule resource

Now only share have metadata property.
We should support it for access rule as well.

Depends-On: https://review.openstack.org/#/c/570708/
Change-Id: I41b2882ecd16ed03bece05a7a77a387c9077bf5c
Partly-Implements: bp metadata-for-access-rule
This commit is contained in:
zhongjun2 2018-05-31 11:03:54 +08:00
parent b0b08cc650
commit 1aa12eb2f8
11 changed files with 442 additions and 36 deletions

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__)
MAX_VERSION = '2.43'
MAX_VERSION = '2.45'
MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {}

View File

@ -982,7 +982,7 @@ class ManilaCLIClient(base.CLIClient):
@not_found_wrapper
def list_access(self, entity_id, columns=None, microversion=None,
is_snapshot=False):
is_snapshot=False, metadata=None):
"""Returns list of access rules for a share.
:param entity_id: str -- Name or ID of a share or snapshot.
@ -997,6 +997,12 @@ class ManilaCLIClient(base.CLIClient):
cmd = 'access-list %s ' % entity_id
if columns is not None:
cmd += ' --columns ' + columns
if metadata:
metadata_cli = ''
for k, v in metadata.items():
metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
if metadata_cli:
cmd += ' --metadata %s ' % metadata_cli
access_list_raw = self.manila(cmd, microversion=microversion)
return output_parser.listing(access_list_raw)
@ -1009,6 +1015,33 @@ class ManilaCLIClient(base.CLIClient):
return access
raise tempest_lib_exc.NotFound()
@not_found_wrapper
def access_show(self, access_id, microversion=None):
raw_access = self.manila("access-show %s" % access_id,
microversion=microversion)
return output_parser.details(raw_access)
@not_found_wrapper
def access_set_metadata(self, access_id, metadata, microversion=None):
if not (isinstance(metadata, dict) and metadata):
msg = ('Provided invalid metadata for setting of access rule'
' metadata - %s' % metadata)
raise exceptions.InvalidData(message=msg)
cmd = "access-metadata %s set " % access_id
for k, v in metadata.items():
cmd += '%(k)s=%(v)s ' % {'k': k, 'v': v}
return self.manila(cmd, microversion=microversion)
@not_found_wrapper
def access_unset_metadata(self, access_id, keys, microversion=None):
if not (isinstance(keys, (list, tuple, set)) and keys):
raise exceptions.InvalidData(
message='Provided invalid keys - %s' % keys)
cmd = 'access-metadata %s unset ' % access_id
for key in keys:
cmd += '%s ' % key
return self.manila(cmd, microversion=microversion)
@not_found_wrapper
def snapshot_access_allow(self, snapshot_id, access_type, access_to,
microversion=None):
@ -1032,16 +1065,20 @@ class ManilaCLIClient(base.CLIClient):
@not_found_wrapper
def access_allow(self, share_id, access_type, access_to, access_level,
microversion=None):
raw_access = self.manila(
'access-allow --access-level %(level)s %(id)s %(type)s '
'%(access_to)s' % {
'level': access_level,
'id': share_id,
'type': access_type,
'access_to': access_to,
},
microversion=microversion)
metadata=None, microversion=None):
cmd = ('access-allow --access-level %(level)s %(id)s %(type)s '
'%(access_to)s' % {
'level': access_level,
'id': share_id,
'type': access_type,
'access_to': access_to})
if metadata:
metadata_cli = ''
for k, v in metadata.items():
metadata_cli += '%(k)s=%(v)s ' % {'k': k, 'v': v}
if metadata_cli:
cmd += ' --metadata %s ' % metadata_cli
raw_access = self.manila(cmd, microversion=microversion)
return output_parser.details(raw_access)
@not_found_wrapper

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import ast
import ddt
from tempest.lib import exceptions as tempest_lib_exc
@ -68,16 +70,19 @@ class ShareAccessReadWriteBase(base.BaseTestCase):
'ipv6': ['2001:db8::%d' % i for i in int_range],
}
def _test_create_list_access_rule_for_share(self, microversion):
def _test_create_list_access_rule_for_share(
self, microversion, metadata=None):
access_type = self.access_types[0]
access = self.user_client.access_allow(
self.share['id'], access_type, self.access_to[access_type].pop(),
self.access_level, microversion=microversion)
self.access_level, metadata=metadata, microversion=microversion)
return access
@ddt.data("1.0", "2.0", "2.6", "2.7", "2.21", "2.33")
@ddt.data(*set([
"1.0", "2.0", "2.6", "2.7", "2.21", "2.33", "2.44", "2.45",
api_versions.MAX_VERSION]))
def test_create_list_access_rule_for_share(self, microversion):
self.skip_if_microversion_not_supported(microversion)
access = self._test_create_list_access_rule_for_share(
@ -95,8 +100,9 @@ class ShareAccessReadWriteBase(base.BaseTestCase):
if (api_versions.APIVersion(microversion) >=
api_versions.APIVersion("2.33")):
self.assertTrue(
all(('access_key' and 'created_at' and 'updated_at')
in a for a in access_list))
all(all(key in access for key in (
'access_key', 'created_at', 'updated_at'))
for access in access_list))
elif (api_versions.APIVersion(microversion) >=
api_versions.APIVersion("2.21")):
self.assertTrue(all('access_key' in a for a in access_list))
@ -156,6 +162,89 @@ class ShareAccessReadWriteBase(base.BaseTestCase):
self.assertRaises(tempest_lib_exc.NotFound,
self.user_client.get_access, share_id, access['id'])
@ddt.data(*set(["2.45", api_versions.MAX_VERSION]))
def test_create_list_access_rule_with_metadata(self, microversion):
self.skip_if_microversion_not_supported(microversion)
md1 = {"key1": "value1", "key2": "value2"}
md2 = {"key3": "value3", "key4": "value4"}
self._test_create_list_access_rule_for_share(
metadata=md1, microversion=microversion)
access = self._test_create_list_access_rule_for_share(
metadata=md2, microversion=microversion)
access_list = self.user_client.list_access(
self.share['id'], metadata={"key4": "value4"},
microversion=microversion)
self.assertEqual(1, len(access_list))
# Verify share metadata
get_access = self.user_client.access_show(
access_list[0]['id'], microversion=microversion)
metadata = ast.literal_eval(get_access['metadata'])
self.assertEqual(2, len(metadata))
self.assertIn('key3', metadata)
self.assertIn('key4', metadata)
self.assertEqual(md2['key3'], metadata['key3'])
self.assertEqual(md2['key4'], metadata['key4'])
self.assertEqual(access['id'], access_list[0]['id'])
self.user_client.access_deny(access['share_id'], access['id'])
self.user_client.wait_for_access_rule_deletion(access['share_id'],
access['id'])
@ddt.data(*set(["2.45", api_versions.MAX_VERSION]))
def test_create_update_show_access_rule_with_metadata(self, microversion):
self.skip_if_microversion_not_supported(microversion)
md1 = {"key1": "value1", "key2": "value2"}
md2 = {"key3": "value3", "key2": "value4"}
# create a access rule with metadata
access = self._test_create_list_access_rule_for_share(
metadata=md1, microversion=microversion)
# get the access rule
get_access = self.user_client.access_show(
access['id'], microversion=microversion)
# verify access rule
self.assertEqual(access['id'], get_access['id'])
self.assertEqual(md1, ast.literal_eval(get_access['metadata']))
# update access rule metadata
self.user_client.access_set_metadata(
access['id'], metadata=md2, microversion=microversion)
get_access = self.user_client.access_show(
access['id'], microversion=microversion)
# verify access rule after update access rule metadata
self.assertEqual(
{"key1": "value1", "key2": "value4", "key3": "value3"},
ast.literal_eval(get_access['metadata']))
self.assertEqual(access['id'], get_access['id'])
@ddt.data(*set(["2.45", api_versions.MAX_VERSION]))
def test_delete_access_rule_metadata(self, microversion):
self.skip_if_microversion_not_supported(microversion)
md = {"key1": "value1", "key2": "value2"}
# create a access rule with metadata
access = self._test_create_list_access_rule_for_share(
metadata=md, microversion=microversion)
# get the access rule
get_access = self.user_client.access_show(
access['id'], microversion=microversion)
# verify access rule
self.assertEqual(access['id'], get_access['id'])
self.assertEqual(md, ast.literal_eval(get_access['metadata']))
# delete access rule metadata
self.user_client.access_unset_metadata(
access['id'], keys=["key1", "key2"], microversion=microversion)
get_access = self.user_client.access_show(
access['id'], microversion=microversion)
# verify access rule after delete access rule metadata
self.assertEqual({}, ast.literal_eval(get_access['metadata']))
self.assertEqual(access['id'], get_access['id'])
@ddt.data("1.0", "2.0", "2.6", "2.7", "2.21", "2.33")
def test_create_delete_ip_access_rule(self, microversion):
self._create_delete_access_rule(

View File

@ -505,6 +505,45 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
raise AssertionError("Unexpected share action: %s" % action)
return (resp, {}, _body)
def get_share_access_rules(self, **kw):
access = {
'access_list': [{
'access_level': 'rw',
'state': 'active',
'id': '1122',
'access_type': 'ip',
'access_to': '10.0.0.7',
'metadata': {'key1': 'v1'}
}]
}
return (200, {}, access)
def get_share_access_rules_9999(self, **kw):
access = {
'access': {
'access_level': 'rw',
'state': 'active',
'id': '9999',
'access_type': 'ip',
'access_to': '10.0.0.7',
'metadata': {'key1': 'v1'}
}
}
return (200, {}, access)
def put_share_access_rules_9999_metadata(self, **kw):
return (200, {}, {'metadata': {'key1': 'v1', 'key2': 'v2'}})
def delete_share_access_rules_9999_metadata_key1(self, **kw):
return (200, {}, None)
def get_shares_2222(self, **kw):
share = {'share': {'id': 2222, 'name': 'sharename'}}
return (200, {}, share)
def post_shares_2222_action(self, body, **kw):
return (202, {}, {'access': {}})
def post_share_networks(self, **kwargs):
return (202, {}, {'share_network': {}})

View File

@ -49,7 +49,7 @@ class SharesTest(utils.TestCase):
self.share.allow(access_type, access_to, access_level)
self.share.manager.allow.assert_called_once_with(
self.share, access_type, access_to, access_level)
self.share, access_type, access_to, access_level, None)
# Testcases for class ShareManager

View File

@ -23,6 +23,7 @@ from oslo_utils import strutils
import six
from six.moves.urllib import parse
from manilaclient import api_versions
from manilaclient import client
from manilaclient.common.apiclient import utils as apiclient_utils
from manilaclient.common import cliutils
@ -1705,17 +1706,63 @@ class ShellTest(test_utils.TestCase):
self.assertRaises(SystemExit, self.run_command,
"access-allow --access-level fake 1111 ip 10.0.0.6")
def test_allow_access_with_metadata(self):
expected = {
"allow_access": {
"access_type": "ip",
"access_to": "10.0.0.6",
"metadata": {"key1": "v1", "key2": "v2"},
}
}
self.run_command(
"access-allow 2222 ip 10.0.0.6 --metadata key1=v1 key2=v2",
version="2.45")
self.assert_called("POST", "/shares/2222/action", body=expected)
def test_set_access_metadata(self):
expected = {
"metadata": {
"key1": "v1",
"key2": "v2",
}
}
self.run_command(
"access-metadata 9999 set key1=v1 key2=v2",
version="2.45")
self.assert_called("PUT", "/share-access-rules/9999/metadata",
body=expected)
def test_unset_access_metadata(self):
self.run_command(
"access-metadata 9999 unset key1",
version="2.45")
self.assert_called("DELETE", "/share-access-rules/9999/metadata/key1")
@ddt.data("1.0", "2.0", "2.44")
def test_allow_access_with_metadata_not_support_version(self, version):
self.assertRaises(
exceptions.CommandError,
self.run_command,
"access-allow 2222 ip 10.0.0.6 --metadata key1=v1",
version=version,
)
@mock.patch.object(cliutils, 'print_list', mock.Mock())
def test_access_list(self):
self.run_command("access-list 1111")
@ddt.data(*set(["2.44", "2.45", api_versions.MAX_VERSION]))
def test_access_list(self, version):
self.run_command("access-list 1111", version=version)
version = api_versions.APIVersion(version)
cliutils.print_list.assert_called_with(
mock.ANY,
['id', 'access_type', 'access_to', 'access_level', 'state',
'access_key', 'created_at', 'updated_at'])
@mock.patch.object(cliutils, 'print_list', mock.Mock())
def test_access_list_select_column(self):
self.run_command("access-list 1111 --columns id,access_type")
@ddt.data(*set(["2.44", "2.45", api_versions.MAX_VERSION]))
def test_access_list_select_column(self, version):
self.run_command("access-list 1111 --columns id,access_type",
version=version)
cliutils.print_list.assert_called_with(
mock.ANY,
['Id', 'Access_Type'])

View File

@ -28,6 +28,7 @@ from manilaclient.v2 import quotas
from manilaclient.v2 import scheduler_stats
from manilaclient.v2 import security_services
from manilaclient.v2 import services
from manilaclient.v2 import share_access_rules
from manilaclient.v2 import share_export_locations
from manilaclient.v2 import share_group_snapshots
from manilaclient.v2 import share_group_type_access
@ -237,6 +238,8 @@ class Client(object):
self.share_servers = share_servers.ShareServerManager(self)
self.share_replicas = share_replicas.ShareReplicaManager(self)
self.pools = scheduler_stats.PoolManager(self)
self.share_access_rules = (
share_access_rules.ShareAccessRuleManager(self))
self._load_extensions(extensions)

View File

@ -0,0 +1,99 @@
# Copyright 2018 Huawei Corporation.
# All Rights Reserved.
#
# Copyright 2018
#
# 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 access rules 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
from manilaclient import utils
RESOURCE_PATH = '/share-access-rules/%s'
RESOURCE_NAME = 'access'
RESOURCES_METADATA_PATH = '/share-access-rules/%s/metadata'
RESOURCE_METADATA_PATH = '/share-access-rules/%s/metadata/%s'
RESOURCE_LIST_PATH = '/share-access-rules?share_id=%s'
class ShareAccessRule(common_base.Resource):
"""A Share Access Rule."""
def __repr__(self):
return "<Share Access Rule: %s>" % self.id
def delete(self):
""""Delete this share access rule."""
self.manager.delete(self)
class ShareAccessRuleManager(base.ManagerWithFind):
"""Manage :class:`ShareAccessRule` resources."""
resource_class = ShareAccessRule
@api_versions.wraps("2.45")
def get(self, share_access_rule):
"""Get a specific share access rule.
:param share_access_rule: either instance of ShareAccessRule, or text
with UUID
:rtype: :class:`ShareAccessRule`
"""
share_access_rule_id = common_base.getid(share_access_rule)
url = RESOURCE_PATH % share_access_rule_id
return self._get(url, RESOURCE_NAME)
@api_versions.wraps("2.45")
def set_metadata(self, access, metadata):
"""Set or update metadata for share access rule.
:param share_access_rule: either share access rule object or
text with its ID.
:param metadata: A list of keys to be set.
"""
body = {'metadata': metadata}
access_id = common_base.getid(access)
url = RESOURCES_METADATA_PATH % access_id
return self._update(url, body, "metadata")
@api_versions.wraps("2.45")
def unset_metadata(self, access, keys):
"""Unset metadata on a share access rule.
:param keys: A list of keys on this object to be unset
:return: None if successful, else API response on failure
"""
for k in keys:
url = RESOURCE_METADATA_PATH % (common_base.getid(access), k)
self._delete(url)
@api_versions.wraps("2.45")
def access_list(self, share, search_opts):
query_string = ""
if search_opts:
search_opts = utils.unicode_key_value_to_string(search_opts)
params = sorted(
[(k, v) for (k, v) in list(search_opts.items()) if v])
if params:
query_string = "&%s" % parse.urlencode(params)
url = RESOURCE_LIST_PATH % common_base.getid(share) + query_string
return self._list(url, 'access_list')

View File

@ -78,9 +78,10 @@ class Share(common_base.Resource):
"""Delete the specified share ignoring its current state."""
self.manager.force_delete(self)
def allow(self, access_type, access, access_level):
def allow(self, access_type, access, access_level, metadata=None):
"""Allow access to a share."""
return self.manager.allow(self, access_type, access, access_level)
return self.manager.allow(
self, access_type, access, access_level, metadata)
def deny(self, id):
"""Deny access from IP to a share."""
@ -505,13 +506,15 @@ class ShareManager(base.ManagerWithFind):
', '.join(valid_access_types))
raise exceptions.CommandError(msg)
def _do_allow(self, share, access_type, access, access_level, action_name):
def _do_allow(self, share, access_type, access, access_level, action_name,
metadata=None):
"""Allow access to a share.
:param share: either share object or text with its ID.
:param access_type: string that represents access type ('ip','domain')
:param access: string that represents access ('127.0.0.1')
:param access_level: string that represents access level ('rw', 'ro')
:param metadata: A dict of key/value pairs to be set
"""
access_params = {
'access_type': access_type,
@ -519,37 +522,47 @@ class ShareManager(base.ManagerWithFind):
}
if access_level:
access_params['access_level'] = access_level
if metadata:
access_params['metadata'] = metadata
access = self._action(action_name, share,
access_params)[1]["access"]
return access
@api_versions.wraps("1.0", "2.6")
def allow(self, share, access_type, access, access_level):
def allow(self, share, access_type, access, access_level, metadata=None):
self._validate_access(access_type, access)
return self._do_allow(
share, access_type, access, access_level, "os-allow_access")
@api_versions.wraps("2.7", "2.12") # noqa
def allow(self, share, access_type, access, access_level):
def allow(self, share, access_type, access, access_level, metadata=None):
self._validate_access(access_type, access)
return self._do_allow(
share, access_type, access, access_level, "allow_access")
@api_versions.wraps("2.13", "2.37") # noqa
def allow(self, share, access_type, access, access_level):
def allow(self, share, access_type, access, access_level, metadata=None):
valid_access_types = ('ip', 'user', 'cert', 'cephx')
self._validate_access(access_type, access, valid_access_types)
return self._do_allow(
share, access_type, access, access_level, "allow_access")
@api_versions.wraps("2.38") # noqa
def allow(self, share, access_type, access, access_level):
@api_versions.wraps("2.38", "2.44") # noqa
def allow(self, share, access_type, access, access_level, metadata=None):
valid_access_types = ('ip', 'user', 'cert', 'cephx')
self._validate_access(access_type, access, valid_access_types,
enable_ipv6=True)
return self._do_allow(
share, access_type, access, access_level, "allow_access")
@api_versions.wraps("2.45") # noqa
def allow(self, share, access_type, access, access_level, metadata=None):
valid_access_types = ('ip', 'user', 'cert', 'cephx')
self._validate_access(access_type, access, valid_access_types,
enable_ipv6=True)
return self._do_allow(
share, access_type, access, access_level, "allow_access", metadata)
def _do_deny(self, share, access_id, action_name):
"""Deny access to a share.
@ -582,7 +595,7 @@ class ShareManager(base.ManagerWithFind):
def access_list(self, share):
return self._do_access_list(share, "os-access_list")
@api_versions.wraps("2.7") # noqa
@api_versions.wraps("2.7", "2.44") # noqa
def access_list(self, share):
return self._do_access_list(share, "access_list")

View File

@ -1302,13 +1302,72 @@ def do_show(cs, args):
action='single_alias',
help='Share access level ("rw" and "ro" access levels are supported). '
'Defaults to rw.')
@cliutils.arg(
'--metadata',
type=str,
nargs='*',
metavar='<key=value>',
help='Space Separated list of key=value pairs of metadata items. '
'OPTIONAL: Default=None. Available only for microversion >= 2.45.',
default=None)
def do_access_allow(cs, args):
"""Allow access to the share."""
"""Allow access to a given share."""
access_metadata = None
if cs.api_version.matches(api_versions.APIVersion("2.45"),
api_versions.APIVersion()):
access_metadata = _extract_metadata(args)
elif getattr(args, 'metadata'):
raise exceptions.CommandError(
"Adding metadata to access rules is supported only beyond "
"API version 2.45")
share = _find_share(cs, args.share)
access = share.allow(args.access_type, args.access_to, args.access_level)
access = share.allow(args.access_type, args.access_to, args.access_level,
access_metadata)
cliutils.print_dict(access)
@api_versions.wraps("2.45")
@cliutils.arg(
'access_id',
metavar='<access_id>',
help='ID of the NAS share access rule.')
def do_access_show(cs, args):
"""Show details about a NAS share access rule."""
access = cs.share_access_rules.get(args.access_id)
view_data = access._info.copy()
cliutils.print_dict(view_data)
@api_versions.wraps("2.45")
@cliutils.arg(
'access_id',
metavar='<access_id>',
help='ID of the NAS share access rule.')
@cliutils.arg(
'action',
metavar='<action>',
choices=['set', 'unset'],
help="Actions: 'set' or 'unset'.")
@cliutils.arg(
'metadata',
metavar='<key=value>',
nargs='+',
default=[],
help='Space separated key=value pairs of metadata items to set. '
'To unset only keys are required. ')
def do_access_metadata(cs, args):
"""Set or delete metadata on a share access rule."""
share_access = cs.share_access_rules.get(args.access_id)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.share_access_rules.set_metadata(share_access, metadata)
elif args.action == 'unset':
cs.share_access_rules.unset_metadata(
share_access, sorted(list(metadata), reverse=True))
@api_versions.wraps("2.32")
@cliutils.arg(
'snapshot',
@ -1437,6 +1496,14 @@ def do_access_list(cs, args):
default=None,
help='Comma separated list of columns to be displayed '
'example --columns "access_type,access_to".')
@cliutils.arg(
'--metadata',
type=str,
nargs='*',
metavar='<key=value>',
help='Filters results by a metadata key and value. OPTIONAL: '
'Default=None. Available only for microversion >= 2.45',
default=None)
def do_access_list(cs, args):
"""Show access list for share."""
list_of_keys = [
@ -1444,11 +1511,18 @@ def do_access_list(cs, args):
'access_key', 'created_at', 'updated_at',
]
share = _find_share(cs, args.share)
if cs.api_version < api_versions.APIVersion("2.45"):
if getattr(args, 'metadata'):
raise exceptions.CommandError(
"Filtering access rules by metadata is supported only beyond "
"API version 2.45")
access_list = share.access_list()
else:
access_list = cs.share_access_rules.access_list(
share, {'metadata': _extract_metadata(args)})
if args.columns is not None:
list_of_keys = _split_columns(columns=args.columns)
share = _find_share(cs, args.share)
access_list = share.access_list()
cliutils.print_list(access_list, list_of_keys)

View File

@ -0,0 +1,5 @@
---
features:
- Added support to create share access rule metadata, update existing share
access rule metadata and delete share access rule metadata. Also added a
new shell command to show details of a share access rule.