Separate update and swap volume policies
This patch changes the volume update policy to be 'rule:system_admin_or_owner' to allow instance owners to update attachment details like delete_on_termination. It creates a new volume swap policy element with the old admin-only behavior, and makes the volume update code check the appropriate policy based on what action is being performed. Co-Authored-By: Dan Smith <dansmith@redhat.com> Partial implement blueprint destroy-instance-with-datavolume Change-Id: I2cbe37b65ceac2efb3b252460dc01d17474e6343
This commit is contained in:
parent
e5cd0fdd87
commit
fcf5863662
@ -465,23 +465,26 @@ class VolumeAttachmentController(wsgi.Controller):
|
|||||||
def update(self, req, server_id, id, body):
|
def update(self, req, server_id, id, body):
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
instance = common.get_instance(self.compute_api, context, server_id)
|
instance = common.get_instance(self.compute_api, context, server_id)
|
||||||
# TODO(danms): For now, use the existing admin-only policy for update.
|
|
||||||
# Later, split off the swap_volume permission and check the correct
|
|
||||||
# policy based on what is being asked by the client.
|
|
||||||
context.can(va_policies.POLICY_ROOT % 'update',
|
|
||||||
target={'project_id': instance.project_id})
|
|
||||||
|
|
||||||
attachment = body['volumeAttachment']
|
attachment = body['volumeAttachment']
|
||||||
volume_id = attachment['volumeId']
|
volume_id = attachment['volumeId']
|
||||||
only_swap = not api_version_request.is_supported(req, '2.85')
|
only_swap = not api_version_request.is_supported(req, '2.85')
|
||||||
|
|
||||||
|
# NOTE(brinzhang): If the 'volumeId' requested by the user is
|
||||||
|
# different from the 'id' in the url path, or only swap is allowed by
|
||||||
|
# the microversion, we should check the swap volume policy.
|
||||||
|
# otherwise, check the volume update policy.
|
||||||
|
if only_swap or id != volume_id:
|
||||||
|
context.can(va_policies.POLICY_ROOT % 'swap', target={})
|
||||||
|
else:
|
||||||
|
context.can(va_policies.POLICY_ROOT % 'update',
|
||||||
|
target={'project_id': instance.project_id})
|
||||||
|
|
||||||
if only_swap:
|
if only_swap:
|
||||||
# NOTE(danms): Original behavior is always call swap on PUT
|
# NOTE(danms): Original behavior is always call swap on PUT
|
||||||
# FIXME(danms): Check the swap volume policy here
|
|
||||||
self._update_volume_swap(req, instance, id, body)
|
self._update_volume_swap(req, instance, id, body)
|
||||||
else:
|
else:
|
||||||
# NOTE(danms): New behavior is update any supported attachment
|
# NOTE(danms): New behavior is update any supported attachment
|
||||||
# properties first, and then call swap if volumeId differs
|
# properties first, and then call swap if volumeId differs
|
||||||
# FIXME(danms): Check the volume attachment update policy here
|
|
||||||
self._update_volume_regular(req, instance, id, body)
|
self._update_volume_regular(req, instance, id, body)
|
||||||
if id != volume_id:
|
if id != volume_id:
|
||||||
self._update_volume_swap(req, instance, id, body)
|
self._update_volume_swap(req, instance, id, body)
|
||||||
|
@ -57,8 +57,24 @@ volumes_attachments_policies = [
|
|||||||
scope_types=['system', 'project']),
|
scope_types=['system', 'project']),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=POLICY_ROOT % 'update',
|
name=POLICY_ROOT % 'update',
|
||||||
|
check_str=base.PROJECT_MEMBER_OR_SYSTEM_ADMIN,
|
||||||
|
description="""Update a volume attachment.
|
||||||
|
New 'update' policy about 'swap + update' request (which is possible
|
||||||
|
only >2.85) only <swap policy> is checked. We expect <swap policy> to be
|
||||||
|
always superset of this policy permission.
|
||||||
|
""",
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'method': 'PUT',
|
||||||
|
'path':
|
||||||
|
'/servers/{server_id}/os-volume_attachments/{volume_id}'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scope_types=['system', 'project']),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=POLICY_ROOT % 'swap',
|
||||||
check_str=base.SYSTEM_ADMIN,
|
check_str=base.SYSTEM_ADMIN,
|
||||||
description="Update a volume attachment",
|
description="Update a volume attachment with a different volumeId",
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'method': 'PUT',
|
'method': 'PUT',
|
||||||
|
@ -113,6 +113,7 @@ policy_data = """
|
|||||||
"os_compute_api:os-volumes-attachments:show": "",
|
"os_compute_api:os-volumes-attachments:show": "",
|
||||||
"os_compute_api:os-volumes-attachments:create": "",
|
"os_compute_api:os-volumes-attachments:create": "",
|
||||||
"os_compute_api:os-volumes-attachments:update": "",
|
"os_compute_api:os-volumes-attachments:update": "",
|
||||||
|
"os_compute_api:os-volumes-attachments:swap":"",
|
||||||
"os_compute_api:os-volumes-attachments:delete": "",
|
"os_compute_api:os-volumes-attachments:delete": "",
|
||||||
"os_compute_api:os-availability-zone:list": "",
|
"os_compute_api:os-availability-zone:list": "",
|
||||||
"os_compute_api:os-availability-zone:detail": "",
|
"os_compute_api:os-availability-zone:detail": "",
|
||||||
|
@ -19,6 +19,7 @@ from nova.api.openstack.compute import volumes as volumes_v21
|
|||||||
from nova.compute import vm_states
|
from nova.compute import vm_states
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import objects
|
from nova import objects
|
||||||
|
from nova.objects import block_device as block_device_obj
|
||||||
from nova.policies import volumes_attachments as va_policies
|
from nova.policies import volumes_attachments as va_policies
|
||||||
from nova.tests.unit.api.openstack import fakes
|
from nova.tests.unit.api.openstack import fakes
|
||||||
from nova.tests.unit import fake_block_device
|
from nova.tests.unit import fake_block_device
|
||||||
@ -160,6 +161,19 @@ class VolumeAttachPolicyTest(base.BasePolicyTest):
|
|||||||
rule_name, self.controller.create,
|
rule_name, self.controller.create,
|
||||||
self.req, FAKE_UUID, body=body)
|
self.req, FAKE_UUID, body=body)
|
||||||
|
|
||||||
|
@mock.patch.object(block_device_obj.BlockDeviceMapping, 'save')
|
||||||
|
def test_update_volume_attach_policy(self, mock_bdm_save):
|
||||||
|
rule_name = self.policy_root % "update"
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.85')
|
||||||
|
body = {'volumeAttachment': {
|
||||||
|
'volumeId': FAKE_UUID_A,
|
||||||
|
'delete_on_termination': True}}
|
||||||
|
self.common_policy_check(self.admin_or_owner_authorized_contexts,
|
||||||
|
self.admin_or_owner_unauthorized_contexts,
|
||||||
|
rule_name, self.controller.update,
|
||||||
|
req, FAKE_UUID,
|
||||||
|
FAKE_UUID_A, body=body)
|
||||||
|
|
||||||
@mock.patch('nova.compute.api.API.detach_volume')
|
@mock.patch('nova.compute.api.API.detach_volume')
|
||||||
def test_delete_volume_attach_policy(self, mock_detach_volume):
|
def test_delete_volume_attach_policy(self, mock_detach_volume):
|
||||||
rule_name = self.policy_root % "delete"
|
rule_name = self.policy_root % "delete"
|
||||||
@ -169,14 +183,52 @@ class VolumeAttachPolicyTest(base.BasePolicyTest):
|
|||||||
self.req, FAKE_UUID, FAKE_UUID_A)
|
self.req, FAKE_UUID, FAKE_UUID_A)
|
||||||
|
|
||||||
@mock.patch('nova.compute.api.API.swap_volume')
|
@mock.patch('nova.compute.api.API.swap_volume')
|
||||||
def test_update_volume_attach_policy(self, mock_swap_volume):
|
def test_swap_volume_attach_policy(self, mock_swap_volume):
|
||||||
rule_name = self.policy_root % "update"
|
rule_name = self.policy_root % "swap"
|
||||||
body = {'volumeAttachment': {'volumeId': FAKE_UUID_B}}
|
body = {'volumeAttachment': {'volumeId': FAKE_UUID_B}}
|
||||||
self.common_policy_check(self.admin_authorized_contexts,
|
self.common_policy_check(self.admin_authorized_contexts,
|
||||||
self.admin_unauthorized_contexts,
|
self.admin_unauthorized_contexts,
|
||||||
rule_name, self.controller.update,
|
rule_name, self.controller.update,
|
||||||
self.req, FAKE_UUID, FAKE_UUID_A, body=body)
|
self.req, FAKE_UUID, FAKE_UUID_A, body=body)
|
||||||
|
|
||||||
|
@mock.patch.object(block_device_obj.BlockDeviceMapping, 'save')
|
||||||
|
@mock.patch('nova.compute.api.API.swap_volume')
|
||||||
|
def test_swap_volume_attach_policy_failed(self,
|
||||||
|
mock_swap_volume,
|
||||||
|
mock_bdm_save):
|
||||||
|
"""Policy check fails for swap + update due to swap policy failure.
|
||||||
|
"""
|
||||||
|
rule_name = self.policy_root % "swap"
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.85')
|
||||||
|
req.environ['nova.context'].user_id = 'other-user'
|
||||||
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
||||||
|
body = {'volumeAttachment': {'volumeId': FAKE_UUID_B,
|
||||||
|
'delete_on_termination': True}}
|
||||||
|
exc = self.assertRaises(
|
||||||
|
exception.PolicyNotAuthorized, self.controller.update,
|
||||||
|
req, FAKE_UUID, FAKE_UUID_A, body=body)
|
||||||
|
self.assertEqual(
|
||||||
|
"Policy doesn't allow %s to be performed." % rule_name,
|
||||||
|
exc.format_message())
|
||||||
|
mock_swap_volume.assert_not_called()
|
||||||
|
mock_bdm_save.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(block_device_obj.BlockDeviceMapping, 'save')
|
||||||
|
@mock.patch('nova.compute.api.API.swap_volume')
|
||||||
|
def test_pass_swap_and_update_volume_attach_policy(self,
|
||||||
|
mock_swap_volume,
|
||||||
|
mock_bdm_save):
|
||||||
|
rule_name = self.policy_root % "swap"
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.85')
|
||||||
|
body = {'volumeAttachment': {'volumeId': FAKE_UUID_B,
|
||||||
|
'delete_on_termination': True}}
|
||||||
|
self.common_policy_check(self.admin_authorized_contexts,
|
||||||
|
self.admin_unauthorized_contexts,
|
||||||
|
rule_name, self.controller.update,
|
||||||
|
req, FAKE_UUID, FAKE_UUID_A, body=body)
|
||||||
|
mock_swap_volume.assert_called()
|
||||||
|
mock_bdm_save.assert_called()
|
||||||
|
|
||||||
|
|
||||||
class VolumeAttachScopeTypePolicyTest(VolumeAttachPolicyTest):
|
class VolumeAttachScopeTypePolicyTest(VolumeAttachPolicyTest):
|
||||||
"""Test os-volume-attachments APIs policies with system scope enabled.
|
"""Test os-volume-attachments APIs policies with system scope enabled.
|
||||||
|
@ -360,7 +360,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
|
|||||||
"os_compute_api:os-console-auth-tokens",
|
"os_compute_api:os-console-auth-tokens",
|
||||||
"os_compute_api:os-quota-class-sets:update",
|
"os_compute_api:os-quota-class-sets:update",
|
||||||
"os_compute_api:os-server-external-events:create",
|
"os_compute_api:os-server-external-events:create",
|
||||||
"os_compute_api:os-volumes-attachments:update",
|
"os_compute_api:os-volumes-attachments:swap",
|
||||||
"os_compute_api:servers:create:zero_disk_flavor",
|
"os_compute_api:servers:create:zero_disk_flavor",
|
||||||
"os_compute_api:servers:migrations:index",
|
"os_compute_api:servers:migrations:index",
|
||||||
"os_compute_api:servers:migrations:show",
|
"os_compute_api:servers:migrations:show",
|
||||||
@ -445,6 +445,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
|
|||||||
"os_compute_api:os-volumes",
|
"os_compute_api:os-volumes",
|
||||||
"os_compute_api:os-volumes-attachments:create",
|
"os_compute_api:os-volumes-attachments:create",
|
||||||
"os_compute_api:os-volumes-attachments:delete",
|
"os_compute_api:os-volumes-attachments:delete",
|
||||||
|
"os_compute_api:os-volumes-attachments:update",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.allow_all_rules = (
|
self.allow_all_rules = (
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
With microversion 2.85, existing policy ``os-volumes-attachments:update``
|
||||||
|
is used for updating the resources with the change in its default value
|
||||||
|
from ``SYSTEM_ADMIN`` to ``PROJECT_MEMBER_OR_SYSTEM_ADMIN``.
|
||||||
|
New policy ``os-volumes-attachments:swap`` is introduced for swapping
|
||||||
|
the attachment of servers with default to ``SYSTEM_ADMIN``.
|
Loading…
Reference in New Issue
Block a user