From cafa9fed0ee2b0d4789327272d926f5d0ef2e11e Mon Sep 17 00:00:00 2001 From: Alexander Kuznetsov Date: Fri, 13 Dec 2013 20:37:49 +0400 Subject: [PATCH] Added trust for workbook runs Change-Id: Id2afa200858ce1cf9481883134d3c3d524f44cd9 --- .gitignore | 1 + etc/mistral.conf.example | 9 ++ mistral/api/controllers/v1/workbook.py | 5 +- mistral/context.py | 6 +- mistral/db/sqlalchemy/models.py | 2 + mistral/services/trusts.py | 82 +++++++++++++++++++ mistral/services/workbooks.py | 26 ++++++ .../api/v1/controllers/test_workbooks.py | 4 +- .../tests/unit/db/test_sqlalchemy_db_api.py | 8 +- mistral/{utils.py => utils/__init__.py} | 0 mistral/utils/openstack/__init__.py | 0 mistral/utils/openstack/keystone.py | 50 +++++++++++ 12 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 mistral/services/trusts.py create mode 100644 mistral/services/workbooks.py rename mistral/{utils.py => utils/__init__.py} (100%) create mode 100644 mistral/utils/openstack/__init__.py create mode 100644 mistral/utils/openstack/keystone.py diff --git a/.gitignore b/.gitignore index 736a62d3..fefb88c8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ nosetests.xml .project .pydevproject .idea +.DS_Store etc/mistral.conf tools/lintstack.head.py tools/pylint_exceptions diff --git a/etc/mistral.conf.example b/etc/mistral.conf.example index b43ac17a..66c5c4ee 100644 --- a/etc/mistral.conf.example +++ b/etc/mistral.conf.example @@ -33,3 +33,12 @@ rabbit_virtual_host = / rabbit_task_queue = tasks rabbit_user = guest rabbit_password = guest + +[keystone_authtoken] +auth_uri=http://localhost:5000/v3 +auth_host=localhost +auth_port=5000 +admin_user=admin +admin_password=password +auth_protocol=http +admin_tenant_name=admin \ No newline at end of file diff --git a/mistral/api/controllers/v1/workbook.py b/mistral/api/controllers/v1/workbook.py index 6d3a0510..5ecfdc8c 100644 --- a/mistral/api/controllers/v1/workbook.py +++ b/mistral/api/controllers/v1/workbook.py @@ -23,6 +23,7 @@ from mistral.api.controllers.v1 import workbook_definition from mistral.api.controllers.v1 import listener from mistral.api.controllers.v1 import execution from mistral.api.controllers import resource +from mistral.services import workbooks from mistral.openstack.common import log as logging from mistral.db import api as db_api @@ -45,7 +46,6 @@ class Workbooks(resource.Resource): class WorkbooksController(rest.RestController): - definition = workbook_definition.WorkbookDefinitionController() listeners = listener.ListenersController() executions = execution.ExecutionsController() @@ -72,7 +72,8 @@ class WorkbooksController(rest.RestController): def post(self, workbook): LOG.debug("Create workbook [workbook=%s]" % workbook) - return Workbook.from_dict(db_api.workbook_create(workbook.to_dict())) + wb = workbooks.create_workbook(workbook.to_dict()) + return Workbook.from_dict(wb) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204) def delete(self, name): diff --git a/mistral/context.py b/mistral/context.py index 71b3db3b..7e912817 100644 --- a/mistral/context.py +++ b/mistral/context.py @@ -56,7 +56,6 @@ class BaseContext(object): class MistralContext(BaseContext): - _elements = set([ "user_id", "project_id", @@ -64,6 +63,7 @@ class MistralContext(BaseContext): "service_catalog", "user_name", "project_name", + "roles", "is_admin", ]) @@ -127,12 +127,12 @@ def context_from_headers(headers): auth_token=headers.get('X-Auth-Token'), service_catalog=headers.get('X-Service-Catalog'), user_name=headers.get('X-User-Name'), - project_name=headers.get('X-Project-Name') + project_name=headers.get('X-Project-Name'), + roles=headers.get('X-Roles', "").split(",") ) class ContextHook(PecanHook): - def before(self, state): request_ctx = context_from_headers(state.request.headers).to_dict() set_ctx(request_ctx) diff --git a/mistral/db/sqlalchemy/models.py b/mistral/db/sqlalchemy/models.py index c66a6e2f..ee915cb4 100644 --- a/mistral/db/sqlalchemy/models.py +++ b/mistral/db/sqlalchemy/models.py @@ -75,6 +75,8 @@ class Workbook(mb.MistralBase): description = sa.Column(sa.String()) tags = sa.Column(st.JsonListType()) scope = sa.Column(sa.String()) + project_id = sa.Column(sa.String()) + trust_id = sa.Column(sa.String()) class Task(mb.MistralBase): diff --git a/mistral/services/trusts.py b/mistral/services/trusts.py new file mode 100644 index 00000000..31eb59da --- /dev/null +++ b/mistral/services/trusts.py @@ -0,0 +1,82 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# 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.config import cfg + +from mistral.utils.openstack import keystone +from mistral import context +from mistral.db import api as db_api + + +CONF = cfg.CONF + + +def create_trust(workbook): + client = keystone.client() + + ctx = context.current() + + admin_user = CONF.keystone_authtoken.admin_user + admin_password = CONF.keystone_authtoken.admin_password + admin_tenant_name = CONF.keystone_authtoken.admin_tenant_name + + trustee_id = keystone.client_for_trusts( + admin_user, + admin_password, + project_name=admin_tenant_name).user_id + + trust = client.trusts.create(trustor_user=client.user_id, + trustee_user=trustee_id, + impersonation=True, + role_names=ctx['roles'], + project=ctx['project_id']) + + return db_api.workbook_update(workbook['name'], + {'trust_id': trust.id, + 'project_id': ctx['project_id']}) + + +def create_context(workbook): + if not workbook.trust_id: + return + + admin_user = CONF.keystone_authtoken.admin_user + admin_password = CONF.keystone_authtoken.admin_password + + client = keystone.client_for_trusts( + admin_user, + admin_password, + trust_id=workbook['trust_id'], + project_id=workbook['project_id']) + + return context.MistralContext( + user_id=client.user_id, + project_id=workbook['project_id'], + auth_token=client.auth_token + ) + + +def delete_trust(workbook): + if workbook.trust_id: + return + + admin_user = CONF.keystone_authtoken.admin_user + admin_password = CONF.keystone_authtoken.admin_password + + keystone_client = keystone.client_for_trusts( + admin_user, + admin_password, + workbook.trust_id) + keystone_client.trusts.delete(workbook.trust_id) diff --git a/mistral/services/workbooks.py b/mistral/services/workbooks.py new file mode 100644 index 00000000..eec497cc --- /dev/null +++ b/mistral/services/workbooks.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# 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 mistral.db import api as db_api +from mistral.services import trusts + + +def create_workbook(values): + workbook = db_api.workbook_create(values) + workbook = trusts.create_trust(workbook) + ##TODO(akuznetsov) filter fields + ##TODO(akuznetsov) create events + + return workbook diff --git a/mistral/tests/api/v1/controllers/test_workbooks.py b/mistral/tests/api/v1/controllers/test_workbooks.py index 8a8bf98d..dadab0b0 100644 --- a/mistral/tests/api/v1/controllers/test_workbooks.py +++ b/mistral/tests/api/v1/controllers/test_workbooks.py @@ -67,8 +67,10 @@ class TestWorkbooksController(base.FunctionalTest): self.assertEqual(resp.status_int, 200) self.assertDictEqual(updated_workbook, resp.json) - def test_post(self): + @mock.patch("mistral.services.trusts.create_trust") + def test_post(self, create_trust): db_api.workbook_create = mock.MagicMock(return_value=WORKBOOKS[0]) + create_trust.return_value = WORKBOOKS[0] resp = self.app.post_json('/v1/workbooks', WORKBOOKS[0]) diff --git a/mistral/tests/unit/db/test_sqlalchemy_db_api.py b/mistral/tests/unit/db/test_sqlalchemy_db_api.py index 45b2df00..940071a2 100644 --- a/mistral/tests/unit/db/test_sqlalchemy_db_api.py +++ b/mistral/tests/unit/db/test_sqlalchemy_db_api.py @@ -79,7 +79,9 @@ WORKBOOKS = [ "definition": u'empty', "tags": [u'mc'], "scope": u'public', - "updated_at": None + "updated_at": None, + "project_id": '123', + "trust_id": '1234' }, { "id": u'2', @@ -88,7 +90,9 @@ WORKBOOKS = [ "definition": u'empty', "tags": [u'mc'], "scope": u'public', - "updated_at": None + "updated_at": None, + "project_id": '1233', + "trust_id": '12345' }, ] diff --git a/mistral/utils.py b/mistral/utils/__init__.py similarity index 100% rename from mistral/utils.py rename to mistral/utils/__init__.py diff --git a/mistral/utils/openstack/__init__.py b/mistral/utils/openstack/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mistral/utils/openstack/keystone.py b/mistral/utils/openstack/keystone.py new file mode 100644 index 00000000..703e421f --- /dev/null +++ b/mistral/utils/openstack/keystone.py @@ -0,0 +1,50 @@ +# Copyright (c) 2013 Mirantis Inc. +# +# 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 keystoneclient.v3 import client as keystone_client +from oslo.config import cfg + +from mistral import context + +CONF = cfg.CONF + + +def client(): + ctx = context.current() + auth_url = CONF.keystone_authtoken.auth_uri + + keystone = keystone_client.Client(username=ctx['user_name'], + token=ctx['auth_token'], + tenant_id=ctx['project_id'], + auth_url=auth_url) + keystone.management_url = auth_url + return keystone + + +def client_for_trusts(username, password, + project_name=None, + trust_id=None, + project_id=None): + auth_url = CONF.keystone_authtoken.auth_uri + + keystone = keystone_client.Client(username=username, + password=password, + tenant_name=project_name, + tenant_id=project_id, + auth_url=auth_url, + trust_id=trust_id) + + keystone.management_url = auth_url + return keystone