[policy in code] Add support for service, limits

This patch adds policy in code support for capabilities,
hosts, services, limits and depends on the quota patch [1].

[1]: https://review.openstack.org/#/c/508091/

Change-Id: Ib2bac2d28d950c0d8b734a54e300dd4185d98ca9
Partial-Implements: blueprint policy-in-code
This commit is contained in:
TommyLike 2017-10-10 08:56:30 +08:00
parent 5a099de01a
commit 5b5715e2ad
16 changed files with 276 additions and 48 deletions

View File

@ -21,13 +21,10 @@ from cinder.api.views import capabilities as capabilities_view
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.policies import capabilities as policy
from cinder.volume import rpcapi
def authorize(context, action_name):
extensions.extension_authorizer('volume', action_name)(context)
class CapabilitiesController(wsgi.Controller):
"""The Capabilities controller for the OpenStack API."""
@ -43,7 +40,7 @@ class CapabilitiesController(wsgi.Controller):
def show(self, req, id):
"""Return capabilities list of given backend."""
context = req.environ['cinder.context']
authorize(context, 'capabilities')
context.authorize(policy.CAPABILITIES_POLICY)
filters = {'host_or_cluster': id, 'binary': 'cinder-volume'}
services = objects.ServiceList.get_all(context, filters)
if not services:

View File

@ -27,13 +27,13 @@ from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.policies import hosts as policy
from cinder.volume import api as volume_api
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('volume', 'hosts')
def _list_hosts(req, service=None):
@ -91,12 +91,14 @@ class HostController(wsgi.Controller):
super(HostController, self).__init__()
def index(self, req):
authorize(req.environ['cinder.context'])
context = req.environ['cinder.context']
context.authorize(policy.MANAGE_POLICY)
return {'hosts': _list_hosts(req)}
@check_host
def update(self, req, id, body):
authorize(req.environ['cinder.context'])
context = req.environ['cinder.context']
context.authorize(policy.MANAGE_POLICY)
update_values = {}
for raw_key, raw_val in body.items():
key = raw_key.lower().strip()

View File

@ -19,15 +19,11 @@ from cinder.api import extensions
from cinder.api import microversions as mv
from cinder.api.openstack import wsgi
from cinder.api.views import scheduler_stats as scheduler_stats_view
from cinder.policies import scheduler_stats as policy
from cinder.scheduler import rpcapi
from cinder import utils
def authorize(context, action_name):
action = 'scheduler_stats:%s' % action_name
extensions.extension_authorizer('scheduler', action)(context)
class SchedulerStatsController(wsgi.Controller):
"""The Scheduler Stats controller for the OpenStack API."""
@ -46,7 +42,7 @@ class SchedulerStatsController(wsgi.Controller):
def get_pools(self, req):
"""List all active pools in scheduler."""
context = req.environ['cinder.context']
authorize(context, 'get_pools')
context.authorize(policy.GET_POOL_POLICY)
detail = utils.get_bool_param('detail', req.params)

View File

@ -29,6 +29,7 @@ from cinder.common import constants
from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder.policies import services as policy
from cinder.scheduler import rpcapi as scheduler_rpcapi
from cinder import utils
from cinder import volume
@ -38,7 +39,6 @@ from cinder.volume import rpcapi as volume_rpcapi
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('volume', 'services')
class ServiceController(wsgi.Controller):
@ -61,7 +61,7 @@ class ServiceController(wsgi.Controller):
Filter by host & service name.
"""
context = req.environ['cinder.context']
authorize(context, action='index')
context.authorize(policy.GET_ALL_POLICY)
detailed = self.ext_mgr.is_loaded('os-extended-services')
now = timeutils.utcnow(with_timezone=True)
@ -223,7 +223,7 @@ class ServiceController(wsgi.Controller):
directly in this API layer.
"""
context = req.environ['cinder.context']
authorize(context, action='update')
context.authorize(policy.UPDATE_POLICY)
support_dynamic_log = req.api_version_request.matches(mv.LOG_LEVEL)

View File

@ -15,19 +15,19 @@
from cinder.api import extensions
from cinder.api import microversions as mv
from cinder.api.openstack import wsgi
from cinder.policies import limits as policy
from cinder import quota
QUOTAS = quota.QUOTAS
authorize = extensions.soft_extension_authorizer('limits', 'used_limits')
class UsedLimitsController(wsgi.Controller):
@wsgi.extends
def index(self, req, resp_obj):
context = req.environ['cinder.context']
if authorize(context):
if context.authorize(
policy.EXTEND_LIMIT_ATTRIBUTE_POLICY, fatal=False):
params = req.params.copy()
req_version = req.api_version_request

View File

@ -19,17 +19,22 @@ from cinder.policies import attachments
from cinder.policies import backup_actions
from cinder.policies import backups
from cinder.policies import base
from cinder.policies import capabilities
from cinder.policies import clusters
from cinder.policies import group_actions
from cinder.policies import group_snapshot_actions
from cinder.policies import group_snapshots
from cinder.policies import group_types
from cinder.policies import groups
from cinder.policies import hosts
from cinder.policies import limits
from cinder.policies import manageable_snapshots
from cinder.policies import messages
from cinder.policies import qos_specs
from cinder.policies import quota_class
from cinder.policies import quotas
from cinder.policies import scheduler_stats
from cinder.policies import services
from cinder.policies import snapshot_actions
from cinder.policies import snapshot_metadata
from cinder.policies import snapshots
@ -57,4 +62,9 @@ def list_rules():
qos_specs.list_rules(),
quota_class.list_rules(),
quotas.list_rules(),
capabilities.list_rules(),
services.list_rules(),
scheduler_stats.list_rules(),
hosts.list_rules(),
limits.list_rules(),
)

View File

@ -0,0 +1,39 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
CAPABILITIES_POLICY = "volume_extension:capabilities",
capabilities_policies = [
policy.DocumentedRuleDefault(
name=CAPABILITIES_POLICY,
check_str=base.RULE_ADMIN_API,
description="Show backend capabilities.",
operations=[
{
'method': 'GET',
'path': '/capabilities/{host_name}'
}
])
]
def list_rules():
return capabilities_policies

42
cinder/policies/hosts.py Normal file
View File

@ -0,0 +1,42 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
MANAGE_POLICY = "volume_extension:hosts"
hosts_policies = [
policy.DocumentedRuleDefault(
name=MANAGE_POLICY,
check_str=base.RULE_ADMIN_API,
description="List or update hosts for a project.",
operations=[
{
'method': 'GET',
'path': '/os-hosts'
},
{
'method': 'PUT',
'path': '/os-hosts/{host_name}'
}
])
]
def list_rules():
return hosts_policies

38
cinder/policies/limits.py Normal file
View File

@ -0,0 +1,38 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
EXTEND_LIMIT_ATTRIBUTE_POLICY = "limits_extension:used_limits"
limits_policies = [
policy.DocumentedRuleDefault(
name=EXTEND_LIMIT_ATTRIBUTE_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Show limits with used limit attributes.",
operations=[
{
'method': 'GET',
'path': '/limits'
}
])
]
def list_rules():
return limits_policies

View File

@ -0,0 +1,38 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
GET_POOL_POLICY = "scheduler_extension:scheduler_stats:get_pools"
pools_policies = [
policy.DocumentedRuleDefault(
name=GET_POOL_POLICY,
check_str=base.RULE_ADMIN_API,
description="List all backend pools.",
operations=[
{
'method': 'GET',
'path': '/scheduler-stats/get_pools'
}
])
]
def list_rules():
return pools_policies

View File

@ -0,0 +1,83 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
GET_ALL_POLICY = "volume_extension:services:index"
UPDATE_POLICY = "volume_extension:services:update"
FAILOVER_POLICY = "volume:failover_host"
FREEZE_POLICY = "volume:freeze_host"
THAW_POLICY = "volume:thaw_host"
services_policies = [
policy.DocumentedRuleDefault(
name=GET_ALL_POLICY,
check_str=base.RULE_ADMIN_API,
description="List all services.",
operations=[
{
'method': 'GET',
'path': '/os-services'
}
]),
policy.DocumentedRuleDefault(
name=UPDATE_POLICY,
check_str=base.RULE_ADMIN_API,
description="Update service, including failover_host, thaw, freeze, "
"disable, enable, set-log and get-log actions.",
operations=[
{
'method': 'PUT',
'path': '/os-services/{action}'
}
]),
policy.DocumentedRuleDefault(
name=FREEZE_POLICY,
check_str=base.RULE_ADMIN_API,
description="Freeze a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/freeze'
}
]),
policy.DocumentedRuleDefault(
name=THAW_POLICY,
check_str=base.RULE_ADMIN_API,
description="Thaw a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/thaw'
}
]),
policy.DocumentedRuleDefault(
name=FAILOVER_POLICY,
check_str=base.RULE_ADMIN_API,
description="Failover a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/failover_host'
}
]),
]
def list_rules():
return services_policies

View File

@ -174,7 +174,7 @@ def fake_service_update(context, service_id, values):
'disabled': values['disabled']}
def fake_policy_enforce(context, action, target):
def fake_policy_authorize(context, action, target):
pass
@ -188,7 +188,7 @@ def fake_utcnow(with_timezone=False):
@mock.patch('cinder.db.service_get', fake_service_get)
@mock.patch('oslo_utils.timeutils.utcnow', fake_utcnow)
@mock.patch('cinder.db.sqlalchemy.api.service_update', fake_service_update)
@mock.patch('cinder.policy.enforce', fake_policy_enforce)
@mock.patch('cinder.policy.authorize', fake_policy_authorize)
class ServicesTest(test.TestCase):
def setUp(self):

View File

@ -47,8 +47,8 @@ class UsedLimitsTestCase(test.TestCase):
(mv.LIMITS_ADMIN_FILTER, True),
(mv.LIMITS_ADMIN_FILTER, False))
@mock.patch('cinder.quota.QUOTAS.get_project_quotas')
@mock.patch('cinder.policy.enforce')
def test_used_limits(self, ver_project, _mock_policy_enforce,
@mock.patch('cinder.policy.authorize')
def test_used_limits(self, ver_project, _mock_policy_authorize,
_mock_get_project_quotas):
version, has_project = ver_project
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
@ -77,7 +77,7 @@ class UsedLimitsTestCase(test.TestCase):
_mock_get_project_quotas.side_effect = get_project_quotas
# allow user to access used limits
_mock_policy_enforce.return_value = None
_mock_policy_authorize.return_value = True
self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute']
@ -111,7 +111,7 @@ class UsedLimitsTestCase(test.TestCase):
res = wsgi.ResponseObject(obj)
# unallow user to access used limits
_mock_policy_enforce.side_effect = exception.NotAuthorized
_mock_policy_authorize.side_effect = exception.NotAuthorized
self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute']

View File

@ -39,9 +39,6 @@
"volume:update_readonly_flag": "",
"volume:retype": "",
"volume:copy_volume_to_image": "",
"volume:failover_host": "rule:admin_api",
"volume:freeze_host": "rule:admin_api",
"volume:thaw_host": "rule:admin_api",
"volume:revert_to_snapshot": "",
"volume_extension:volume_admin_actions:reset_status": "rule:admin_api",
"volume_extension:volume_admin_actions:force_delete": "rule:admin_api",
@ -68,13 +65,11 @@
"volume_extension:volume_host_attribute": "rule:admin_api",
"volume_extension:volume_tenant_attribute": "rule:admin_api",
"volume_extension:volume_mig_status_attribute": "rule:admin_api",
"volume_extension:hosts": "rule:admin_api",
"volume_extension:services:index": "",
"volume_extension:services:update" : "rule:admin_api",
"volume_extension:volume_manage": "rule:admin_api",
"volume_extension:volume_unmanage": "rule:admin_api",
"volume_extension:list_manageable": "rule:admin_api",
"volume_extension:capabilities": "rule:admin_api",
"limits_extension:used_limits": "",
@ -115,8 +110,6 @@
"group:enable_replication": "",
"group:disable_replication": "",
"group:failover_replication": "",
"group:list_replication_targets": "",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
"group:list_replication_targets": ""
}

View File

@ -44,6 +44,7 @@ from cinder import objects
from cinder.objects import base as objects_base
from cinder.objects import fields
from cinder.policies import attachments as attachment_policy
from cinder.policies import services as svr_policy
from cinder.policies import snapshot_metadata as s_meta_policy
from cinder.policies import snapshots as snapshot_policy
import cinder.policy
@ -1843,7 +1844,7 @@ class API(base.Base):
return cluster, services
def failover(self, ctxt, host, cluster_name, secondary_id=None):
check_policy(ctxt, 'failover_host')
ctxt.authorize(svr_policy.FAILOVER_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
# TODO(geguileo): In P - Remove this version check
@ -1864,7 +1865,7 @@ class API(base.Base):
self.volume_rpcapi.failover(ctxt, services[0], secondary_id)
def freeze_host(self, ctxt, host, cluster_name):
check_policy(ctxt, 'freeze_host')
ctxt.authorize(svr_policy.FREEZE_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
expected = False
@ -1879,7 +1880,7 @@ class API(base.Base):
self.volume_rpcapi.freeze_host(ctxt, services[0])
def thaw_host(self, ctxt, host, cluster_name):
check_policy(ctxt, 'thaw_host')
ctxt.authorize(svr_policy.THAW_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
expected = True

View File

@ -46,26 +46,17 @@
"volume_extension:volume_host_attribute": "rule:admin_api",
"volume_extension:volume_tenant_attribute": "rule:admin_or_owner",
"volume_extension:volume_mig_status_attribute": "rule:admin_api",
"volume_extension:hosts": "rule:admin_api",
"volume_extension:services:index": "rule:admin_api",
"volume_extension:services:update" : "rule:admin_api",
"volume_extension:volume_manage": "rule:admin_api",
"volume_extension:volume_unmanage": "rule:admin_api",
"volume_extension:list_manageable": "rule:admin_api",
"volume_extension:capabilities": "rule:admin_api",
"volume:create_transfer": "rule:admin_or_owner",
"volume:accept_transfer": "",
"volume:delete_transfer": "rule:admin_or_owner",
"volume:get_transfer": "rule:admin_or_owner",
"volume:get_all_transfers": "rule:admin_or_owner",
"volume:failover_host": "rule:admin_api",
"volume:freeze_host": "rule:admin_api",
"volume:thaw_host": "rule:admin_api",
"consistencygroup:create" : "group:nobody",
"consistencygroup:delete": "group:nobody",
"consistencygroup:update": "group:nobody",
@ -77,6 +68,4 @@
"consistencygroup:get_cgsnapshot": "group:nobody",
"consistencygroup:get_all_cgsnapshots": "group:nobody",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
}