New way of session handling

Change-Id: I3246fa50d2dac9a65f26045bcb9386660b602aa4
This commit is contained in:
Serg Melikyan 2013-05-24 16:49:41 +04:00
parent 9d13fb7b24
commit b15769420a
30 changed files with 839 additions and 316 deletions

View File

@ -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

View File

@ -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 <http://murano.mirantis.com>`__

View File

@ -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.

View File

@ -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.

View File

@ -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'

View File

@ -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 <EnvId: {0}>'.
format(environment_id)))
log.debug(_('ActiveDirectory:Index '
'<EnvId: {0}>'.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 <EnvId: {0}, Body: {1}>'.
format(environment_id, body)))
log.debug(_('ActiveDirectory:Create <EnvId: {0}, '
'Body: {1}>'.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 <EnvId: {0}, Id: {1}>'.
format(environment_id, active_directory_id)))
log.debug(_('ActiveDirectory:Delete <EnvId: {0}, '
'Id: {1}>'.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():

View File

@ -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 <Body {0}>'.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 <Id: {0}, Body: {1}>'.
format(environment_id, body)))
log.debug(_('Environments:Update <Id: {0}, '
'Body: {1}>'.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 <Id: {0}>'.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():

View File

@ -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',

View File

@ -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 <EnvId: {0}>'.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 <EnvId: {0}>'.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 <EnvId: {0}>,'
'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 <EnvId: {0}, '
'SessionId: {1}>'.format(environment_id, session_id)))
def show(self, request, session_id):
log.debug(_('Session:Show <SessionId: {0}>'.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 <UserId {0}> is not authorized to access '
'session <SessionId {1}>.'.format(user_id, session_id))
raise exc.HTTPUnauthorized()
if not SessionServices.validate(session):
log.info('Session <SessionId {0}> is invalid'.format(session_id))
raise exc.HTTPForbidden()
return session.to_dict()
def delete(self, request, environment_id, session_id):
log.debug(_('Session:Delete <EnvId: {0}, '
'SessionId: {1}>'.format(environment_id, session_id)))
def delete(self, request, session_id):
log.debug(_('Session:Delete <SessionId: {0}>'.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 <UserId {0}> is not authorized to access '
'session <SessionId {1}>.'.format(user_id, session_id))
raise exc.HTTPUnauthorized()
if session.state == SessionState.deploying:
log.info('Session <SessionId: {0}> 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 <SessionId: {0}>'.format(session_id)))
unit = get_session()
session = unit.query(Session).get(session_id)
if not SessionServices.validate(session):
log.info('Session <SessionId {0}> is invalid'.format(session_id))
raise exc.HTTPForbidden()
if session.state != SessionState.open:
log.info('Session <SessionId {0}> 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 <EnvId: {0}, '
'SessionId: {1}>'.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 <EnvId: {0}, '
'SessionId: {1}>'.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())

View File

@ -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 <EnvId: {0}>'.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 <EnvId: {0}, Body: {1}>'.
format(environment_id, body)))
log.debug(_('WebServer:Create <EnvId: {0}, '
'Body: {1}>'.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 <EnvId: {0}, Id: {1}>'.
format(environment_id, web_server_id)))
log.debug(_('WebServer:Delete <EnvId: {0}, '
'Id: {1}>'.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():

View File

@ -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.

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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.

View File

@ -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)

View File

@ -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'
)

View File

@ -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)

View File

@ -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 <SessionId {0}> is invalid'.format(session_id))
raise exc.HTTPForbidden()
if session.state == SessionState.deploying:
log.info('Session <SessionId {0}> is already in '
'deployment state'.format(session_id))
raise exc.HTTPForbidden()
return func(self, request, *args, **kwargs)
return __inner