From b15769420aa56b0f43d1826a4419457e0c733dc4 Mon Sep 17 00:00:00 2001 From: Serg Melikyan Date: Fri, 24 May 2013 16:49:41 +0400 Subject: [PATCH] New way of session handling Change-Id: I3246fa50d2dac9a65f26045bcb9386660b602aa4 --- bin/murano-api | 2 +- doc/source/man/muranoapi.rst | 69 +++++++ muranoapi/api/__init__.py | 2 +- muranoapi/api/middleware/__init__.py | 2 +- muranoapi/api/v1/__init__.py | 53 +---- muranoapi/api/v1/active_directories.py | 73 +++---- muranoapi/api/v1/environments.py | 86 +++----- muranoapi/api/v1/router.py | 6 +- muranoapi/api/v1/sessions.py | 144 ++++++------- muranoapi/api/v1/webservers.py | 67 ++---- muranoapi/common/__init__.py | 2 +- muranoapi/common/config.py | 9 + muranoapi/common/service.py | 2 +- muranoapi/common/uuidutils.py | 2 +- muranoapi/db/__init__.py | 2 +- muranoapi/db/migrate_repo/manage.py | 2 +- .../versions/001_add_initial_tables.py | 2 +- .../versions/002_add_session_table.py | 2 +- .../versions/003_add_status_table.py | 2 +- .../004_add_description_column_to_session.py | 2 +- .../005_remove_obsolete_service_table.py | 2 +- .../006_add_entity_id_column_to_status.py | 2 +- .../007_add_version_column_to_environment.py | 31 +++ .../008_add_version_column_to_session.py | 31 +++ muranoapi/db/models.py | 8 +- muranoapi/db/services/__init__.py | 13 ++ muranoapi/db/services/environments.py | 180 +++++++++++++++++ muranoapi/db/services/sessions.py | 140 +++++++++++++ muranoapi/db/services/systemservices.py | 191 ++++++++++++++++++ muranoapi/utils.py | 26 ++- 30 files changed, 839 insertions(+), 316 deletions(-) create mode 100644 doc/source/man/muranoapi.rst create mode 100644 muranoapi/db/migrate_repo/versions/007_add_version_column_to_environment.py create mode 100644 muranoapi/db/migrate_repo/versions/008_add_version_column_to_session.py create mode 100644 muranoapi/db/services/__init__.py create mode 100644 muranoapi/db/services/environments.py create mode 100644 muranoapi/db/services/sessions.py create mode 100644 muranoapi/db/services/systemservices.py diff --git a/bin/murano-api b/bin/murano-api index 420f76412..189836f63 100644 --- a/bin/murano-api +++ b/bin/murano-api @@ -12,7 +12,7 @@ # 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 +# under the License. import gettext import os diff --git a/doc/source/man/muranoapi.rst b/doc/source/man/muranoapi.rst new file mode 100644 index 000000000..b2cbd1d2f --- /dev/null +++ b/doc/source/man/muranoapi.rst @@ -0,0 +1,69 @@ +.. + 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. + +========== +murano-api +========== + +----------------------------- +Murano API Server +----------------------------- + +:Author: smelikyan@mirantis.com +:Date: 2013-04-04 +:Copyright: Mirantis, Inc. +:Version: 2013.1-dev +:Manual section: 1 +:Manual group: cloud computing + + +SYNOPSIS +======== + + murano-api [options] + +DESCRIPTION +=========== + +murano-api is a server daemon that serves the Murano API + +OPTIONS +======= + + **General options** + + **-v, --verbose** + Print more verbose output + + **--config-file** + Config file used for running service + + **--bind-host=HOST** + Address of host running ``murano-api``. Defaults to `0.0.0.0`. + + **--bind-port=PORT** + Port that ``murano-api`` listens on. Defaults to `8082`. + + +FILES +===== + +* /etc/murano-api/murano-api.conf +* /etc/murano-api/murano-api-paste.conf + +SEE ALSO +======== + +* `Murano `__ diff --git a/muranoapi/api/__init__.py b/muranoapi/api/__init__.py index 8405b416f..7d93825c6 100644 --- a/muranoapi/api/__init__.py +++ b/muranoapi/api/__init__.py @@ -10,4 +10,4 @@ # 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 +# under the License. diff --git a/muranoapi/api/middleware/__init__.py b/muranoapi/api/middleware/__init__.py index 8405b416f..7d93825c6 100644 --- a/muranoapi/api/middleware/__init__.py +++ b/muranoapi/api/middleware/__init__.py @@ -10,4 +10,4 @@ # 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 +# under the License. diff --git a/muranoapi/api/v1/__init__.py b/muranoapi/api/v1/__init__.py index ca75e772d..2cb9ae17b 100644 --- a/muranoapi/api/v1/__init__.py +++ b/muranoapi/api/v1/__init__.py @@ -10,7 +10,7 @@ # 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 +# under the License. from muranoapi.db.models import Session, Environment, Status from muranoapi.db.session import get_session @@ -35,57 +35,6 @@ def save_draft(session_id, draft): session.save(unit) -def get_env_status(environment_id, session_id): - status = 'draft' - - unit = get_session() - - if not session_id: - variants = ['open', 'deploying'] - session = unit.query(Session).filter( - Session.environment_id == environment_id and - Session.state.in_(variants) - ).first() - if session: - session_id = session.id - else: - return status - - session_state = unit.query(Session).get(session_id).state - reports_count = unit.query(Status).filter_by(environment_id=environment_id, - session_id=session_id).count() - - if session_state == 'deployed': - status = 'finished' - - if session_state == 'deploying' and reports_count > 1: - status = 'pending' - - draft = get_draft(environment_id, session_id) - - if not 'services' in draft: - return 'pending' - - def get_statuses(type): - if type in draft['services']: - services = draft['services'][type] - return [get_service_status(environment_id, - session_id, - service) for service in services] - else: - return [] - - is_inprogress = filter(lambda item: item == 'inprogress', - get_statuses('activeDirectories') + - get_statuses('webServers') + - get_statuses('aspNetApps')) - - if session_state == 'deploying' and is_inprogress > 1: - status = 'inprogress' - - return status - - def get_service_status(environment_id, session_id, service): status = 'draft' diff --git a/muranoapi/api/v1/active_directories.py b/muranoapi/api/v1/active_directories.py index 0d3013bc1..284a78542 100644 --- a/muranoapi/api/v1/active_directories.py +++ b/muranoapi/api/v1/active_directories.py @@ -10,12 +10,11 @@ # 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 +# under the License. from muranoapi import utils -from muranoapi.api.v1 import save_draft, get_draft, get_service_status -from muranoapi.common import uuidutils -from muranoapi.openstack.common import wsgi, timeutils +from muranoapi.db.services.systemservices import SystemServices +from muranoapi.openstack.common import wsgi from muranoapi.openstack.common import log as logging log = logging.getLogger(__name__) @@ -23,62 +22,40 @@ log = logging.getLogger(__name__) class Controller(object): def index(self, request, environment_id): - log.debug(_('ActiveDirectory:Index '. - format(environment_id))) + log.debug(_('ActiveDirectory:Index ' + ''.format(environment_id))) - draft = prepare_draft(get_draft(environment_id, - request.context.session)) + session_id = None + if hasattr(request, 'context') and request.context.session: + session_id = request.context.session - for dc in draft['services']['activeDirectories']: - dc['status'] = get_service_status(environment_id, - request.context.session, - dc) + get = SystemServices.get_services - return {'activeDirectories': draft['services']['activeDirectories']} + services = get(environment_id, 'activeDirectories', session_id) + services = [srv.to_dict() for srv in services] + + return {'activeDirectories': services} @utils.verify_session def create(self, request, environment_id, body): - log.debug(_('ActiveDirectory:Create '. - format(environment_id, body))) + log.debug(_('ActiveDirectory:Create '.format(environment_id, body))) - draft = get_draft(session_id=request.context.session) + session_id = request.context.session + create = SystemServices.create_active_directory - active_directory = body.copy() - active_directory['id'] = uuidutils.generate_uuid() - active_directory['created'] = str(timeutils.utcnow()) - active_directory['updated'] = str(timeutils.utcnow()) - - unit_count = 0 - for unit in active_directory['units']: - unit_count += 1 - unit['id'] = uuidutils.generate_uuid() - unit['name'] = 'dc{0}'.format(unit_count) - - draft = prepare_draft(draft) - draft['services']['activeDirectories'].append(active_directory) - save_draft(request.context.session, draft) - - return active_directory + return create(body.copy(), session_id, environment_id) + @utils.verify_session def delete(self, request, environment_id, active_directory_id): - log.debug(_('ActiveDirectory:Delete '. - format(environment_id, active_directory_id))) + log.debug(_('ActiveDirectory:Delete '.format(environment_id, active_directory_id))) - draft = get_draft(session_id=request.context.session) - items = [service for service in draft['services']['activeDirectories'] - if service['id'] != active_directory_id] - draft['services']['activeDirectories'] = items - save_draft(request.context.session, draft) + session_id = request.context.session + delete = SystemServices.delete_service - -def prepare_draft(draft): - if not 'services' in draft: - draft['services'] = {} - - if not 'activeDirectories' in draft['services']: - draft['services']['activeDirectories'] = [] - - return draft + delete(active_directory_id, 'activeDirectories', session_id, + environment_id) def create_resource(): diff --git a/muranoapi/api/v1/environments.py b/muranoapi/api/v1/environments.py index 9a9d9be2c..42837df2a 100644 --- a/muranoapi/api/v1/environments.py +++ b/muranoapi/api/v1/environments.py @@ -10,16 +10,15 @@ # 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 +# under the License. -from amqplib.client_0_8 import Message -import anyjson import eventlet from webob import exc from muranoapi.common import config -from muranoapi.api.v1 import get_env_status from muranoapi.db.session import get_session from muranoapi.db.models import Environment +from muranoapi.db.services.environments import EnvironmentServices +from muranoapi.db.services.systemservices import SystemServices from muranoapi.openstack.common import wsgi from muranoapi.openstack.common import log as logging @@ -33,35 +32,18 @@ class Controller(object): def index(self, request): log.debug(_('Environments:List')) - #Only environments from same tenant as users should be shown + #Only environments from same tenant as user should be returned filters = {'tenant_id': request.context.tenant} - - session = get_session() - environments = session.query(Environment).filter_by(**filters) + environments = EnvironmentServices.get_environments_by(filters) environments = [env.to_dict() for env in environments] - for env in environments: - env['status'] = get_env_status(env['id'], request.context.session) - return {"environments": environments} def create(self, request, body): log.debug(_('Environments:Create '.format(body))) - #tagging environment by tenant_id for later checks - params = body.copy() - params['tenant_id'] = request.context.tenant - - environment = Environment() - environment.update(params) - - session = get_session() - with session.begin(): - session.add(environment) - - #saving environment as Json to itself - environment.update({"description": environment.to_dict()}) - environment.save(session) + environment = EnvironmentServices.create(body.copy(), + request.context.tenant) return environment.to_dict() @@ -76,13 +58,30 @@ class Controller(object): raise exc.HTTPUnauthorized env = environment.to_dict() - env['status'] = get_env_status(environment_id, request.context.session) + env['status'] = EnvironmentServices.get_status(env['id']) + + session_id = None + if hasattr(request, 'context') and request.context.session: + session_id = request.context.session + + #add services to env + get = SystemServices.get_service + + ad = get(environment_id, 'activeDirectories', session_id) + webServers = get(environment_id, 'webServers', session_id) + aspNetApps = get(environment_id, 'aspNetApps', session_id) + + env['services'] = { + 'activeDirectories': ad, + 'webServers': webServers, + 'aspNetApps': aspNetApps + } return env def update(self, request, environment_id, body): - log.debug(_('Environments:Update '. - format(environment_id, body))) + log.debug(_('Environments:Update '.format(environment_id, body))) session = get_session() environment = session.query(Environment).get(environment_id) @@ -99,33 +98,14 @@ class Controller(object): def delete(self, request, environment_id): log.debug(_('Environments:Delete '.format(environment_id))) - session = get_session() - environment = session.query(Environment).get(environment_id) + unit = get_session() + environment = unit.query(Environment).get(environment_id) - with session.begin(): - session.delete(environment) + if environment.tenant_id != request.context.tenant: + log.info('User is not authorized to access this tenant resources.') + raise exc.HTTPUnauthorized - #preparing data for removal from conductor - env = environment.description - env['services'] = [] - env['deleted'] = True - #Set X-Auth-Token for conductor - env['token'] = request.context.auth_token - - connection = amqp.Connection('{0}:{1}'. - format(rabbitmq.host, rabbitmq.port), - virtual_host=rabbitmq.virtual_host, - userid=rabbitmq.login, - password=rabbitmq.password, - ssl=rabbitmq.use_ssl, insist=True) - channel = connection.channel() - channel.exchange_declare('tasks', 'direct', durable=True, - auto_delete=False) - - channel.basic_publish(Message(body=anyjson.serialize(env)), 'tasks', - 'tasks') - - return None + EnvironmentServices.delete(environment_id, request.context.auth_token) def create_resource(): diff --git a/muranoapi/api/v1/router.py b/muranoapi/api/v1/router.py index 570db7aee..bc87afbb2 100644 --- a/muranoapi/api/v1/router.py +++ b/muranoapi/api/v1/router.py @@ -10,7 +10,7 @@ # 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 +# under the License. import routes from muranoapi.openstack.common import wsgi @@ -50,10 +50,6 @@ class API(wsgi.Router): conditions={'method': ['DELETE']}) sessions_resource = sessions.create_resource() - mapper.connect('/environments/{environment_id}/sessions', - controller=sessions_resource, - action='index', - conditions={'method': ['GET']}) mapper.connect('/environments/{environment_id}/configure', controller=sessions_resource, action='configure', diff --git a/muranoapi/api/v1/sessions.py b/muranoapi/api/v1/sessions.py index f92b35661..7ff7f1ea7 100644 --- a/muranoapi/api/v1/sessions.py +++ b/muranoapi/api/v1/sessions.py @@ -10,93 +10,95 @@ # 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 +# under the License. -from amqplib.client_0_8 import Message -import anyjson -import eventlet from webob import exc -from muranoapi.common import config -from muranoapi.db.models import Session, Status, Environment +from muranoapi.db.models import Session, Status from muranoapi.db.session import get_session +from muranoapi.db.services.sessions import SessionServices +from muranoapi.db.services.sessions import SessionState +from muranoapi.db.services.environments import EnvironmentServices +from muranoapi.db.services.environments import EnvironmentStatus from muranoapi.openstack.common import wsgi from muranoapi.openstack.common import log as logging -amqp = eventlet.patcher.import_patched('amqplib.client_0_8') -rabbitmq = config.CONF.rabbitmq log = logging.getLogger(__name__) class Controller(object): - def index(self, request, environment_id): - log.debug(_('Session:List '.format(environment_id))) - - filters = {'environment_id': environment_id, - 'user_id': request.context.user} - - unit = get_session() - configuration_sessions = unit.query(Session).filter_by(**filters) - - sessions = [session.to_dict() for session in configuration_sessions if - session.environment.tenant_id == request.context.tenant] - return {"sessions": sessions} - def configure(self, request, environment_id): log.debug(_('Session:Configure '.format(environment_id))) - params = {'environment_id': environment_id, - 'user_id': request.context.user, 'state': 'open'} + # no new session can be opened if environment has deploying status + env_status = EnvironmentServices.get_status(environment_id) + if env_status == EnvironmentStatus.deploying: + log.info('Could not open session for environment ,' + 'environment has deploying ' + 'status.'.format(environment_id)) + raise exc.HTTPForbidden() - session = Session() - session.update(params) - - unit = get_session() - if unit.query(Session).filter( - Session.environment_id == environment_id and - Session.state.in_(['open', 'deploying']) - ).first(): - log.info('There is already open session for this environment') - raise exc.HTTPConflict - - #create draft for apply later changes - environment = unit.query(Environment).get(environment_id) - session.description = environment.description - - with unit.begin(): - unit.add(session) + user_id = request.context.user + session = SessionServices.create(environment_id, user_id) return session.to_dict() - def show(self, request, environment_id, session_id): - log.debug(_('Session:Show '.format(environment_id, session_id))) + def show(self, request, session_id): + log.debug(_('Session:Show '.format(session_id))) unit = get_session() session = unit.query(Session).get(session_id) - if session.environment.tenant_id != request.context.tenant: - log.info('User is not authorized to access this tenant resources.') - raise exc.HTTPUnauthorized + user_id = request.context.user + if session.user_id != user_id: + log.info('User is not authorized to access ' + 'session .'.format(user_id, session_id)) + raise exc.HTTPUnauthorized() + + if not SessionServices.validate(session): + log.info('Session is invalid'.format(session_id)) + raise exc.HTTPForbidden() return session.to_dict() - def delete(self, request, environment_id, session_id): - log.debug(_('Session:Delete '.format(environment_id, session_id))) + def delete(self, request, session_id): + log.debug(_('Session:Delete '.format(session_id))) unit = get_session() session = unit.query(Session).get(session_id) - comment = 'Session object in \'deploying\' state could not be deleted' - if session.state == 'deploying': - log.info(comment) - raise exc.HTTPForbidden(comment=comment) + user_id = request.context.user + if session.user_id != user_id: + log.info('User is not authorized to access ' + 'session .'.format(user_id, session_id)) + raise exc.HTTPUnauthorized() + + if session.state == SessionState.deploying: + log.info('Session is in deploying state and ' + 'could not be deleted'.format(session_id)) + raise exc.HTTPForbidden() with unit.begin(): unit.delete(session) return None + def deploy(self, request, session_id): + log.debug(_('Session:Deploy '.format(session_id))) + + unit = get_session() + session = unit.query(Session).get(session_id) + + if not SessionServices.validate(session): + log.info('Session is invalid'.format(session_id)) + raise exc.HTTPForbidden() + + if session.state != SessionState.open: + log.info('Session is already deployed or ' + 'deployment is in progress'.format(session_id)) + raise exc.HTTPForbidden() + + SessionServices.deploy(session, request.context.auth_token) + def reports(self, request, environment_id, session_id): log.debug(_('Session:Reports '.format(environment_id, session_id))) @@ -110,11 +112,11 @@ class Controller(object): environment = unit.query(Session).get(session_id).description services = [] - if 'services' in environment and 'activeDirectories' in\ + if 'services' in environment and 'activeDirectories' in \ environment['services']: services += environment['services']['activeDirectories'] - if 'services' in environment and 'webServers' in\ + if 'services' in environment and 'webServers' in \ environment['services']: services += environment['services']['webServers'] @@ -135,38 +137,6 @@ class Controller(object): return {'reports': [status.to_dict() for status in result]} - def deploy(self, request, environment_id, session_id): - log.debug(_('Session:Deploy '.format(environment_id, session_id))) - - unit = get_session() - session = unit.query(Session).get(session_id) - - msg = _('Could not deploy session. Session is already ' - 'deployed or in deployment state') - if session.state != 'open': - log.warn(msg) - - session.state = 'deploying' - session.save(unit) - - #Set X-Auth-Token for conductor - env = session.description - env['token'] = request.context.auth_token - - connection = amqp.Connection('{0}:{1}'. - format(rabbitmq.host, rabbitmq.port), - virtual_host=rabbitmq.virtual_host, - userid=rabbitmq.login, - password=rabbitmq.password, - ssl=rabbitmq.use_ssl, insist=True) - channel = connection.channel() - channel.exchange_declare('tasks', 'direct', durable=True, - auto_delete=False) - - channel.basic_publish(Message(body=anyjson.serialize(env)), 'tasks', - 'tasks') - def create_resource(): return wsgi.Resource(Controller()) diff --git a/muranoapi/api/v1/webservers.py b/muranoapi/api/v1/webservers.py index e99655405..8f024a23a 100644 --- a/muranoapi/api/v1/webservers.py +++ b/muranoapi/api/v1/webservers.py @@ -10,12 +10,11 @@ # 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 +# under the License. from muranoapi import utils -from muranoapi.api.v1 import save_draft, get_draft, get_service_status -from muranoapi.common import uuidutils -from muranoapi.openstack.common import wsgi, timeutils +from muranoapi.db.services.systemservices import SystemServices +from muranoapi.openstack.common import wsgi from muranoapi.openstack.common import log as logging log = logging.getLogger(__name__) @@ -25,60 +24,36 @@ class Controller(object): def index(self, request, environment_id): log.debug(_('WebServer:List '.format(environment_id))) - draft = prepare_draft(get_draft(environment_id, - request.context.session)) + session_id = None + if hasattr(request, 'context') and request.context.session: + session_id = request.context.session - for dc in draft['services']['webServers']: - dc['status'] = get_service_status(environment_id, - request.context.session, dc) + get = SystemServices.get_services - return {'webServers': draft['services']['webServers']} + services = get(environment_id, 'webServers', session_id) + services = [srv.to_dict() for srv in services] + + return {'webServers': services} @utils.verify_session def create(self, request, environment_id, body): - log.debug(_('WebServer:Create '. - format(environment_id, body))) + log.debug(_('WebServer:Create '.format(environment_id, body))) - draft = get_draft(session_id=request.context.session) + session_id = request.context.session + create = SystemServices.create_web_server - webServer = body.copy() - webServer['id'] = uuidutils.generate_uuid() - webServer['created'] = str(timeutils.utcnow()) - webServer['updated'] = str(timeutils.utcnow()) - - unit_count = 0 - for unit in webServer['units']: - unit_count += 1 - unit['id'] = uuidutils.generate_uuid() - unit['name'] = webServer['name'] + '_instance_' + str(unit_count) - - draft = prepare_draft(draft) - draft['services']['webServers'].append(webServer) - save_draft(request.context.session, draft) - - return webServer + return create(body.copy(), session_id, environment_id) @utils.verify_session def delete(self, request, environment_id, web_server_id): - log.debug(_('WebServer:Delete '. - format(environment_id, web_server_id))) + log.debug(_('WebServer:Delete '.format(environment_id, web_server_id))) - draft = get_draft(session_id=request.context.session) + session_id = request.context.session + delete = SystemServices.delete_service - elements = [service for service in draft['services']['webServers'] if - service['id'] != web_server_id] - draft['services']['webServers'] = elements - save_draft(request.context.session, draft) - - -def prepare_draft(draft): - if not 'services' in draft: - draft['services'] = {} - - if not 'webServers' in draft['services']: - draft['services']['webServers'] = [] - - return draft + delete(web_server_id, 'webServers', session_id, environment_id) def create_resource(): diff --git a/muranoapi/common/__init__.py b/muranoapi/common/__init__.py index 8405b416f..7d93825c6 100644 --- a/muranoapi/common/__init__.py +++ b/muranoapi/common/__init__.py @@ -10,4 +10,4 @@ # 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 +# under the License. diff --git a/muranoapi/common/config.py b/muranoapi/common/config.py index 10901c706..7ba4adada 100644 --- a/muranoapi/common/config.py +++ b/muranoapi/common/config.py @@ -29,6 +29,7 @@ import sys from oslo.config import cfg from paste import deploy +from muranoapi.openstack.common import log from muranoapi.version import version_info as version paste_deploy_opts = [ @@ -76,6 +77,14 @@ CONF.import_opt('use_syslog', 'muranoapi.openstack.common.log') CONF.import_opt('syslog_log_facility', 'muranoapi.openstack.common.log') +cfg.set_defaults(log.log_opts, + default_log_levels=['amqplib=WARN', + 'qpid.messaging=INFO', + 'sqlalchemy=WARN', + 'keystoneclient=INFO', + 'eventlet.wsgi.server=WARN']) + + def parse_args(args=None, usage=None, default_config_files=None): CONF(args=args, project='muranoapi', diff --git a/muranoapi/common/service.py b/muranoapi/common/service.py index 0f5d0c47a..3ded3ae48 100644 --- a/muranoapi/common/service.py +++ b/muranoapi/common/service.py @@ -10,7 +10,7 @@ # 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 +# under the License. import anyjson from eventlet import patcher diff --git a/muranoapi/common/uuidutils.py b/muranoapi/common/uuidutils.py index 86b85193f..16cf60eab 100644 --- a/muranoapi/common/uuidutils.py +++ b/muranoapi/common/uuidutils.py @@ -10,7 +10,7 @@ # 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 +# under the License. import uuid diff --git a/muranoapi/db/__init__.py b/muranoapi/db/__init__.py index cb0f3b941..9ee4d343d 100644 --- a/muranoapi/db/__init__.py +++ b/muranoapi/db/__init__.py @@ -10,7 +10,7 @@ # 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 +# under the License. from oslo.config import cfg diff --git a/muranoapi/db/migrate_repo/manage.py b/muranoapi/db/migrate_repo/manage.py index cc59d86b7..51a855a49 100644 --- a/muranoapi/db/migrate_repo/manage.py +++ b/muranoapi/db/migrate_repo/manage.py @@ -10,7 +10,7 @@ # 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 +# under the License. from migrate.versioning.shell import main diff --git a/muranoapi/db/migrate_repo/versions/001_add_initial_tables.py b/muranoapi/db/migrate_repo/versions/001_add_initial_tables.py index 8f74d10ce..d2c68cb04 100644 --- a/muranoapi/db/migrate_repo/versions/001_add_initial_tables.py +++ b/muranoapi/db/migrate_repo/versions/001_add_initial_tables.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column, ForeignKey from sqlalchemy.types import String, Text, DateTime diff --git a/muranoapi/db/migrate_repo/versions/002_add_session_table.py b/muranoapi/db/migrate_repo/versions/002_add_session_table.py index cb794938e..b674e6406 100644 --- a/muranoapi/db/migrate_repo/versions/002_add_session_table.py +++ b/muranoapi/db/migrate_repo/versions/002_add_session_table.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column, ForeignKey from sqlalchemy.types import String, Text, DateTime diff --git a/muranoapi/db/migrate_repo/versions/003_add_status_table.py b/muranoapi/db/migrate_repo/versions/003_add_status_table.py index 135c0bcd5..dd96b9c3b 100644 --- a/muranoapi/db/migrate_repo/versions/003_add_status_table.py +++ b/muranoapi/db/migrate_repo/versions/003_add_status_table.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column, ForeignKey from sqlalchemy.types import String, Text, DateTime diff --git a/muranoapi/db/migrate_repo/versions/004_add_description_column_to_session.py b/muranoapi/db/migrate_repo/versions/004_add_description_column_to_session.py index 27ff7c7d4..3d62b4443 100644 --- a/muranoapi/db/migrate_repo/versions/004_add_description_column_to_session.py +++ b/muranoapi/db/migrate_repo/versions/004_add_description_column_to_session.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column from sqlalchemy.types import Text diff --git a/muranoapi/db/migrate_repo/versions/005_remove_obsolete_service_table.py b/muranoapi/db/migrate_repo/versions/005_remove_obsolete_service_table.py index 57be93da4..e37d8b6c7 100644 --- a/muranoapi/db/migrate_repo/versions/005_remove_obsolete_service_table.py +++ b/muranoapi/db/migrate_repo/versions/005_remove_obsolete_service_table.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column, ForeignKey from sqlalchemy.types import String, Text, DateTime diff --git a/muranoapi/db/migrate_repo/versions/006_add_entity_id_column_to_status.py b/muranoapi/db/migrate_repo/versions/006_add_entity_id_column_to_status.py index 2e938d1cf..3655b89b4 100644 --- a/muranoapi/db/migrate_repo/versions/006_add_entity_id_column_to_status.py +++ b/muranoapi/db/migrate_repo/versions/006_add_entity_id_column_to_status.py @@ -10,7 +10,7 @@ # 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 +# under the License. from sqlalchemy.schema import MetaData, Table, Column from sqlalchemy.types import String diff --git a/muranoapi/db/migrate_repo/versions/007_add_version_column_to_environment.py b/muranoapi/db/migrate_repo/versions/007_add_version_column_to_environment.py new file mode 100644 index 000000000..1f05922ab --- /dev/null +++ b/muranoapi/db/migrate_repo/versions/007_add_version_column_to_environment.py @@ -0,0 +1,31 @@ +# 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 sqlalchemy.schema import MetaData, Table, Column +from sqlalchemy.types import BigInteger + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + environment = Table('environment', meta, autoload=True) + version = Column('version', BigInteger, nullable=False, server_default='0') + version.create(environment) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + environment = Table('environment', meta, autoload=True) + environment.c.version.drop() diff --git a/muranoapi/db/migrate_repo/versions/008_add_version_column_to_session.py b/muranoapi/db/migrate_repo/versions/008_add_version_column_to_session.py new file mode 100644 index 000000000..ca2b26729 --- /dev/null +++ b/muranoapi/db/migrate_repo/versions/008_add_version_column_to_session.py @@ -0,0 +1,31 @@ +# 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 sqlalchemy.schema import MetaData, Table, Column +from sqlalchemy.types import BigInteger + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + session = Table('session', meta, autoload=True) + version = Column('version', BigInteger, nullable=False, server_default='0') + version.create(session) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + session = Table('session', meta, autoload=True) + session.c.version.drop() diff --git a/muranoapi/db/models.py b/muranoapi/db/models.py index f0006669b..149034429 100644 --- a/muranoapi/db/models.py +++ b/muranoapi/db/models.py @@ -51,10 +51,12 @@ class ModelBase(object): def update(self, values): """dict.update() behaviour.""" + self.updated = timeutils.utcnow() for k, v in values.iteritems(): self[k] = v def __setitem__(self, key, value): + self.updated = timeutils.utcnow() setattr(self, key, value) def __getitem__(self, key): @@ -79,8 +81,8 @@ class ModelBase(object): def to_dict(self): dictionary = self.__dict__.copy() - return dict([(k, v) for k, v in dictionary.iteritems() - if k != '_sa_instance_state']) + return dict((k, v) for k, v in dictionary.iteritems() + if k != '_sa_instance_state') class JsonBlob(TypeDecorator): @@ -100,6 +102,7 @@ class Environment(BASE, ModelBase): id = Column(String(32), primary_key=True, default=uuidutils.generate_uuid) name = Column(String(255), nullable=False) tenant_id = Column(String(32), nullable=False) + version = Column(BigInteger, nullable=False, default=0) description = Column(JsonBlob(), nullable=False, default={}) sessions = relationship("Session", backref='environment', @@ -122,6 +125,7 @@ class Session(BASE, ModelBase): user_id = Column(String(36), nullable=False) state = Column(String(36), nullable=False) description = Column(JsonBlob(), nullable=False) + version = Column(BigInteger, nullable=False, default=0) def to_dict(self): dictionary = super(Session, self).to_dict() diff --git a/muranoapi/db/services/__init__.py b/muranoapi/db/services/__init__.py new file mode 100644 index 000000000..7d93825c6 --- /dev/null +++ b/muranoapi/db/services/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/muranoapi/db/services/environments.py b/muranoapi/db/services/environments.py new file mode 100644 index 000000000..091baebd3 --- /dev/null +++ b/muranoapi/db/services/environments.py @@ -0,0 +1,180 @@ +# 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 collections import namedtuple + +from amqplib.client_0_8 import Message +import anyjson +import eventlet +from muranoapi.common import config +from muranoapi.db.models import Session, Environment +from muranoapi.db.session import get_session +from sessions import SessionServices, SessionState + + +amqp = eventlet.patcher.import_patched('amqplib.client_0_8') +rabbitmq = config.CONF.rabbitmq + +EnvironmentStatus = namedtuple('EnvironmentStatus', [ + 'ready', 'pending', 'deploying' +])( + ready='ready', pending='pending', deploying='deploying' +) + + +class EnvironmentServices(object): + @staticmethod + def get_environments_by(filters): + """ + Returns list of environments + :param filters: property filters + :return: Returns list of environments + """ + unit = get_session() + environments = unit.query(Environment).filter_by(**filters) + + for env in environments: + env['status'] = EnvironmentServices.get_status(env['id']) + + return environments + + @staticmethod + def get_status(environment_id): + """ + Environment can have one of three distinguished statuses: + + - Deploying: there is at least one session with status `deploying`; + - Pending: there is at least one session with status `open`; + - Ready: there is no sessions in status `deploying` or `open`. + + :param environment_id: Id of environment for which we checking status. + :return: Environment status + """ + #Ready: there are no sessions in status `deploying` or `open` + status = 'ready' + + #Deploying: there is at least one valid session with status `deploying` + deploying = SessionServices.get_sessions(environment_id, + SessionState.deploying) + if len(deploying) > 0: + status = 'deploying' + + #Pending: there is at least one valid session with status `open`; + open = SessionServices.get_sessions(environment_id, SessionState.open) + if len(open) > 0: + status = 'pending' + + return status + + @staticmethod + def create(environment_params, tenant_id): + #tagging environment by tenant_id for later checks + """ + Creates environment with specified params, in particular - name + :param environment_params: Dict, e.g. {'name': 'env-name'} + :param tenant_id: Tenant Id + :return: Created Environment + """ + environment_params['tenant_id'] = tenant_id + + environment = Environment() + environment.update(environment_params) + + unit = get_session() + with unit.begin(): + unit.add(environment) + + #saving environment as Json to itself + environment.update({"description": environment.to_dict()}) + environment.save(unit) + + return environment + + @staticmethod + def delete(environment_id, token): + """ + Deletes environment and notify orchestration engine about deletion + + :param environment_id: Environment that is going to be deleted + :param token: OpenStack auth token + """ + unit = get_session() + environment = unit.query(Environment).get(environment_id) + + with unit.begin(): + unit.delete(environment) + + #preparing data for removal from conductor + env = environment.description + env['services'] = {} + env['deleted'] = True + + #Set X-Auth-Token for conductor + env['token'] = token + + connection = amqp.Connection('{0}:{1}'. + format(rabbitmq.host, rabbitmq.port), + virtual_host=rabbitmq.virtual_host, + userid=rabbitmq.login, + password=rabbitmq.password, + ssl=rabbitmq.use_ssl, insist=True) + channel = connection.channel() + channel.exchange_declare('tasks', 'direct', durable=True, + auto_delete=False) + + channel.basic_publish(Message(body=anyjson.serialize(env)), 'tasks', + 'tasks') + + @staticmethod + def get_environment_description(environment_id, session_id=None): + """ + Returns environment description for specified environment. If session + is specified and not in deploying state function returns modified + environment description, otherwise returns actual environment desc. + + :param environment_id: Environment Id + :param session_id: Session Id + :return: Environment Description Object + """ + unit = get_session() + + if session_id: + session = unit.query(Session).get(session_id) + if SessionServices.validate(session): + if session.state != SessionState.deployed: + env_description = session.description + else: + env = unit.query(Environment).get(session.environment_id) + env_description = env.description + else: + env = unit.query(Environment).get(session.environment_id) + env_description = env.description + else: + env = (unit.query(Environment).get(environment_id)) + env_description = env.description + + return env_description + + @staticmethod + def save_environment_description(session_id, environment): + """ + Saves environment description to specified session + + :param session_id: Session Id + :param environment: Environment Description + """ + unit = get_session() + session = unit.query(Session).get(session_id) + + session.description = environment + session.save(unit) diff --git a/muranoapi/db/services/sessions.py b/muranoapi/db/services/sessions.py new file mode 100644 index 000000000..625c42fe1 --- /dev/null +++ b/muranoapi/db/services/sessions.py @@ -0,0 +1,140 @@ +# 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 collections import namedtuple + +from amqplib.client_0_8 import Message +import anyjson +import eventlet +from muranoapi.common import config +from muranoapi.db.models import Session, Environment +from muranoapi.db.session import get_session + + +amqp = eventlet.patcher.import_patched('amqplib.client_0_8') +rabbitmq = config.CONF.rabbitmq + +SessionState = namedtuple('SessionState', ['open', 'deploying', 'deployed'])( + open='open', deploying='deploying', deployed='deployed' +) + + +class SessionServices(object): + @staticmethod + def get_sessions(environment_id, state=None): + """ + Get list of sessions for specified environment + + :param environment_id: Environment Id + :param state: glazierapi.db.services.environments.EnvironmentStatus + :return: Sessions for specified Environment, if SessionState is + not defined all sessions for specified environment is returned. + """ + + unit = get_session() + # Here we duplicate logic for reducing calls to database + # Checks for validation is same as in validate. + environment = unit.query(Environment).get(environment_id) + + return unit.query(Session).filter( + #Get all session for this environment + Session.environment_id == environment_id, + #in this state, if state is not specified return in all states + Session.state.in_(SessionState if state is None else [state]), + #Only sessions with same version as current env version are valid + Session.version == environment.version + ).all() + + @staticmethod + def create(environment_id, user_id): + """ + Creates session object for specific environment for specified user. + + :param environment_id: Environment Id + :param user_id: User Id + :return: Created session + """ + unit = get_session() + environment = unit.query(Environment).get(environment_id) + + session = Session() + session.environment_id = environment.id + session.user_id = user_id + session.state = SessionState.open + # used for checking if other sessions was deployed before this one + session.version = environment.version + # all changes to environment is stored here, and translated to + # environment only after deployment completed + session.description = environment.description + + with unit.begin(): + unit.add(session) + + return session + + @staticmethod + def validate(session): + """ + Session is valid only if no other session for same + environment was already deployed on in deploying state, + + :param session: Session for validation + """ + + #if other session is deploying now current session is invalid + unit = get_session() + other_is_deploying = unit.query(Session).filter_by( + environment_id=session.environment_id, state=SessionState.deploying + ).count() > 0 + if session.state == SessionState.open and other_is_deploying: + return False + + #if environment version is higher then version on which current session + #is created then other session was already deployed + current_env = unit.query(Environment).get(session.environment_id) + if current_env.version > session.version: + return False + + return True + + @staticmethod + def deploy(session, token): + """ + Prepares environment for deployment and send deployment command to + orchestration engine + + :param session: session that is going to be deployed + :param token: auth token that is going to be used by orchestration + """ + unit = get_session() + + #Set X-Auth-Token for conductor + environment = session.description + environment['token'] = token + + session.state = SessionState.deploying + session.save(unit) + + connection = amqp.Connection('{0}:{1}'. + format(rabbitmq.host, rabbitmq.port), + virtual_host=rabbitmq.virtual_host, + userid=rabbitmq.login, + password=rabbitmq.password, + ssl=rabbitmq.use_ssl, insist=True) + channel = connection.channel() + channel.exchange_declare('tasks', 'direct', durable=True, + auto_delete=False) + + channel.basic_publish( + Message(body=anyjson.serialize(environment)), 'tasks', 'tasks' + ) diff --git a/muranoapi/db/services/systemservices.py b/muranoapi/db/services/systemservices.py new file mode 100644 index 000000000..1b310c7a7 --- /dev/null +++ b/muranoapi/db/services/systemservices.py @@ -0,0 +1,191 @@ +# 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. + +import eventlet +from muranoapi.common import config +from muranoapi.db.services.environments import EnvironmentServices +from muranoapi.openstack.common import uuidutils, timeutils + + +amqp = eventlet.patcher.import_patched('amqplib.client_0_8') +rabbitmq = config.CONF.rabbitmq + + +class SystemServices(object): + @staticmethod + def get_service_status(environment_id, service_id): + """ + Service can have one of three distinguished statuses: + + - Deploying: if environment has status deploying and there is at least + one orchestration engine report for this service; + - Pending: if environment has status `deploying` and there is no + report from orchestration engine about this service; + - Ready: If environment has status ready. + + :param environment_id: Service environment, we always know to which + environment service belongs to + :param service_id: Id of service for which we checking status. + :return: Service status + """ + # Now we assume that service has same status as environment. + # TODO: implement as designed and described above + + return EnvironmentServices.get_status(environment_id) + + @staticmethod + def get_services(environment_id, service_type, session_id=None): + """ + Get services of specified service_type from specified environment. + If session_id is specified session state is checked, and if session is + not deployed function returns service from the modified environment. + + :param environment_id: Environment Id + :param service_type: Service service_type, e.g. activeDirectories + :param session_id: Session Id + :return: Service Object List + """ + env_description = EnvironmentServices.get_environment_description( + environment_id, session_id) + + if not 'services' in env_description: + return [] + + if service_type in env_description['services']: + services = env_description['services'][service_type] + for service in services: + service['status'] = SystemServices.get_service_status( + environment_id, None) + return services + else: + return [] + + @staticmethod + def get_service(environment_id, service_id, session_id=None): + """ + Get services from specified environment. If session_id is specified + session state is checked, and if session is not deployed function + returns service from the modified environment. + + :param environment_id: Environment Id + :param service_id: Service Id + :param session_id: Session Id + :return: Service Object + :raise: ValueError if no services described in environment or if + service not found + """ + env_description = EnvironmentServices.get_environment_description( + environment_id, session_id) + + if not 'services' in env_description: + raise ValueError('This environment does not have services') + + services = [] + if 'activeDirectories' in env_description['services']: + services = env_description['services']['activeDirectories'] + + if 'webServers' in env_description['services']: + services += env_description['services']['webServers'] + + services = filter(lambda s: s.id == service_id, services) + + if len(services) > 0: + return services[0] + + raise ValueError('Service with specified id does not exist') + + @staticmethod + def create_active_directory(ad_params, session_id, environment_id): + """ + Creates active directory service and saves it in specified session + :param ad_params: Active Directory Params as Dict + :param session_id: Session + """ + env_description = EnvironmentServices.get_environment_description( + environment_id, session_id) + + active_directory = ad_params + active_directory['id'] = uuidutils.generate_uuid() + active_directory['created'] = str(timeutils.utcnow()) + active_directory['updated'] = str(timeutils.utcnow()) + + unit_count = 0 + for unit in active_directory['units']: + unit_count += 1 + unit['id'] = uuidutils.generate_uuid() + unit['name'] = 'dc{0}'.format(unit_count) + + if not 'services' in env_description: + env_description['services'] = {} + + if not 'activeDirectories' in env_description['services']: + env_description['services']['activeDirectories'] = [] + + env_description['services']['activeDirectories'].append( + active_directory) + EnvironmentServices.save_environment_description(session_id, + env_description) + + return active_directory + + @staticmethod + def create_web_server(ws_params, session_id, environment_id): + """ + Creates web server service and saves it in specified session + :param ws_params: Web Server Params as Dict + :param session_id: Session + """ + env_description = EnvironmentServices.get_environment_description( + environment_id, session_id) + + web_server = ws_params + web_server['id'] = uuidutils.generate_uuid() + web_server['created'] = str(timeutils.utcnow()) + web_server['updated'] = str(timeutils.utcnow()) + + unit_count = 0 + for unit in web_server['units']: + unit_count += 1 + unit['id'] = uuidutils.generate_uuid() + unit['name'] = web_server['name'] + '_instance_' + str(unit_count) + + if not 'services' in env_description: + env_description['services'] = {} + + if not 'webServers' in env_description['services']: + env_description['services']['webServers'] = [] + + env_description['services']['webServers'].append(web_server) + EnvironmentServices.save_environment_description(session_id, + env_description) + + return web_server + + @staticmethod + def delete_service(service_id, service_type, session_id, environment_id): + env_description = EnvironmentServices.get_environment_description( + environment_id, session_id) + + if not 'services' in env_description: + raise ValueError('This environment does not have services') + + services = [] + if service_type in env_description['services']: + services = env_description['services'][service_type] + + services = [srv for srv in services if srv['id'] != service_id] + env_description['services'][service_type] = services + + EnvironmentServices.save_environment_description(session_id, + env_description) diff --git a/muranoapi/utils.py b/muranoapi/utils.py index 5cba35ce9..2b5c8548d 100644 --- a/muranoapi/utils.py +++ b/muranoapi/utils.py @@ -14,6 +14,7 @@ import functools import logging +from muranoapi.db.services.sessions import SessionServices, SessionState from webob import exc from muranoapi.db.models import Session from muranoapi.db.session import get_session @@ -24,15 +25,22 @@ log = logging.getLogger(__name__) def verify_session(func): @functools.wraps(func) def __inner(self, request, *args, **kwargs): - if hasattr(request, 'context') and request.context.session: - uw = get_session().query(Session) - configuration_session = uw.get(request.context.session) + if not hasattr(request, 'context') and not request.context.session: + log.info('Session is required for this call') + raise exc.HTTPForbidden() - if configuration_session.state != 'open': - log.info('Session is already deployed') - raise exc.HTTPUnauthorized - else: - log.info('No session is supplied') - raise exc.HTTPUnauthorized + session_id = request.context.session + + unit = get_session() + session = unit.query(Session).get(session_id) + + if not SessionServices.validate(session): + log.info('Session is invalid'.format(session_id)) + raise exc.HTTPForbidden() + + if session.state == SessionState.deploying: + log.info('Session is already in ' + 'deployment state'.format(session_id)) + raise exc.HTTPForbidden() return func(self, request, *args, **kwargs) return __inner