Volume targets/connectors Project Scoped RBAC
This patch adds project scoped access, as part of the work to delineate system and project scope access. Adds policies: * baremetal:volume:list_all * baremetal:volume:list * baremetal:volume:view_target_properties Change-Id: I898310b515195b7065a3b1c7998ef3f29f5e8747
This commit is contained in:
parent
e9dfe5ddaa
commit
e870bd34d0
@ -66,8 +66,12 @@ Supported Endpoints
|
||||
* /nodes
|
||||
* /nodes/<uuid>/ports
|
||||
* /nodes/<uuid>/portgroups
|
||||
* /nodes/<uuid>/volume/connectors
|
||||
* /nodes/<uuid>/volume/targets
|
||||
* /ports
|
||||
* /portgroups
|
||||
* /volume/connectors
|
||||
* /volume/targets
|
||||
|
||||
How Project Scoped Works
|
||||
------------------------
|
||||
@ -146,7 +150,7 @@ More information is available on these fields in :doc:`/configuration/policy`.
|
||||
Pratical differences
|
||||
--------------------
|
||||
|
||||
Most users, upon implementing the use of ``system`` scoped authenticaiton,
|
||||
Most users, upon implementing the use of ``system`` scoped authentication
|
||||
should not notice a difference as long as their authentication token is
|
||||
properly scoped to ``system`` and with the appropriate role for their
|
||||
access level. For most users who used a ``baremetal`` project,
|
||||
@ -154,7 +158,7 @@ or other custom project via a custom policy file, along with a custom
|
||||
role name such as ``baremetal_admin``, this will require changing
|
||||
the user to be a ``system`` scoped user with ``admin`` privilges.
|
||||
|
||||
The most noticable difference for API consumers is the HTTP 403 access
|
||||
The most noticeable difference for API consumers is the HTTP 403 access
|
||||
code is now mainly a HTTP 404 access code. The access concept has changed
|
||||
from "Does the user user broadly has access to the API?" to
|
||||
"Does user have access to the node, and then do they have access
|
||||
|
@ -1787,6 +1787,110 @@ def check_port_list_policy(portgroup=False, parent_node=None,
|
||||
return owner
|
||||
|
||||
|
||||
def check_volume_list_policy(parent_node=None):
|
||||
"""Check if the specified policy authorizes this request on a port.
|
||||
|
||||
:param parent_node: The UUID of a node, if any, to apply a policy
|
||||
check to as well before applying other policy
|
||||
check operations.
|
||||
|
||||
:raises: HTTPForbidden if the policy forbids access.
|
||||
:return: owner that should be used for list query, if needed
|
||||
"""
|
||||
|
||||
cdict = api.request.context.to_policy_values()
|
||||
|
||||
# No node is associated with this request, yet.
|
||||
rpc_node = None
|
||||
conceal_linked_node = None
|
||||
|
||||
if parent_node:
|
||||
try:
|
||||
rpc_node = objects.Node.get_by_uuid(api.request.context,
|
||||
parent_node)
|
||||
conceal_linked_node = rpc_node.uuid
|
||||
except exception.NotFound:
|
||||
raise exception.NodeNotFound(node=parent_node)
|
||||
if parent_node:
|
||||
try:
|
||||
check_owner_policy(
|
||||
'node', 'baremetal:node:get',
|
||||
rpc_node.owner, rpc_node.lessee,
|
||||
conceal_node=conceal_linked_node)
|
||||
except exception.NotAuthorized:
|
||||
if parent_node:
|
||||
# This should likely never be hit, because
|
||||
# the existence of a parent node should
|
||||
# trigger the node not found exception to be
|
||||
# explicitly raised.
|
||||
raise exception.NodeNotFound(
|
||||
node=parent_node)
|
||||
raise
|
||||
|
||||
try:
|
||||
policy.authorize('baremetal:volume:list_all',
|
||||
cdict, api.request.context)
|
||||
except exception.HTTPForbidden:
|
||||
owner = cdict.get('project_id')
|
||||
if not owner:
|
||||
raise
|
||||
policy.authorize('baremetal:volume:list',
|
||||
cdict, api.request.context)
|
||||
return owner
|
||||
|
||||
|
||||
def check_volume_policy_and_retrieve(policy_name, vol_ident, target=False):
|
||||
"""Check if the specified policy authorizes this request on a port.
|
||||
|
||||
:param: policy_name: Name of the policy to check.
|
||||
:param: vol_ident: The name, uuid, or other valid ID value to find
|
||||
a port or portgroup by.
|
||||
:param: target: Boolean value to indicate if the check is for a volume
|
||||
target or connector. Default value is False, implying
|
||||
connector.
|
||||
|
||||
:raises: HTTPForbidden if the policy forbids access.
|
||||
:raises: VolumeConnectorNotFound if the node is not found.
|
||||
:raises: VolumeTargetNotFound if the node is not found.
|
||||
:return: RPC port identified by port_ident associated node
|
||||
"""
|
||||
context = api.request.context
|
||||
cdict = context.to_policy_values()
|
||||
owner = None
|
||||
lessee = None
|
||||
try:
|
||||
if not target:
|
||||
rpc_vol = objects.VolumeConnector.get(context, vol_ident)
|
||||
else:
|
||||
rpc_vol = objects.VolumeTarget.get(context, vol_ident)
|
||||
except (exception.VolumeConnectorNotFound, exception.VolumeTargetNotFound):
|
||||
# don't expose non-existence of port unless requester
|
||||
# has generic access to policy
|
||||
raise
|
||||
|
||||
target_dict = dict(cdict)
|
||||
try:
|
||||
rpc_node = objects.Node.get_by_id(context, rpc_vol.node_id)
|
||||
owner = rpc_node['owner']
|
||||
lessee = rpc_node['lessee']
|
||||
except exception.NodeNotFound:
|
||||
pass
|
||||
target_dict = dict(cdict)
|
||||
target_dict['node.owner'] = owner
|
||||
target_dict['node.lessee'] = lessee
|
||||
try:
|
||||
policy.authorize('baremetal:node:get', target_dict, context)
|
||||
except exception.NotAuthorized:
|
||||
if not target:
|
||||
raise exception.VolumeConnectorNotFound(connector=vol_ident)
|
||||
else:
|
||||
raise exception.VolumeTargetNotFound(target=vol_ident)
|
||||
|
||||
policy.authorize(policy_name, target_dict, context)
|
||||
|
||||
return rpc_vol, rpc_node
|
||||
|
||||
|
||||
def allow_build_configdrive():
|
||||
"""Check if building configdrive is allowed.
|
||||
|
||||
|
@ -111,7 +111,8 @@ class VolumeConnectorsController(rest.RestController):
|
||||
def _get_volume_connectors_collection(self, node_ident, marker, limit,
|
||||
sort_key, sort_dir,
|
||||
resource_url=None,
|
||||
fields=None, detail=None):
|
||||
fields=None, detail=None,
|
||||
project=None):
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
@ -135,13 +136,15 @@ class VolumeConnectorsController(rest.RestController):
|
||||
node = api_utils.get_rpc_node(node_ident)
|
||||
connectors = objects.VolumeConnector.list_by_node_id(
|
||||
api.request.context, node.id, limit, marker_obj,
|
||||
sort_key=sort_key, sort_dir=sort_dir)
|
||||
sort_key=sort_key, sort_dir=sort_dir,
|
||||
project=project)
|
||||
else:
|
||||
connectors = objects.VolumeConnector.list(api.request.context,
|
||||
limit,
|
||||
marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return list_convert_with_links(connectors, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
@ -156,7 +159,7 @@ class VolumeConnectorsController(rest.RestController):
|
||||
sort_dir=args.string, fields=args.string_list,
|
||||
detail=args.boolean)
|
||||
def get_all(self, node=None, marker=None, limit=None, sort_key='id',
|
||||
sort_dir='asc', fields=None, detail=None):
|
||||
sort_dir='asc', fields=None, detail=None, project=None):
|
||||
"""Retrieve a list of volume connectors.
|
||||
|
||||
:param node: UUID or name of a node, to get only volume connectors
|
||||
@ -179,7 +182,8 @@ class VolumeConnectorsController(rest.RestController):
|
||||
:raises: InvalidParameterValue if sort key is invalid for sorting.
|
||||
:raises: InvalidParameterValue if both fields and detail are specified.
|
||||
"""
|
||||
api_utils.check_policy('baremetal:volume:get')
|
||||
project = api_utils.check_volume_list_policy(
|
||||
parent_node=self.parent_node_ident)
|
||||
|
||||
if fields is None and not detail:
|
||||
fields = _DEFAULT_RETURN_FIELDS
|
||||
@ -191,7 +195,7 @@ class VolumeConnectorsController(rest.RestController):
|
||||
resource_url = 'volume/connectors'
|
||||
return self._get_volume_connectors_collection(
|
||||
node, marker, limit, sort_key, sort_dir, resource_url=resource_url,
|
||||
fields=fields, detail=detail)
|
||||
fields=fields, detail=detail, project=project)
|
||||
|
||||
@METRICS.timer('VolumeConnectorsController.get_one')
|
||||
@method.expose()
|
||||
@ -210,13 +214,15 @@ class VolumeConnectorsController(rest.RestController):
|
||||
:raises: VolumeConnectorNotFound if no volume connector exists with
|
||||
the specified UUID.
|
||||
"""
|
||||
api_utils.check_policy('baremetal:volume:get')
|
||||
|
||||
rpc_connector, _ = api_utils.check_volume_policy_and_retrieve(
|
||||
'baremetal:volume:get',
|
||||
connector_uuid,
|
||||
target=False)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
|
||||
rpc_connector = objects.VolumeConnector.get_by_uuid(
|
||||
api.request.context, connector_uuid)
|
||||
return convert_with_links(rpc_connector, fields=fields)
|
||||
|
||||
@METRICS.timer('VolumeConnectorsController.post')
|
||||
@ -238,7 +244,23 @@ class VolumeConnectorsController(rest.RestController):
|
||||
same UUID already exists
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:create')
|
||||
owner = None
|
||||
lessee = None
|
||||
raise_node_not_found = False
|
||||
node_uuid = connector.get('node_uuid')
|
||||
|
||||
try:
|
||||
node = api_utils.replace_node_uuid_with_id(connector)
|
||||
owner = node.owner
|
||||
lessee = node.lessee
|
||||
except exception.NotFound:
|
||||
raise_node_not_found = True
|
||||
api_utils.check_owner_policy('node', 'baremetal:volume:create',
|
||||
owner, lessee=lessee, conceal_node=False)
|
||||
|
||||
if raise_node_not_found:
|
||||
raise exception.InvalidInput(fieldname='node_uuid',
|
||||
value=node_uuid)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
@ -247,8 +269,6 @@ class VolumeConnectorsController(rest.RestController):
|
||||
if not connector.get('uuid'):
|
||||
connector['uuid'] = uuidutils.generate_uuid()
|
||||
|
||||
node = api_utils.replace_node_uuid_with_id(connector)
|
||||
|
||||
new_connector = objects.VolumeConnector(context, **connector)
|
||||
|
||||
notify.emit_start_notification(context, new_connector, 'create',
|
||||
@ -294,7 +314,11 @@ class VolumeConnectorsController(rest.RestController):
|
||||
volume connector is not powered off.
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:update')
|
||||
|
||||
rpc_connector, rpc_node = api_utils.check_volume_policy_and_retrieve(
|
||||
'baremetal:volume:update',
|
||||
connector_uuid,
|
||||
target=False)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
@ -307,9 +331,6 @@ class VolumeConnectorsController(rest.RestController):
|
||||
"%(uuid)s.") % {'uuid': str(value)}
|
||||
raise exception.InvalidUUID(message=message)
|
||||
|
||||
rpc_connector = objects.VolumeConnector.get_by_uuid(context,
|
||||
connector_uuid)
|
||||
|
||||
connector_dict = rpc_connector.as_dict()
|
||||
# NOTE(smoriya):
|
||||
# 1) Remove node_id because it's an internal value and
|
||||
@ -370,14 +391,14 @@ class VolumeConnectorsController(rest.RestController):
|
||||
volume connector is not powered off.
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:delete')
|
||||
|
||||
rpc_connector, rpc_node = api_utils.check_volume_policy_and_retrieve(
|
||||
'baremetal:volume:delete',
|
||||
connector_uuid,
|
||||
target=False)
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
|
||||
rpc_connector = objects.VolumeConnector.get_by_uuid(context,
|
||||
connector_uuid)
|
||||
rpc_node = objects.Node.get_by_id(context, rpc_connector.node_id)
|
||||
notify.emit_start_notification(context, rpc_connector, 'delete',
|
||||
node_uuid=rpc_node.uuid)
|
||||
with notify.handle_error_notification(context, rpc_connector,
|
||||
|
@ -27,6 +27,7 @@ from ironic.api import method
|
||||
from ironic.common import args
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import policy
|
||||
from ironic import objects
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
@ -119,9 +120,22 @@ class VolumeTargetsController(rest.RestController):
|
||||
super(VolumeTargetsController, self).__init__()
|
||||
self.parent_node_ident = node_ident
|
||||
|
||||
def _redact_target_properties(self, target):
|
||||
# Filters what could contain sensitive information. For iSCSI
|
||||
# volumes this can include iscsi connection details which may
|
||||
# be sensitive.
|
||||
redacted = ('** Value redacted: Requires permission '
|
||||
'baremetal:volume:view_target_properties '
|
||||
'access. Permission denied. **')
|
||||
redacted_message = {
|
||||
'redacted_contents': redacted
|
||||
}
|
||||
target.properties = redacted_message
|
||||
|
||||
def _get_volume_targets_collection(self, node_ident, marker, limit,
|
||||
sort_key, sort_dir, resource_url=None,
|
||||
fields=None, detail=None):
|
||||
fields=None, detail=None,
|
||||
project=None):
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
@ -134,7 +148,6 @@ class VolumeTargetsController(rest.RestController):
|
||||
raise exception.InvalidParameterValue(
|
||||
_("The sort_key value %(key)s is an invalid field for "
|
||||
"sorting") % {'key': sort_key})
|
||||
|
||||
node_ident = self.parent_node_ident or node_ident
|
||||
|
||||
if node_ident:
|
||||
@ -145,12 +158,19 @@ class VolumeTargetsController(rest.RestController):
|
||||
node = api_utils.get_rpc_node(node_ident)
|
||||
targets = objects.VolumeTarget.list_by_node_id(
|
||||
api.request.context, node.id, limit, marker_obj,
|
||||
sort_key=sort_key, sort_dir=sort_dir)
|
||||
sort_key=sort_key, sort_dir=sort_dir, project=project)
|
||||
else:
|
||||
targets = objects.VolumeTarget.list(api.request.context,
|
||||
limit, marker_obj,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
cdict = api.request.context.to_policy_values()
|
||||
if not policy.check_policy('baremetal:volume:view_target_properties',
|
||||
cdict, cdict):
|
||||
for target in targets:
|
||||
self._redact_target_properties(target)
|
||||
|
||||
return list_convert_with_links(targets, limit,
|
||||
url=resource_url,
|
||||
fields=fields,
|
||||
@ -165,7 +185,7 @@ class VolumeTargetsController(rest.RestController):
|
||||
sort_dir=args.string, fields=args.string_list,
|
||||
detail=args.boolean)
|
||||
def get_all(self, node=None, marker=None, limit=None, sort_key='id',
|
||||
sort_dir='asc', fields=None, detail=None):
|
||||
sort_dir='asc', fields=None, detail=None, project=None):
|
||||
"""Retrieve a list of volume targets.
|
||||
|
||||
:param node: UUID or name of a node, to get only volume targets
|
||||
@ -180,6 +200,8 @@ class VolumeTargetsController(rest.RestController):
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
of the resource to be returned.
|
||||
:param detail: Optional, whether to retrieve with detail.
|
||||
:param project: Optional, an associated node project (owner,
|
||||
or lessee) to filter the query upon.
|
||||
|
||||
:returns: a list of volume targets, or an empty list if no volume
|
||||
target is found.
|
||||
@ -188,8 +210,8 @@ class VolumeTargetsController(rest.RestController):
|
||||
:raises: InvalidParameterValue if sort key is invalid for sorting.
|
||||
:raises: InvalidParameterValue if both fields and detail are specified.
|
||||
"""
|
||||
api_utils.check_policy('baremetal:volume:get')
|
||||
|
||||
project = api_utils.check_volume_list_policy(
|
||||
parent_node=self.parent_node_ident)
|
||||
if fields is None and not detail:
|
||||
fields = _DEFAULT_RETURN_FIELDS
|
||||
|
||||
@ -202,7 +224,8 @@ class VolumeTargetsController(rest.RestController):
|
||||
sort_key, sort_dir,
|
||||
resource_url=resource_url,
|
||||
fields=fields,
|
||||
detail=detail)
|
||||
detail=detail,
|
||||
project=project)
|
||||
|
||||
@METRICS.timer('VolumeTargetsController.get_one')
|
||||
@method.expose()
|
||||
@ -220,13 +243,20 @@ class VolumeTargetsController(rest.RestController):
|
||||
node.
|
||||
:raises: VolumeTargetNotFound if no volume target with this UUID exists
|
||||
"""
|
||||
api_utils.check_policy('baremetal:volume:get')
|
||||
|
||||
rpc_target, _ = api_utils.check_volume_policy_and_retrieve(
|
||||
'baremetal:volume:get',
|
||||
target_uuid,
|
||||
target=True)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
|
||||
rpc_target = objects.VolumeTarget.get_by_uuid(
|
||||
api.request.context, target_uuid)
|
||||
cdict = api.request.context.to_policy_values()
|
||||
if not policy.check_policy('baremetal:volume:view_target_properties',
|
||||
cdict, cdict):
|
||||
self._redact_target_properties(rpc_target)
|
||||
|
||||
return convert_with_links(rpc_target, fields=fields)
|
||||
|
||||
@METRICS.timer('VolumeTargetsController.post')
|
||||
@ -248,7 +278,23 @@ class VolumeTargetsController(rest.RestController):
|
||||
UUID exists
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:create')
|
||||
raise_node_not_found = False
|
||||
node = None
|
||||
owner = None
|
||||
lessee = None
|
||||
node_uuid = target.get('node_uuid')
|
||||
try:
|
||||
node = api_utils.replace_node_uuid_with_id(target)
|
||||
owner = node.owner
|
||||
lessee = node.lessee
|
||||
except exception.NotFound:
|
||||
raise_node_not_found = True
|
||||
api_utils.check_owner_policy('node', 'baremetal:volume:create',
|
||||
owner, lessee=lessee,
|
||||
conceal_node=False)
|
||||
if raise_node_not_found:
|
||||
raise exception.InvalidInput(fieldname='node_uuid',
|
||||
value=node_uuid)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
@ -256,9 +302,6 @@ class VolumeTargetsController(rest.RestController):
|
||||
# NOTE(hshiina): UUID is mandatory for notification payload
|
||||
if not target.get('uuid'):
|
||||
target['uuid'] = uuidutils.generate_uuid()
|
||||
|
||||
node = api_utils.replace_node_uuid_with_id(target)
|
||||
|
||||
new_target = objects.VolumeTarget(context, **target)
|
||||
|
||||
notify.emit_start_notification(context, new_target, 'create',
|
||||
@ -301,7 +344,10 @@ class VolumeTargetsController(rest.RestController):
|
||||
volume target is not powered off.
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:update')
|
||||
|
||||
api_utils.check_volume_policy_and_retrieve('baremetal:volume:update',
|
||||
target_uuid,
|
||||
target=True)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
@ -327,6 +373,10 @@ class VolumeTargetsController(rest.RestController):
|
||||
|
||||
try:
|
||||
if target_dict['node_uuid'] != rpc_node.uuid:
|
||||
|
||||
# TODO(TheJulia): I guess the intention is to
|
||||
# permit the mapping to be changed
|
||||
# should we even allow this at all?
|
||||
rpc_node = objects.Node.get(
|
||||
api.request.context, target_dict['node_uuid'])
|
||||
except exception.NodeNotFound as e:
|
||||
@ -374,7 +424,10 @@ class VolumeTargetsController(rest.RestController):
|
||||
volume target is not powered off.
|
||||
"""
|
||||
context = api.request.context
|
||||
api_utils.check_policy('baremetal:volume:delete')
|
||||
|
||||
api_utils.check_volume_policy_and_retrieve('baremetal:volume:delete',
|
||||
target_uuid,
|
||||
target=True)
|
||||
|
||||
if self.parent_node_ident:
|
||||
raise exception.OperationNotPermitted()
|
||||
|
@ -112,7 +112,18 @@ SYSTEM_OR_OWNER_READER = (
|
||||
'(' + SYSTEM_READER + ') or (' + PROJECT_OWNER_READER + ')'
|
||||
)
|
||||
|
||||
SYSTEM_MEMBER_OR_OWNER_LESSEE_ADMIN = (
|
||||
'(' + SYSTEM_MEMBER + ') or (' + PROJECT_OWNER_ADMIN + ') or (' + PROJECT_LESSEE_ADMIN + ')' # noqa
|
||||
)
|
||||
|
||||
|
||||
# Special purpose aliases for things like "ability to access the API
|
||||
# as a reader, or permission checking that does not require node
|
||||
# owner relationship checking
|
||||
API_READER = ('role:reader')
|
||||
TARGET_PROPERTIES_READER = (
|
||||
'(' + SYSTEM_READER + ') or (role:admin)'
|
||||
)
|
||||
|
||||
default_policies = [
|
||||
# Legacy setting, don't remove. Likely to be overridden by operators who
|
||||
@ -1339,9 +1350,40 @@ roles.
|
||||
|
||||
volume_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:get',
|
||||
name='baremetal:volume:list_all',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
scope_types=['system', 'project'],
|
||||
description=('Retrieve a list of all Volume connector and target '
|
||||
'records'),
|
||||
operations=[
|
||||
{'path': '/volume/connectors', 'method': 'GET'},
|
||||
{'path': '/volume/targets', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/volume/connectors', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/volume/targets', 'method': 'GET'}
|
||||
],
|
||||
deprecated_rule=deprecated_volume_get,
|
||||
deprecated_reason=deprecated_volume_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:list',
|
||||
check_str=API_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve a list of Volume connector and target records',
|
||||
operations=[
|
||||
{'path': '/volume/connectors', 'method': 'GET'},
|
||||
{'path': '/volume/targets', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/volume/connectors', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/volume/targets', 'method': 'GET'}
|
||||
],
|
||||
deprecated_rule=deprecated_volume_get,
|
||||
deprecated_reason=deprecated_volume_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:get',
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve Volume connector and target records',
|
||||
operations=[
|
||||
{'path': '/volume', 'method': 'GET'},
|
||||
@ -1360,8 +1402,8 @@ volume_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:create',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Create Volume connector and target records',
|
||||
operations=[
|
||||
{'path': '/volume/connectors', 'method': 'POST'},
|
||||
@ -1373,8 +1415,8 @@ volume_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:delete',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Delete Volume connector and target records',
|
||||
operations=[
|
||||
{'path': '/volume/connectors/{volume_connector_id}',
|
||||
@ -1388,8 +1430,8 @@ volume_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:update',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Update Volume connector and target records',
|
||||
operations=[
|
||||
{'path': '/volume/connectors/{volume_connector_id}',
|
||||
@ -1401,6 +1443,21 @@ volume_policies = [
|
||||
deprecated_reason=deprecated_volume_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:volume:view_target_properties',
|
||||
check_str=TARGET_PROPERTIES_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Ability to view volume target properties',
|
||||
operations=[
|
||||
{'path': '/volume/connectors/{volume_connector_id}',
|
||||
'method': 'GET'},
|
||||
{'path': '/volume/targets/{volume_target_id}',
|
||||
'method': 'GET'}
|
||||
],
|
||||
deprecated_rule=deprecated_volume_update,
|
||||
deprecated_reason=deprecated_volume_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -714,7 +714,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_volume_connector_list(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None,
|
||||
project=None):
|
||||
"""Return a list of volume connectors.
|
||||
|
||||
:param limit: Maximum number of volume connectors to return.
|
||||
@ -723,6 +724,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: A list of volume connectors.
|
||||
:raises: InvalidParameterValue If sort_key does not exist.
|
||||
"""
|
||||
@ -750,7 +753,7 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def get_volume_connectors_by_node_id(self, node_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
sort_dir=None, project=None):
|
||||
"""List all the volume connectors for a given node.
|
||||
|
||||
:param node_id: The integer node ID.
|
||||
@ -760,6 +763,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
:param sort_key: Attribute by which results should be sorted
|
||||
:param sort_dir: Direction in which results should be sorted
|
||||
(asc, desc)
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: A list of volume connectors.
|
||||
:raises: InvalidParameterValue If sort_key does not exist.
|
||||
"""
|
||||
@ -813,7 +818,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_volume_target_list(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None,
|
||||
project=None):
|
||||
"""Return a list of volume targets.
|
||||
|
||||
:param limit: Maximum number of volume targets to return.
|
||||
@ -822,6 +828,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: A list of volume targets.
|
||||
:raises: InvalidParameterValue if sort_key does not exist.
|
||||
"""
|
||||
@ -849,7 +857,7 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def get_volume_targets_by_node_id(self, node_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
sort_dir=None, project=None):
|
||||
"""List all the volume targets for a given node.
|
||||
|
||||
:param node_id: The integer node ID.
|
||||
@ -859,6 +867,8 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
:param sort_key: Attribute by which results should be sorted
|
||||
:param sort_dir: direction in which results should be sorted
|
||||
(asc, desc)
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: A list of volume targets.
|
||||
:raises: InvalidParameterValue if sort_key does not exist.
|
||||
"""
|
||||
@ -866,7 +876,7 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def get_volume_targets_by_volume_id(self, volume_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
sort_dir=None, project=None):
|
||||
"""List all the volume targets for a given volume id.
|
||||
|
||||
:param volume_id: The UUID of the volume.
|
||||
|
@ -169,6 +169,20 @@ def add_portgroup_filter_by_node_project(query, value):
|
||||
| (models.Node.lessee == value))
|
||||
|
||||
|
||||
def add_volume_conn_filter_by_node_project(query, value):
|
||||
query = query.join(models.Node,
|
||||
models.VolumeConnector.node_id == models.Node.id)
|
||||
return query.filter((models.Node.owner == value)
|
||||
| (models.Node.lessee == value))
|
||||
|
||||
|
||||
def add_volume_target_filter_by_node_project(query, value):
|
||||
query = query.join(models.Node,
|
||||
models.VolumeTarget.node_id == models.Node.id)
|
||||
return query.filter((models.Node.owner == value)
|
||||
| (models.Node.lessee == value))
|
||||
|
||||
|
||||
def add_portgroup_filter(query, value):
|
||||
"""Adds a portgroup-specific filter to a query.
|
||||
|
||||
@ -1235,9 +1249,12 @@ class Connection(api.Connection):
|
||||
% addresses)
|
||||
|
||||
def get_volume_connector_list(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
query = model_query(models.VolumeConnector)
|
||||
if project:
|
||||
query = add_volume_conn_filter_by_node_project(query, project)
|
||||
return _paginate_query(models.VolumeConnector, limit, marker,
|
||||
sort_key, sort_dir)
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def get_volume_connector_by_id(self, db_id):
|
||||
query = model_query(models.VolumeConnector).filter_by(id=db_id)
|
||||
@ -1256,8 +1273,10 @@ class Connection(api.Connection):
|
||||
|
||||
def get_volume_connectors_by_node_id(self, node_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
sort_dir=None, project=None):
|
||||
query = model_query(models.VolumeConnector).filter_by(node_id=node_id)
|
||||
if project:
|
||||
add_volume_conn_filter_by_node_project(query, project)
|
||||
return _paginate_query(models.VolumeConnector, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
@ -1315,9 +1334,12 @@ class Connection(api.Connection):
|
||||
raise exception.VolumeConnectorNotFound(connector=ident)
|
||||
|
||||
def get_volume_target_list(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
query = model_query(models.VolumeTarget)
|
||||
if project:
|
||||
query = add_volume_target_filter_by_node_project(query, project)
|
||||
return _paginate_query(models.VolumeTarget, limit, marker,
|
||||
sort_key, sort_dir)
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def get_volume_target_by_id(self, db_id):
|
||||
query = model_query(models.VolumeTarget).filter_by(id=db_id)
|
||||
@ -1334,15 +1356,20 @@ class Connection(api.Connection):
|
||||
raise exception.VolumeTargetNotFound(target=uuid)
|
||||
|
||||
def get_volume_targets_by_node_id(self, node_id, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None,
|
||||
project=None):
|
||||
query = model_query(models.VolumeTarget).filter_by(node_id=node_id)
|
||||
if project:
|
||||
add_volume_target_filter_by_node_project(query, project)
|
||||
return _paginate_query(models.VolumeTarget, limit, marker, sort_key,
|
||||
sort_dir, query)
|
||||
|
||||
def get_volume_targets_by_volume_id(self, volume_id, limit=None,
|
||||
marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
sort_dir=None, project=None):
|
||||
query = model_query(models.VolumeTarget).filter_by(volume_id=volume_id)
|
||||
if project:
|
||||
query = add_volume_target_filter_by_node_project(query, project)
|
||||
return _paginate_query(models.VolumeTarget, limit, marker, sort_key,
|
||||
sort_dir, query)
|
||||
|
||||
|
@ -108,7 +108,7 @@ class VolumeConnector(base.IronicObject,
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
"""Return a list of VolumeConnector objects.
|
||||
|
||||
:param context: security context
|
||||
@ -116,13 +116,15 @@ class VolumeConnector(base.IronicObject,
|
||||
:param marker: pagination marker for large data sets
|
||||
:param sort_key: column to sort results by
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:raises: InvalidParameterValue if sort_key does not exist
|
||||
"""
|
||||
db_connectors = cls.dbapi.get_volume_connector_list(limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return cls._from_db_object_list(context, db_connectors)
|
||||
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
@ -131,7 +133,7 @@ class VolumeConnector(base.IronicObject,
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list_by_node_id(cls, context, node_id, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
"""Return a list of VolumeConnector objects related to a given node ID.
|
||||
|
||||
:param context: security context
|
||||
@ -140,6 +142,8 @@ class VolumeConnector(base.IronicObject,
|
||||
:param marker: pagination marker for large data sets
|
||||
:param sort_key: column to sort results by
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:raises: InvalidParameterValue if sort_key does not exist
|
||||
"""
|
||||
@ -148,7 +152,8 @@ class VolumeConnector(base.IronicObject,
|
||||
limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return cls._from_db_object_list(context, db_connectors)
|
||||
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
|
@ -107,7 +107,7 @@ class VolumeTarget(base.IronicObject,
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
"""Return a list of VolumeTarget objects.
|
||||
|
||||
:param context: security context
|
||||
@ -115,13 +115,16 @@ class VolumeTarget(base.IronicObject,
|
||||
:param marker: pagination marker for large data sets
|
||||
:param sort_key: column to sort results by
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: a list of :class:`VolumeTarget` objects
|
||||
:raises: InvalidParameterValue if sort_key does not exist
|
||||
"""
|
||||
db_targets = cls.dbapi.get_volume_target_list(limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return cls._from_db_object_list(context, db_targets)
|
||||
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
@ -130,7 +133,7 @@ class VolumeTarget(base.IronicObject,
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list_by_node_id(cls, context, node_id, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
"""Return a list of VolumeTarget objects related to a given node ID.
|
||||
|
||||
:param context: security context
|
||||
@ -139,6 +142,8 @@ class VolumeTarget(base.IronicObject,
|
||||
:param marker: pagination marker for large data sets
|
||||
:param sort_key: column to sort results by
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:param project: The associated node project to search with.
|
||||
:returns: a list of :class:`VolumeConnector` objects
|
||||
:returns: a list of :class:`VolumeTarget` objects
|
||||
:raises: InvalidParameterValue if sort_key does not exist
|
||||
"""
|
||||
@ -147,7 +152,8 @@ class VolumeTarget(base.IronicObject,
|
||||
limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return cls._from_db_object_list(context, db_targets)
|
||||
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
@ -156,7 +162,7 @@ class VolumeTarget(base.IronicObject,
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list_by_volume_id(cls, context, volume_id, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key=None, sort_dir=None, project=None):
|
||||
"""Return a list of VolumeTarget objects related to a given volume ID.
|
||||
|
||||
:param context: security context
|
||||
@ -174,7 +180,8 @@ class VolumeTarget(base.IronicObject,
|
||||
limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
sort_dir=sort_dir,
|
||||
project=project)
|
||||
return cls._from_db_object_list(context, db_targets)
|
||||
|
||||
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
|
||||
|
@ -391,6 +391,13 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
node_id=owned_node['id'],
|
||||
name='magicfoo',
|
||||
address='01:03:09:ff:01:01')
|
||||
db_utils.create_test_volume_target(
|
||||
uuid='a265e2f0-e97f-4177-b1c0-8298add53086',
|
||||
node_id=owned_node['id'])
|
||||
db_utils.create_test_volume_connector(
|
||||
uuid='65ea0296-219b-4635-b0c8-a6e055da878d',
|
||||
node_id=owned_node['id'],
|
||||
connector_id='iqn.2012-06.org.openstack.magic')
|
||||
|
||||
# Leased nodes
|
||||
leased_node = db_utils.create_test_node(
|
||||
|
@ -1315,9 +1315,9 @@ volume_connectors_post_admin:
|
||||
path: '/v1/volume/connectors'
|
||||
method: post
|
||||
headers: *admin_headers
|
||||
assert_status: 400
|
||||
assert_status: 201
|
||||
body: &volume_connector_body
|
||||
node_uuid: 68a552fb-dcd2-43bf-9302-e4c93287be16
|
||||
node_uuid: 1be26c0b-03f2-4d2e-ae87-c02d7f33c123
|
||||
type: ip
|
||||
connector_id: 192.168.1.100
|
||||
deprecated: true
|
||||
@ -1349,7 +1349,7 @@ volume_volume_connector_id_get_member:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_connector_id_get_observer:
|
||||
@ -1375,7 +1375,7 @@ volume_volume_connector_id_patch_member:
|
||||
method: patch
|
||||
headers: *member_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_connector_id_patch_observer:
|
||||
@ -1397,7 +1397,7 @@ volume_volume_connector_id_delete_member:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_connector_id_delete_observer:
|
||||
@ -1437,11 +1437,11 @@ volume_targets_post_admin:
|
||||
path: '/v1/volume/targets'
|
||||
method: post
|
||||
headers: *admin_headers
|
||||
assert_status: 400
|
||||
assert_status: 201
|
||||
body: &volume_target_body
|
||||
node_uuid: 68a552fb-dcd2-43bf-9302-e4c93287be16
|
||||
node_uuid: 1be26c0b-03f2-4d2e-ae87-c02d7f33c123
|
||||
volume_type: iscsi
|
||||
boot_index: 0
|
||||
boot_index: 4
|
||||
volume_id: 'test-id'
|
||||
deprecated: true
|
||||
|
||||
@ -1472,7 +1472,7 @@ volume_volume_target_id_get_member:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_target_id_get_observer:
|
||||
@ -1493,12 +1493,12 @@ volume_volume_target_id_patch_admin:
|
||||
assert_status: 503
|
||||
deprecated: true
|
||||
|
||||
volume_volume_target_id_patch_admin:
|
||||
volume_volume_target_id_patch_member:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: patch
|
||||
body: *volume_target_patch
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_target_id_patch_observer:
|
||||
@ -1520,7 +1520,7 @@ volume_volume_target_id_delete_member:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
volume_volume_target_id_delete_observer:
|
||||
@ -1564,7 +1564,7 @@ nodes_volume_connectors_get_member:
|
||||
path: '/v1/nodes/{node_ident}/volume/connectors'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_volume_connectors_get_observer:
|
||||
@ -1585,7 +1585,7 @@ nodes_volume_targets_get_member:
|
||||
path: '/v1/nodes/{node_ident}/volume/targets'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_volume_targets_get_observer:
|
||||
|
@ -1884,7 +1884,6 @@ owner_reader_can_list_volume_connectors:
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
connectors: 2
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_reader_can_list_volume_connectors:
|
||||
path: '/v1/volume/connectors'
|
||||
@ -1893,27 +1892,24 @@ lessee_reader_can_list_volume_connectors:
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
connectors: 1
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_get_connector_list:
|
||||
path: '/v1/volume/targets'
|
||||
path: '/v1/volume/connectors'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
connectors: 0
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_admin_can_post_volume_connector:
|
||||
path: '/v1/volume/connectors'
|
||||
method: post
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 400
|
||||
headers: *owner_admin_headers
|
||||
assert_status: 201
|
||||
body: &volume_connector_body
|
||||
node_uuid: 68a552fb-dcd2-43bf-9302-e4c93287be16
|
||||
node_uuid: 1ab63b9e-66d7-4cd7-8618-dddd0f9f7881
|
||||
type: ip
|
||||
connector_id: 192.168.1.100
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_admin_cannot_post_volume_connector:
|
||||
path: '/v1/volume/connectors'
|
||||
@ -1921,7 +1917,6 @@ lessee_admin_cannot_post_volume_connector:
|
||||
headers: *lessee_admin_headers
|
||||
assert_status: 403
|
||||
body: *volume_connector_body
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_post_volume_connector:
|
||||
path: '/v1/volume/connectors'
|
||||
@ -1929,28 +1924,24 @@ third_party_admin_cannot_post_volume_connector:
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 403
|
||||
body: *volume_connector_body
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_reader_can_get_volume_connector:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: get
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 200
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_reader_can_get_volume_connector:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 200
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_get_volume_connector:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 404
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_member_cannot_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -1961,7 +1952,6 @@ lessee_member_cannot_patch_volume_connectors:
|
||||
path: /extra
|
||||
value: {'test': 'testing'}
|
||||
assert_status: 403
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_admin_can_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -1969,7 +1959,6 @@ owner_admin_can_patch_volume_connectors:
|
||||
headers: *owner_member_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 503
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_admin_cannot_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -1977,7 +1966,6 @@ lessee_admin_cannot_patch_volume_connectors:
|
||||
headers: *owner_member_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 503
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_member_can_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -1985,7 +1973,6 @@ owner_member_can_patch_volume_connectors:
|
||||
headers: *owner_member_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 503
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_member_cannot_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -1993,7 +1980,6 @@ lessee_member_cannot_patch_volume_connectors:
|
||||
headers: *lessee_member_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 403
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_patch_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
@ -2001,28 +1987,24 @@ third_party_admin_cannot_patch_volume_connectors:
|
||||
headers: *third_party_admin_headers
|
||||
body: *connector_patch_body
|
||||
assert_status: 404
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_admin_can_delete_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: delete
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 403
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_admin_cannot_delete_volume_connectors:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: delete
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 403
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_delete_volume_connector:
|
||||
path: '/v1/volume/connectors/{volume_connector_ident}'
|
||||
method: delete
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 403
|
||||
skip_reason: policy not implemented
|
||||
assert_status: 404
|
||||
|
||||
# Volume targets
|
||||
|
||||
@ -2034,7 +2016,6 @@ owner_reader_can_get_targets:
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
targets: 2
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lesse_reader_can_get_targets:
|
||||
path: '/v1/volume/targets'
|
||||
@ -2043,7 +2024,6 @@ lesse_reader_can_get_targets:
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
targets: 1
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_get_target_list:
|
||||
path: '/v1/volume/targets'
|
||||
@ -2052,56 +2032,58 @@ third_party_admin_cannot_get_target_list:
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
targets: 0
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_reader_can_get_volume_target:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: get
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 200
|
||||
skip_reason: policy not implemented
|
||||
assert_dict_contains:
|
||||
# This helps assert that the field has been redacted.
|
||||
properties:
|
||||
redacted_contents: '** Value redacted: Requires permission baremetal:volume:view_target_properties access. Permission denied. **'
|
||||
|
||||
|
||||
lessee_reader_can_get_volume_target:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 200
|
||||
skip_reason: policy not implemented
|
||||
|
||||
third_party_admin_cannot_get_volume_target:
|
||||
path: '/v1/volume/targets/{volume_target_ident}'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 404
|
||||
skip_reason: policy not implemented
|
||||
|
||||
owner_admin_create_volume_target:
|
||||
path: '/v1/volume/targets'
|
||||
method: post
|
||||
headers: *owner_admin_headers
|
||||
assert_status: 400
|
||||
assert_status: 201
|
||||
body: &volume_target_body
|
||||
node_uuid: 68a552fb-dcd2-43bf-9302-e4c93287be16
|
||||
node_uuid: 1ab63b9e-66d7-4cd7-8618-dddd0f9f7881
|
||||
volume_type: iscsi
|
||||
boot_index: 0
|
||||
boot_index: 2
|
||||
volume_id: 'test-id'
|
||||
skip_reason: policy not implemented
|
||||
|
||||
lessee_admin_create_volume_target:
|
||||
path: '/v1/volume/targets'
|
||||
method: post
|
||||
headers: *owner_admin_headers
|
||||
assert_status: 400
|
||||
body: *volume_target_body
|
||||
skip_reason: policy not implemented
|
||||
assert_status: 201
|
||||
body:
|
||||
node_uuid: 38d5abed-c585-4fce-a57e-a2ffc2a2ec6f
|
||||
volume_type: iscsi
|
||||
boot_index: 2
|
||||