From c30c27fb0c94f0d956e58db506000c63e6358b9e Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Thu, 5 Jan 2017 11:41:16 -0500 Subject: [PATCH] Allows fetching of deployments from all environments. Adds new endpoint /deployments to Murano, to enable Murano Dashboard to get all deployments for all environments. This is needed in order to improve log browsing for deployments, which calls for creating a new view in which all deployments across all environments can be viewed. Also made deployment unit tests more robust. Partially-implements: blueprint improve-deployment-log-browsing Change-Id: I1b6a313af1a0c4aa57bd4e6f51da92b396b35165 --- .../articles/specification/murano-api.rst | 3 + etc/murano/policy.json | 1 + murano/api/v1/deployments.py | 46 ++++-- murano/api/v1/router.py | 4 + murano/tests/unit/api/v1/test_deployments.py | 141 +++++++++++++++++- .../deployment-list-8c2da5a5efc6dbac.yaml | 5 + 6 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/deployment-list-8c2da5a5efc6dbac.yaml diff --git a/doc/source/appendix/articles/specification/murano-api.rst b/doc/source/appendix/articles/specification/murano-api.rst index f6004ac4a..7b55249ab 100644 --- a/doc/source/appendix/articles/specification/murano-api.rst +++ b/doc/source/appendix/articles/specification/murano-api.rst @@ -679,6 +679,9 @@ Returns information about all deployments of the specified environment. +==========+====================================+======================================+ | GET | /environments//deployments | Get list of environment deployments | +----------+------------------------------------+--------------------------------------+ +| GET | /deployments | Get list of deployments for all | +| | | environments in user's project | ++----------+---------------------------------------------------------------------------+ *Response* diff --git a/etc/murano/policy.json b/etc/murano/policy.json index ac355dbc8..78fae1f14 100644 --- a/etc/murano/policy.json +++ b/etc/murano/policy.json @@ -16,6 +16,7 @@ "add_category": "rule:admin_api", "list_deployments": "rule:default", + "list_deployments_all_environments": "rule:default", "statuses_deployments": "rule:default", "list_environments": "rule:default", diff --git a/murano/api/v1/deployments.py b/murano/api/v1/deployments.py index 391e3fab0..618f8993d 100644 --- a/murano/api/v1/deployments.py +++ b/murano/api/v1/deployments.py @@ -14,6 +14,7 @@ from oslo_log import log as logging from sqlalchemy import desc +from sqlalchemy.orm import load_only from webob import exc from murano.api.v1 import request_statistics @@ -24,7 +25,7 @@ from murano.common import utils from murano.common import wsgi from murano.db import models from murano.db import session as db_session -from murano.utils import verify_env +from murano.utils import check_env LOG = logging.getLogger(__name__) @@ -33,18 +34,41 @@ API_NAME = 'Deployments' class Controller(object): @request_statistics.stats_count(API_NAME, 'Index') - @verify_env - def index(self, request, environment_id): - target = {"environment_id": environment_id} - policy.check("list_deployments", request.context, target) + def index(self, request, environment_id=None): + all_environments = environment_id is None + LOG.debug('Deployments:List ' + .format(all_environments)) + + if all_environments: + policy.check("list_deployments_all_environments", request.context) + else: + check_env(request, environment_id) + target = {"environment_id": environment_id} + policy.check("list_deployments", request.context, target) unit = db_session.get_session() - query = unit.query(models.Task) \ - .filter_by(environment_id=environment_id) \ - .order_by(desc(models.Task.created)) - result = query.all() - deployments = [set_dep_state(deployment, unit).to_dict() for deployment - in result] + + if all_environments: + query = unit.query(models.Environment) \ + .options(load_only('tenant_id')) \ + .filter_by(tenant_id=request.context.tenant) \ + .join(models.Task) \ + .order_by(desc(models.Task.created)) + result = query.all() + # The join statement above fetches the deployments into + # Environment.tasks. Iterate over that to get the deployments. + deployments = [] + for row in result: + for deployment in row.tasks: + deployment = set_dep_state(deployment, unit).to_dict() + deployments.append(deployment) + else: + query = unit.query(models.Task) \ + .filter_by(environment_id=environment_id) \ + .order_by(desc(models.Task.created)) + result = query.all() + deployments = [set_dep_state(deployment, unit).to_dict() + for deployment in result] return {'deployments': deployments} @request_statistics.stats_count(API_NAME, 'Statuses') diff --git a/murano/api/v1/router.py b/murano/api/v1/router.py index 642a28dcb..47e22ac5b 100644 --- a/murano/api/v1/router.py +++ b/murano/api/v1/router.py @@ -168,6 +168,10 @@ class API(wsgi.Router): controller=deployments_resource, action='statuses', conditions={'method': ['GET']}) + mapper.connect('/deployments', + controller=deployments_resource, + action='index', + conditions={'method': ['GET']}) sessions_resource = sessions.create_resource() mapper.connect('/environments/{environment_id}/configure', diff --git a/murano/tests/unit/api/v1/test_deployments.py b/murano/tests/unit/api/v1/test_deployments.py index 695425786..b802abfc4 100644 --- a/murano/tests/unit/api/v1/test_deployments.py +++ b/murano/tests/unit/api/v1/test_deployments.py @@ -39,25 +39,156 @@ class TestDeploymentsApi(tb.ControllerTest, tb.MuranoApiTestCase): {'create_environment': '@', 'list_deployments': '@'} ) - self.expect_policy_check('create_environment') - # Create environment + # Create an environment. + self.expect_policy_check('create_environment') request = self._post( '/environments', jsonutils.dump_as_bytes({'name': 'test_environment_1'}), **CREDENTIALS ) response_body = jsonutils.loads(request.get_response(self.api).body) - self.assertEqual(CREDENTIALS['tenant'], - response_body['tenant_id']) - + self.assertEqual(CREDENTIALS['tenant'], response_body['tenant_id']) + self.assertEqual('test_environment_1', response_body['name']) ENVIRONMENT_ID = response_body['id'] + # Verify that the environment has not yet been deployed. self.expect_policy_check('list_deployments', {'environment_id': ENVIRONMENT_ID}) result = self.deployments_controller.index(request, ENVIRONMENT_ID) self.assertEqual([], result['deployments']) + # Deploy the environment. + request = self._post('/environments/{environment_id}/configure' + .format(environment_id=ENVIRONMENT_ID), + b'', **CREDENTIALS) + response_body = jsonutils.loads(request.get_response(self.api).body) + + SESSION_ID = response_body['id'] + + request = self._post('/environments/{environment_id}/sessions/' + '{session_id}/deploy' + .format(environment_id=ENVIRONMENT_ID, + session_id=SESSION_ID), + b'', **CREDENTIALS) + result = request.get_response(self.api) + self.assertEqual('200 OK', result.status) + + # Verify that the environment was deployed. + self.expect_policy_check('list_deployments', + {'environment_id': ENVIRONMENT_ID}) + result = self.deployments_controller.index(request, ENVIRONMENT_ID) + deployment_id = result['deployments'][0]['id'] + self.assertEqual(1, len(result['deployments'])) + self.assertIsNotNone(deployment_id) + + def test_deployments_all_environments(self): + """Test list deployments for all environments. + + Create 2 environments, deploy both, and check that 2 deployments exist. + """ + CREDENTIALS = {'tenant': 'test_tenant_1', 'user': 'test_user_1'} + self._set_policy_rules( + {'create_environment': '@', + 'list_deployments_all_environments': '@'} + ) + + for count in range(2): + # Create environment. + self.expect_policy_check('create_environment') + request = self._post( + '/environments', + jsonutils.dump_as_bytes( + {'name': 'test_environment_{0}'.format(count)}), + **CREDENTIALS + ) + response_body = jsonutils.loads( + request.get_response(self.api).body) + self.assertEqual(CREDENTIALS['tenant'], response_body['tenant_id']) + self.assertEqual('test_environment_{0}'.format(count), + response_body['name']) + ENVIRONMENT_ID = response_body['id'] + + # Deploy environment. + request = self._post('/environments/{environment_id}/configure' + .format(environment_id=ENVIRONMENT_ID), + b'', **CREDENTIALS) + response_body = jsonutils.loads( + request.get_response(self.api).body) + SESSION_ID = response_body['id'] + request = self._post('/environments/{environment_id}/sessions/' + '{session_id}/deploy' + .format(environment_id=ENVIRONMENT_ID, + session_id=SESSION_ID), + b'', **CREDENTIALS) + result = request.get_response(self.api) + self.assertEqual('200 OK', result.status) + + # Check that 2 deployments exist. + self.expect_policy_check('list_deployments_all_environments') + result = self.deployments_controller.index(request, None) + self.assertEqual(2, len(result['deployments'])) + for deployment in result['deployments']: + self.assertIsNotNone(deployment) + self.assertNotEqual(result['deployments'][0], result['deployments'][1]) + + def test_deployments_all_environments_different_tenants(self): + """Test list deployments for all environments in different tenants. + + Should only return return environments for current tenant. + """ + CREDENTIALS = {'tenant': 'test_tenant_1', 'user': 'test_user_1'} + ALT_CREDENTIALS = {'tenant': 'test_tenant_2', 'user': 'test_user_2'} + self._set_policy_rules( + {'create_environment': '@', + 'list_deployments_all_environments': '@'} + ) + + deployments = [] + + # Create the first environment inside first tenant and the second + # environments inside the alternate tenant. Then deploy both. + for count, creds in enumerate([CREDENTIALS, ALT_CREDENTIALS]): + # Create each environment. + self.expect_policy_check('create_environment') + request = self._post( + '/environments', + jsonutils.dump_as_bytes( + {'name': 'test_environment_{0}'.format(count)}), + **creds + ) + response_body = jsonutils.loads( + request.get_response(self.api).body) + self.assertEqual(creds['tenant'], response_body['tenant_id']) + self.assertEqual('test_environment_{0}'.format(count), + response_body['name']) + ENVIRONMENT_ID = response_body['id'] + + # Deploy each environment. + request = self._post('/environments/{environment_id}/configure' + .format(environment_id=ENVIRONMENT_ID), + b'', **creds) + response_body = jsonutils.loads( + request.get_response(self.api).body) + SESSION_ID = response_body['id'] + request = self._post('/environments/{environment_id}/sessions/' + '{session_id}/deploy' + .format(environment_id=ENVIRONMENT_ID, + session_id=SESSION_ID), + b'', **creds) + result = request.get_response(self.api) + self.assertEqual('200 OK', result.status) + + # Check that each tenant only returns one deployment. + self.expect_policy_check('list_deployments_all_environments') + result = self.deployments_controller.index(request, None) + self.assertEqual(1, len(result['deployments'])) + deployment_id = result['deployments'][0]['id'] + self.assertIsNotNone(deployment_id) + deployments.append(deployment_id) + + self.assertNotEqual(deployments[0], deployments[1]) + def test_deployments_not_found_statuses(self): CREDENTIALS = {'tenant': 'test_tenant_1', 'user': 'test_user_1'} self._set_policy_rules( diff --git a/releasenotes/notes/deployment-list-8c2da5a5efc6dbac.yaml b/releasenotes/notes/deployment-list-8c2da5a5efc6dbac.yaml new file mode 100644 index 000000000..00a32fed1 --- /dev/null +++ b/releasenotes/notes/deployment-list-8c2da5a5efc6dbac.yaml @@ -0,0 +1,5 @@ +--- +features: + - It is now possible to make a GET request to '/deployments' endpoint. + This will result in deployments for all environments in a specific project + (tenant) being returned.