From 198fce72e5baa9450fc26f55c16dc6c0bfe3d5d0 Mon Sep 17 00:00:00 2001 From: Feilong Wang Date: Thu, 4 Jan 2018 10:33:57 +1300 Subject: [PATCH] Support accessing all clusters/templates across projects As an admin user, I'd like to access all clusters or templates across all projects for operation purpose. Similar function is supported by most of the other services, like Nova, Neutron, Cinder, Heat, etc. Related-Bug: #1740982 Change-Id: Icaba09de79a3452286fb60fee80a53430317cba0 --- magnum/api/controllers/v1/cluster.py | 35 ++++++++++++++++++ magnum/api/controllers/v1/cluster_template.py | 37 +++++++++++++++++++ magnum/common/policies/cluster.py | 34 +++++++++++++++++ magnum/common/policies/cluster_template.py | 35 ++++++++++++++++++ .../unit/api/controllers/v1/test_cluster.py | 24 ++++++++++++ .../controllers/v1/test_cluster_template.py | 23 ++++++++++++ ...ll-tenants-for-admin-a042f5c520d35837.yaml | 5 +++ 7 files changed, 193 insertions(+) create mode 100644 releasenotes/notes/support-all-tenants-for-admin-a042f5c520d35837.yaml diff --git a/magnum/api/controllers/v1/cluster.py b/magnum/api/controllers/v1/cluster.py index 06a7061c22..eb7d5bc636 100755 --- a/magnum/api/controllers/v1/cluster.py +++ b/magnum/api/controllers/v1/cluster.py @@ -147,6 +147,12 @@ class Cluster(base.APIBase): container_version = wsme.wsattr(wtypes.text, readonly=True) """Version of the container software. Example: docker version.""" + project_id = wsme.wsattr(wtypes.text, readonly=True) + """Project id of the cluster belongs to""" + + user_id = wsme.wsattr(wtypes.text, readonly=True) + """User id of the cluster belongs to""" + node_addresses = wsme.wsattr([wtypes.text], readonly=True) """IP addresses of cluster slave nodes""" @@ -272,6 +278,23 @@ class ClustersController(base.Controller): sort_key, sort_dir, expand=False, resource_url=None): + context = pecan.request.context + if context.is_admin: + if expand: + policy.enforce(context, "cluster:detail_all_projects", + action="cluster:detail_all_projects") + else: + policy.enforce(context, "cluster:get_all_all_projects", + action="cluster:get_all_all_projects") + # TODO(flwang): Instead of asking an extra 'all_project's + # parameter, currently the design is allowing admin user to list + # all clusters from all projects. But the all_tenants is one of + # the condition to do project filter in DB API. And it's also used + # by periodic tasks. So the could be removed in the future and + # a new parameter 'project_id' would be added so that admin user + # can list clusters for a particular project. + context.all_tenants = True + limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) @@ -362,6 +385,18 @@ class ClustersController(base.Controller): :param cluster_ident: UUID or logical name of the Cluster. """ context = pecan.request.context + if context.is_admin: + policy.enforce(context, "cluster:get_one_all_projects", + action="cluster:get_one_all_projects") + # TODO(flwang): Instead of asking an extra 'all_project's + # parameter, currently the design is allowing admin user to list + # all clusters from all projects. But the all_tenants is one of + # the condition to do project filter in DB API. And it's also used + # by periodic tasks. So the could be removed in the future and + # a new parameter 'project_id' would be added so that admin user + # can list clusters for a particular project. + context.all_tenants = True + cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:get', cluster.as_dict(), action='cluster:get') diff --git a/magnum/api/controllers/v1/cluster_template.py b/magnum/api/controllers/v1/cluster_template.py index fe0d942761..d85c43d4bc 100644 --- a/magnum/api/controllers/v1/cluster_template.py +++ b/magnum/api/controllers/v1/cluster_template.py @@ -139,6 +139,12 @@ class ClusterTemplate(base.APIBase): floating_ip_enabled = wsme.wsattr(types.boolean, default=True) """Indicates whether created clusters should have a floating ip or not.""" + project_id = wsme.wsattr(wtypes.text, readonly=True) + """Project id of the cluster belongs to""" + + user_id = wsme.wsattr(wtypes.text, readonly=True) + """User id of the cluster belongs to""" + def __init__(self, **kwargs): self.fields = [] for field in objects.ClusterTemplate.fields: @@ -249,6 +255,23 @@ class ClusterTemplatesController(base.Controller): sort_key, sort_dir, resource_url=None): + context = pecan.request.context + if context.is_admin: + if resource_url == '/'.join(['clustertemplates', 'detail']): + policy.enforce(context, "clustertemplate:detail_all_projects", + action="clustertemplate:detail_all_projects") + else: + policy.enforce(context, "clustertemplate:get_all_all_projects", + action="clustertemplate:get_all_all_projects") + # TODO(flwang): Instead of asking an extra 'all_project's + # parameter, currently the design is allowing admin user to list + # all clusters from all projects. But the all_tenants is one of + # the condition to do project filter in DB API. And it's also used + # by periodic tasks. So the could be removed in the future and + # a new parameter 'project_id' would be added so that admin user + # can list clusters for a particular project. + context.all_tenants = True + limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) @@ -317,8 +340,22 @@ class ClusterTemplatesController(base.Controller): ClusterTemplate. """ context = pecan.request.context + + if context.is_admin: + policy.enforce(context, "clustertemplate:get_one_all_projects", + action="clustertemplate:get_one_all_projects") + # TODO(flwang): Instead of asking an extra 'all_project's + # parameter, currently the design is allowing admin user to list + # all clusters from all projects. But the all_tenants is one of + # the condition to do project filter in DB API. And it's also used + # by periodic tasks. So the could be removed in the future and + # a new parameter 'project_id' would be added so that admin user + # can list clusters for a particular project. + context.all_tenants = True + cluster_template = api_utils.get_resource('ClusterTemplate', cluster_template_ident) + if not cluster_template.public: policy.enforce(context, 'clustertemplate:get', cluster_template.as_dict(), diff --git a/magnum/common/policies/cluster.py b/magnum/common/policies/cluster.py index 84eff7db0a..70cd6cb3f6 100644 --- a/magnum/common/policies/cluster.py +++ b/magnum/common/policies/cluster.py @@ -51,6 +51,17 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER % 'detail_all_projects', + check_str=base.RULE_ADMIN_API, + description='Retrieve a list of clusters with detail across projects.', + operations=[ + { + 'path': '/v1/clusters', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER % 'get', check_str=base.RULE_DENY_CLUSTER_USER, @@ -62,6 +73,18 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER % 'get_one_all_projects', + check_str=base.RULE_ADMIN_API, + description=('Retrieve information about the given cluster across ' + 'projects.'), + operations=[ + { + 'path': '/v1/clusters/{cluster_ident}', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER % 'get_all', check_str=base.RULE_DENY_CLUSTER_USER, @@ -73,6 +96,17 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER % 'get_all_all_projects', + check_str=base.RULE_ADMIN_API, + description='Retrieve a list of all clusters across projects.', + operations=[ + { + 'path': '/v1/clusters/', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER % 'update', check_str=base.RULE_DENY_CLUSTER_USER, diff --git a/magnum/common/policies/cluster_template.py b/magnum/common/policies/cluster_template.py index 027063796d..eec530f280 100644 --- a/magnum/common/policies/cluster_template.py +++ b/magnum/common/policies/cluster_template.py @@ -40,6 +40,18 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER_TEMPLATE % 'detail_all_projects', + check_str=base.RULE_ADMIN_API, + description=('Retrieve a list of cluster templates with detail across ' + 'projects.'), + operations=[ + { + 'path': '/v1/clustertemplates', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER_TEMPLATE % 'detail', check_str=base.RULE_DENY_CLUSTER_USER, @@ -62,6 +74,18 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER_TEMPLATE % 'get_one_all_projects', + check_str=base.RULE_ADMIN_API, + description=('Retrieve information about the given cluster template ' + 'across project.'), + operations=[ + { + 'path': '/v1/clustertemplate/{clustertemplate_ident}', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER_TEMPLATE % 'get_all', check_str=base.RULE_DENY_CLUSTER_USER, @@ -73,6 +97,17 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=CLUSTER_TEMPLATE % 'get_all_all_projects', + check_str=base.RULE_ADMIN_API, + description='Retrieve a list of cluster templates across projects.', + operations=[ + { + 'path': '/v1/clustertemplates', + 'method': 'GET' + } + ] + ), policy.DocumentedRuleDefault( name=CLUSTER_TEMPLATE % 'update', check_str=base.RULE_DENY_CLUSTER_USER, diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster.py b/magnum/tests/unit/api/controllers/v1/test_cluster.py index fb015a6ad6..391e74c038 100644 --- a/magnum/tests/unit/api/controllers/v1/test_cluster.py +++ b/magnum/tests/unit/api/controllers/v1/test_cluster.py @@ -144,6 +144,17 @@ class TestListCluster(api_base.FunctionalTest): self.assertEqual('application/json', response.content_type) self.assertTrue(response.json['errors']) + @mock.patch("magnum.common.policy.enforce") + @mock.patch("magnum.common.context.make_context") + def test_get_one_by_uuid_admin(self, mock_context, mock_policy): + temp_uuid = uuidutils.generate_uuid() + obj_utils.create_test_cluster(self.context, uuid=temp_uuid, + project_id=temp_uuid) + self.context.is_admin = True + response = self.get_json( + '/clusters/%s' % temp_uuid) + self.assertEqual(temp_uuid, response['uuid']) + def test_get_one_by_name_multiple_cluster(self): obj_utils.create_test_cluster(self.context, name='test_cluster', uuid=uuidutils.generate_uuid()) @@ -169,6 +180,19 @@ class TestListCluster(api_base.FunctionalTest): self.assertEqual(cluster_list[-1].uuid, response['clusters'][0]['uuid']) + @mock.patch("magnum.common.policy.enforce") + @mock.patch("magnum.common.context.make_context") + def test_get_all_with_all_projects(self, mock_context, mock_policy): + for id_ in range(4): + temp_uuid = uuidutils.generate_uuid() + obj_utils.create_test_cluster(self.context, id=id_, + uuid=temp_uuid, + project_id=id_) + + self.context.is_admin = True + response = self.get_json('/clusters') + self.assertEqual(4, len(response['clusters'])) + def test_detail(self): cluster = obj_utils.create_test_cluster(self.context) response = self.get_json('/clusters/detail') diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster_template.py b/magnum/tests/unit/api/controllers/v1/test_cluster_template.py index fdc0af6fdb..a7c0b4161e 100644 --- a/magnum/tests/unit/api/controllers/v1/test_cluster_template.py +++ b/magnum/tests/unit/api/controllers/v1/test_cluster_template.py @@ -114,6 +114,17 @@ class TestListClusterTemplate(api_base.FunctionalTest): self.assertEqual('application/json', response.content_type) self.assertTrue(response.json['errors']) + @mock.patch("magnum.common.policy.enforce") + @mock.patch("magnum.common.context.make_context") + def test_get_one_by_uuid_admin(self, mock_context, mock_policy): + temp_uuid = uuidutils.generate_uuid() + obj_utils.create_test_cluster_template(self.context, uuid=temp_uuid, + project_id=temp_uuid) + self.context.is_admin = True + response = self.get_json( + '/clustertemplates/%s' % temp_uuid) + self.assertEqual(temp_uuid, response['uuid']) + def test_get_one_by_name_multiple_cluster_template(self): obj_utils.create_test_cluster_template( self.context, name='test_clustertemplate', @@ -142,6 +153,18 @@ class TestListClusterTemplate(api_base.FunctionalTest): self.assertEqual(bm_list[-1].uuid, response['clustertemplates'][0]['uuid']) + @mock.patch("magnum.common.policy.enforce") + @mock.patch("magnum.common.context.make_context") + def test_get_all_with_all_projects(self, mock_context, mock_policy): + for id_ in range(4): + obj_utils.create_test_cluster_template( + self.context, id=id_, project_id=id_, + uuid=uuidutils.generate_uuid()) + + self.context.is_admin = True + response = self.get_json('/clustertemplates') + self.assertEqual(4, len(response['clustertemplates'])) + def test_detail(self): cluster_template = obj_utils.create_test_cluster_template(self.context) response = self.get_json('/clustertemplates/detail') diff --git a/releasenotes/notes/support-all-tenants-for-admin-a042f5c520d35837.yaml b/releasenotes/notes/support-all-tenants-for-admin-a042f5c520d35837.yaml new file mode 100644 index 0000000000..bc0c82799f --- /dev/null +++ b/releasenotes/notes/support-all-tenants-for-admin-a042f5c520d35837.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Now admin user can access all clusters across projects. +