Cloud Foundry Service Broker API initial commit
In this commit we added new murano service called murano-cfapi. It's Cloud Foundry Service Broker API implementation for OpenStack. * add new entrypoint for murano-cfapi * add provision/deprovision operations for Cloud Foundry. partial-implement: bp cloudfoundry-api-support Change-Id: I5cdb612774f5e46ec7fc787f961e5c7e16ed8501
This commit is contained in:
parent
788cd5aa7a
commit
5e3b37aac8
@ -1,3 +1,6 @@
|
||||
[pipeline:cloudfoundry]
|
||||
pipeline = cloudfoundryapi
|
||||
|
||||
[pipeline:murano]
|
||||
pipeline = request_id versionnegotiation faultwrap authtoken context rootapp
|
||||
|
||||
@ -20,6 +23,9 @@ paste.app_factory = murano.api.versions:create_resource
|
||||
[app:apiv1app]
|
||||
paste.app_factory = murano.api.v1.router:API.factory
|
||||
|
||||
[app:cloudfoundryapi]
|
||||
paste.app_factory = murano.api.v1.cloudfoundry.router:API.factory
|
||||
|
||||
[filter:versionnegotiation]
|
||||
paste.filter_factory = murano.api.middleware.version_negotiation:VersionNegotiationFilter.factory
|
||||
|
||||
|
0
murano/api/v1/cloudfoundry/__init__.py
Normal file
0
murano/api/v1/cloudfoundry/__init__.py
Normal file
30
murano/api/v1/cloudfoundry/auth.py
Normal file
30
murano/api/v1/cloudfoundry/auth.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.v3 import client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def authenticate(user, password, tenant=None):
|
||||
project_name = tenant or CONF.cfapi.tenant
|
||||
keystone = client.Client(username=user,
|
||||
password=password,
|
||||
project_name=project_name,
|
||||
auth_url=CONF.cfapi.auth_url.replace(
|
||||
'v2.0', 'v3'))
|
||||
return keystone
|
249
murano/api/v1/cloudfoundry/cfapi.py
Normal file
249
murano/api/v1/cloudfoundry/cfapi.py
Normal file
@ -0,0 +1,249 @@
|
||||
# Copyright (c) 2015 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 base64
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import muranoclient.client as client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from webob import exc
|
||||
|
||||
from murano.api.v1.cloudfoundry import auth as keystone_auth
|
||||
from murano.common.i18n import _LI, _LW
|
||||
from murano.common import wsgi
|
||||
from murano import context
|
||||
from murano.db.catalog import api as db_api
|
||||
from murano.db.services import cf_connections as db_cf
|
||||
|
||||
|
||||
cfapi_opts = [
|
||||
cfg.StrOpt('tenant', default='admin',
|
||||
help=('Tenant for service broker')),
|
||||
cfg.StrOpt('bind_host', default='localhost',
|
||||
help=('host for service broker')),
|
||||
cfg.StrOpt('bind_port', default='8083',
|
||||
help=('host for service broker')),
|
||||
cfg.StrOpt('auth_url', default='localhost:5000/v2.0')]
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(cfapi_opts, group='cfapi')
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""WSGI controller for application catalog resource in Murano v1 API"""
|
||||
|
||||
def _package_to_service(self, package):
|
||||
srv = {}
|
||||
srv['id'] = package.id
|
||||
srv['name'] = package.name
|
||||
srv['description'] = package.description
|
||||
srv['bindable'] = True
|
||||
srv['tags'] = []
|
||||
for tag in package.tags:
|
||||
srv['tags'].append(tag.name)
|
||||
plan = {'id': package.id + '-1',
|
||||
'name': 'default',
|
||||
'description': 'Default plan for the service {name}'.format(
|
||||
name=package.name)}
|
||||
srv['plans'] = [plan]
|
||||
return srv
|
||||
|
||||
def _check_auth(self, req, tenant=None):
|
||||
auth = req.headers.get('Authorization', None)
|
||||
if auth is None:
|
||||
raise exc.HTTPUnauthorized(explanation='Bad credentials')
|
||||
|
||||
auth_info = auth.split(' ')[1]
|
||||
auth_decoded = base64.b64decode(auth_info)
|
||||
user = auth_decoded.split(':')[0]
|
||||
password = auth_decoded.split(':')[1]
|
||||
if tenant:
|
||||
keystone = keystone_auth.authenticate(user, password, tenant)
|
||||
else:
|
||||
keystone = keystone_auth.authenticate(user, password)
|
||||
return (user, password, keystone)
|
||||
|
||||
def _make_service(self, name, package, plan_id):
|
||||
id = uuid.uuid4().hex
|
||||
|
||||
return {"name": name,
|
||||
"?": {plan_id: {"name": package.name},
|
||||
"type": package.fully_qualified_name,
|
||||
"id": id}}
|
||||
|
||||
def list(self, req):
|
||||
user, passwd, keystone = self._check_auth(req)
|
||||
# Once we get here we were authorized by keystone
|
||||
token = keystone.auth_token
|
||||
|
||||
ctx = context.RequestContext(user=user, tenant='', auth_token=token)
|
||||
|
||||
packages = db_api.package_search({'type': 'application'}, ctx,
|
||||
catalog=True)
|
||||
services = []
|
||||
for package in packages:
|
||||
services.append(self._package_to_service(package))
|
||||
|
||||
resp = {'services': services}
|
||||
|
||||
return resp
|
||||
|
||||
def provision(self, req, body, instance_id):
|
||||
"""Here is the example of request body given us from Cloud Foundry:
|
||||
|
||||
{
|
||||
"service_id": "service-guid-here",
|
||||
"plan_id": "plan-guid-here",
|
||||
"organization_guid": "org-guid-here",
|
||||
"space_guid": "space-guid-here",
|
||||
"parameters": {"param1": "value1",
|
||||
"param2": "value2"}
|
||||
}
|
||||
"""
|
||||
data = json.loads(req.body)
|
||||
space_guid = data['space_guid']
|
||||
org_guid = data['organization_guid']
|
||||
plan_id = data['plan_id']
|
||||
service_id = data['service_id']
|
||||
parameters = data['parameters']
|
||||
self.current_session = None
|
||||
|
||||
# Here we'll take an entry for CF org and space from db. If we
|
||||
# don't have any entries we will create it from scratch.
|
||||
try:
|
||||
tenant = db_cf.get_tenant_for_org(org_guid)
|
||||
except AttributeError:
|
||||
# FIXME(Kezar): need to find better way to get tenant
|
||||
tenant = CONF.cfapi.tenant
|
||||
db_cf.set_tenant_for_org(org_guid, tenant)
|
||||
LOG.info(_LI("Cloud Foundry {org_id} mapped to tenant "
|
||||
"{tenant_name}").format(org_id=org_guid,
|
||||
tenant_name=tenant))
|
||||
|
||||
# Now as we have all parameters we can try to auth user in actual
|
||||
# tenant
|
||||
|
||||
user, passwd, keystone = self._check_auth(req, tenant)
|
||||
# Once we get here we were authorized by keystone
|
||||
token = keystone.auth_token
|
||||
m_cli = muranoclient(token)
|
||||
try:
|
||||
environment_id = db_cf.get_environment_for_space(space_guid)
|
||||
except AttributeError:
|
||||
body = {'name': 'my_{uuid}'.format(uuid=uuid.uuid4().hex)}
|
||||
env = m_cli.environments.create(body)
|
||||
environment_id = env.id
|
||||
db_cf.set_environment_for_space(space_guid, environment_id)
|
||||
LOG.info(_LI("Cloud Foundry {space_id} mapped to {environment_id}")
|
||||
.format(space_id=space_guid,
|
||||
environment_id=environment_id))
|
||||
|
||||
LOG.debug('Auth: %s' % keystone.auth_ref)
|
||||
tenant_id = keystone.project_id
|
||||
ctx = context.RequestContext(user=user, tenant=tenant_id)
|
||||
|
||||
package = db_api.package_get(service_id, ctx)
|
||||
LOG.debug('Adding service {name}'.format(name=package.name))
|
||||
|
||||
service = self._make_service(space_guid, package, plan_id)
|
||||
db_cf.set_instance_for_service(instance_id, service['?']['id'],
|
||||
environment_id, tenant)
|
||||
# NOTE(Kezar): Here we are going through JSON and add ids where
|
||||
# it's necessary
|
||||
params = [parameters]
|
||||
while params:
|
||||
a = params.pop()
|
||||
for k, v in a.iteritems():
|
||||
if isinstance(v, dict):
|
||||
params.append(v)
|
||||
if k == '?':
|
||||
v['id'] = uuid.uuid4().hex
|
||||
service.update(parameters)
|
||||
# Now we need to obtain session to modify the env
|
||||
session_id = create_session(m_cli, environment_id)
|
||||
m_cli.services.post(environment_id,
|
||||
path='/',
|
||||
data=service,
|
||||
session_id=session_id)
|
||||
m_cli.sessions.deploy(environment_id, session_id)
|
||||
self.current_session = session_id
|
||||
return {}
|
||||
|
||||
def deprovision(self, req, instance_id):
|
||||
service = db_cf.get_service_for_instance(instance_id)
|
||||
if not service:
|
||||
return {}
|
||||
|
||||
service_id = service.service_id
|
||||
environment_id = service.environment_id
|
||||
tenant = service.tenant
|
||||
user, passwd, keystone = self._check_auth(req, tenant)
|
||||
# Once we get here we were authorized by keystone
|
||||
token = keystone.auth_token
|
||||
m_cli = muranoclient(token)
|
||||
|
||||
try:
|
||||
session_id = create_session(m_cli, environment_id)
|
||||
except exc.HTTPForbidden:
|
||||
# FIXME(Kezar): this is a temporary solution, should be replaced
|
||||
# with 'incomplete' response for Cloud Foudry as soon as we will
|
||||
# know which is right format for it.
|
||||
LOG.warning(_LW("Can't create new session. Please remove service "
|
||||
"manually in environment {0}")
|
||||
.format(environment_id))
|
||||
return {}
|
||||
|
||||
m_cli.services.delete(environment_id, '/' + service_id, session_id)
|
||||
m_cli.sessions.deploy(environment_id, session_id)
|
||||
return {}
|
||||
|
||||
def bind(self, req, instance_id, id):
|
||||
pass
|
||||
|
||||
def unbind(self, req, instance_id, id):
|
||||
pass
|
||||
|
||||
def get_last_operation(self):
|
||||
"""Not implemented functionality
|
||||
|
||||
For some reason it's difficult to provide a valid JSON with the
|
||||
response code which is needed for our broker to be true asynchronous.
|
||||
In that case last_operation API call is not supported.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def muranoclient(token_id):
|
||||
endpoint = "http://{murano_host}:{murano_port}".format(
|
||||
murano_host=CONF.bind_host, murano_port=CONF.bind_port)
|
||||
insecure = False
|
||||
|
||||
LOG.debug('murano client created. Murano::Client <Url: {endpoint}'.format(
|
||||
endpoint=endpoint))
|
||||
|
||||
return client.Client(1, endpoint=endpoint, token=token_id,
|
||||
insecure=insecure)
|
||||
|
||||
|
||||
def create_session(client, environment_id):
|
||||
id = client.sessions.configure(environment_id).id
|
||||
return id
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(Controller())
|
45
murano/api/v1/cloudfoundry/router.py
Normal file
45
murano/api/v1/cloudfoundry/router.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Copyright (c) 2015 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 routes
|
||||
|
||||
from murano.api.v1.cloudfoundry import cfapi
|
||||
|
||||
from murano.common import wsgi
|
||||
|
||||
|
||||
class API(wsgi.Router):
|
||||
@classmethod
|
||||
def factory(cls, global_conf, **local_conf):
|
||||
return cls(routes.Mapper())
|
||||
|
||||
def __init__(self, mapper):
|
||||
services_resource = cfapi.create_resource()
|
||||
mapper.connect('/v2/catalog',
|
||||
controller=services_resource,
|
||||
action='list',
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect(('/v2/service_instances/{instance_id}'),
|
||||
controller=services_resource,
|
||||
action='provision',
|
||||
conditions={'method': ['PUT']})
|
||||
mapper.connect(('/v2/service_instances/{instance_id}'),
|
||||
controller=services_resource,
|
||||
action='deprovision',
|
||||
conditions={'method': ['DELETE']})
|
||||
mapper.connect(('/v2/service_instances/{instance_id}/last_operation'),
|
||||
controller=services_resource,
|
||||
action='get_last_operation',
|
||||
conditions={'method': ['GET']})
|
||||
|
||||
super(API, self).__init__(mapper)
|
75
murano/cmd/cfapi.py
Normal file
75
murano/cmd/cfapi.py
Normal file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2015 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 os
|
||||
import sys
|
||||
|
||||
import eventlet
|
||||
|
||||
if os.name == 'nt':
|
||||
# eventlet monkey patching causes subprocess.Popen to fail on Windows
|
||||
# when using pipes due to missing non blocking I/O support
|
||||
eventlet.monkey_patch(os=False)
|
||||
else:
|
||||
eventlet.monkey_patch()
|
||||
|
||||
# If ../murano/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
root = os.path.join(os.path.abspath(__file__), os.pardir, os.pardir, os.pardir)
|
||||
if os.path.exists(os.path.join(root, 'murano', '__init__.py')):
|
||||
sys.path.insert(0, root)
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import service
|
||||
|
||||
from murano.api.v1 import request_statistics
|
||||
from murano.common import app_loader
|
||||
from murano.common import config
|
||||
from murano.common import policy
|
||||
from murano.common import server
|
||||
from murano.common import wsgi
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
config.parse_args()
|
||||
logging.setup(CONF, 'murano-cfapi')
|
||||
request_statistics.init_stats()
|
||||
policy.init()
|
||||
|
||||
launcher = service.ServiceLauncher(CONF)
|
||||
|
||||
cfapp = app_loader.load_paste_app('cloudfoundry')
|
||||
cfport, cfhost = (config.CONF.cfapi.bind_port,
|
||||
config.CONF.cfapi.bind_host)
|
||||
|
||||
launcher.launch_service(wsgi.Service(cfapp, cfport, cfhost))
|
||||
|
||||
launcher.launch_service(server.get_rpc_service())
|
||||
launcher.launch_service(server.get_notification_service())
|
||||
|
||||
launcher.wait()
|
||||
except RuntimeError as e:
|
||||
sys.stderr.write("ERROR: %s\n" % e)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,68 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Revision ID: 009
|
||||
Revises: None
|
||||
Create Date: 2015-08-17 16:34:33.698760
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '009'
|
||||
down_revision = '008'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
MYSQL_ENGINE = 'InnoDB'
|
||||
MYSQL_CHARSET = 'utf8'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'cf_orgs',
|
||||
sa.Column('id', sa.String(length=255), nullable=False),
|
||||
sa.Column('tenant', sa.String(length=255), nullable=False),
|
||||
sa.UniqueConstraint('tenant'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine=MYSQL_ENGINE,
|
||||
mysql_charset=MYSQL_CHARSET)
|
||||
|
||||
op.create_table(
|
||||
'cf_spaces',
|
||||
sa.Column('id', sa.String(length=255), nullable=False),
|
||||
sa.Column('environment_id', sa.String(length=255), nullable=False),
|
||||
sa.ForeignKeyConstraint(['environment_id'], ['environment.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine=MYSQL_ENGINE,
|
||||
mysql_charset=MYSQL_CHARSET)
|
||||
|
||||
op.create_table(
|
||||
'cf_serv_inst',
|
||||
sa.Column('id', sa.String(length=255), primary_key=True),
|
||||
sa.Column('service_id', sa.String(255), nullable=False),
|
||||
sa.Column('environment_id', sa.String(255), nullable=False),
|
||||
sa.Column('tenant', sa.String(255), nullable=False),
|
||||
sa.ForeignKeyConstraint(['environment_id'], ['environment.id'],),
|
||||
mysql_engine=MYSQL_ENGINE,
|
||||
mysql_charset=MYSQL_CHARSET)
|
||||
# end Alembic commands #
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('cf_orgs')
|
||||
op.drop_table('cf_spaces')
|
||||
op.drop_table('cf_serv_inst')
|
||||
# end Alembic commands #
|
@ -74,6 +74,13 @@ class Environment(Base, TimestampMixin):
|
||||
tasks = sa_orm.relationship('Task', backref='environment',
|
||||
cascade='save-update, merge, delete')
|
||||
|
||||
cf_spaces = sa_orm.relationship("CFSpace", backref='environment',
|
||||
cascade='save-update, merge, delete')
|
||||
|
||||
cf_serv_inst = sa_orm.relationship("CFServiceInstance",
|
||||
backref='environment',
|
||||
cascade='save-update, merge, delete')
|
||||
|
||||
def to_dict(self):
|
||||
dictionary = super(Environment, self).to_dict()
|
||||
del dictionary['description']
|
||||
@ -320,10 +327,45 @@ class Lock(Base):
|
||||
ts = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
|
||||
class CFOrganization(Base):
|
||||
__tablename__ = "cf_orgs"
|
||||
id = sa.Column(sa.String(255), primary_key=True)
|
||||
tenant = sa.Column(sa.String(255), nullable=False)
|
||||
|
||||
|
||||
class CFSpace(Base):
|
||||
__tablename__ = "cf_spaces"
|
||||
id = sa.Column(sa.String(255), primary_key=True)
|
||||
environment_id = sa.Column(sa.String(255), sa.ForeignKey('environment.id'),
|
||||
nullable=False)
|
||||
|
||||
def to_dict(self):
|
||||
dictionary = super(CFSpace, self).to_dict()
|
||||
if 'environment' in dictionary:
|
||||
del dictionary['environment']
|
||||
return dictionary
|
||||
|
||||
|
||||
class CFServiceInstance(Base):
|
||||
__tablename__ = 'cf_serv_inst'
|
||||
id = sa.Column(sa.String(255), primary_key=True)
|
||||
service_id = sa.Column(sa.String(255), nullable=False)
|
||||
environment_id = sa.Column(sa.String(255), sa.ForeignKey('environment.id'),
|
||||
nullable=False)
|
||||
tenant = sa.Column(sa.String(255), nullable=False)
|
||||
|
||||
def to_dict(self):
|
||||
dictionary = super(CFSpace, self).to_dict()
|
||||
if 'environment' in dictionary:
|
||||
del dictionary['environment']
|
||||
return dictionary
|
||||
|
||||
|
||||
def register_models(engine):
|
||||
"""Creates database tables for all models with the given engine."""
|
||||
models = (Environment, Status, Session, Task,
|
||||
ApiStats, Package, Category, Class, Instance, Lock)
|
||||
ApiStats, Package, Category, Class, Instance, Lock, CFSpace,
|
||||
CFOrganization)
|
||||
for model in models:
|
||||
model.metadata.create_all(engine)
|
||||
|
||||
@ -331,6 +373,7 @@ def register_models(engine):
|
||||
def unregister_models(engine):
|
||||
"""Drops database tables for all models with the given engine."""
|
||||
models = (Environment, Status, Session, Task,
|
||||
ApiStats, Package, Category, Class, Lock)
|
||||
ApiStats, Package, Category, Class, Lock, CFOrganization,
|
||||
CFSpace)
|
||||
for model in models:
|
||||
model.metadata.drop_all(engine)
|
||||
|
91
murano/db/services/cf_connections.py
Normal file
91
murano/db/services/cf_connections.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_db import exception
|
||||
import sqlalchemy
|
||||
|
||||
from murano.db import models
|
||||
from murano.db import session as db_session
|
||||
|
||||
|
||||
def set_tenant_for_org(cf_org_id, tenant):
|
||||
"""Store tenant-org link to db"""
|
||||
unit = db_session.get_session()
|
||||
try:
|
||||
with unit.begin():
|
||||
org = models.CFOrganization()
|
||||
org.id = cf_org_id
|
||||
org.tenant = tenant
|
||||
unit.add(org)
|
||||
except exception.DBDuplicateEntry:
|
||||
unit.execute(sqlalchemy.update(models.CFOrganization).where(
|
||||
models.CFOrganization.id == cf_org_id).values(
|
||||
tenant=tenant))
|
||||
|
||||
|
||||
def set_environment_for_space(cf_space_id, environment_id):
|
||||
"""Store env-space link to db"""
|
||||
unit = db_session.get_session()
|
||||
try:
|
||||
with unit.begin():
|
||||
space = models.CFSpace()
|
||||
space.id = cf_space_id
|
||||
space.environment_id = environment_id
|
||||
unit.add(space)
|
||||
except exception.DBDuplicateEntry:
|
||||
unit.execute(sqlalchemy.update(models.CFSpace).where(
|
||||
models.CFSpace.id == cf_space_id).values(
|
||||
environment_id=environment_id))
|
||||
|
||||
|
||||
def set_instance_for_service(instance_id, service_id, environment_id, tenant):
|
||||
"""Store env-space link to db"""
|
||||
unit = db_session.get_session()
|
||||
try:
|
||||
with unit.begin():
|
||||
connection = models.CFServiceInstance()
|
||||
connection.id = instance_id
|
||||
connection.service_id = service_id
|
||||
connection.environment_id = environment_id
|
||||
connection.tenant = tenant
|
||||
unit.add(connection)
|
||||
except exception.DBDuplicateEntry:
|
||||
unit.execute(sqlalchemy.update(models.CFServiceInstance).where(
|
||||
models.CFServiceInstance.id == instance_id).values(
|
||||
environment_id=environment_id))
|
||||
|
||||
|
||||
def get_environment_for_space(cf_space_id):
|
||||
"""Take env id related to space from db"""
|
||||
unit = db_session.get_session()
|
||||
connection = unit.query(models.CFSpace).get(cf_space_id)
|
||||
return connection.environment_id
|
||||
|
||||
|
||||
def get_tenant_for_org(cf_org_id):
|
||||
"""Take tenant id related to org from db"""
|
||||
unit = db_session.get_session()
|
||||
connection = unit.query(models.CFOrganization).get(cf_org_id)
|
||||
return connection.tenant
|
||||
|
||||
|
||||
def get_service_for_instance(instance_id):
|
||||
unit = db_session.get_session()
|
||||
connection = unit.query(models.CFServiceInstance).get(instance_id)
|
||||
return connection
|
||||
|
||||
|
||||
def delete_environment_from_space(environment_id):
|
||||
unit = db_session.get_session()
|
||||
unit.query(models.CFSpace).filter(
|
||||
models.CFSpace.environment_id == environment_id).delete(
|
||||
synchronize=False)
|
0
murano/tests/unit/api/v1/cloudfoundry/__init__.py
Normal file
0
murano/tests/unit/api/v1/cloudfoundry/__init__.py
Normal file
129
murano/tests/unit/api/v1/cloudfoundry/test_cfapi.py
Normal file
129
murano/tests/unit/api/v1/cloudfoundry/test_cfapi.py
Normal file
@ -0,0 +1,129 @@
|
||||
# Copyright (c) 2015 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 base64
|
||||
|
||||
import json
|
||||
import mock
|
||||
|
||||
from murano.api.v1.cloudfoundry import cfapi as api
|
||||
from murano.tests.unit import base
|
||||
|
||||
|
||||
class TestController(base.MuranoTestCase):
|
||||
def setUp(self):
|
||||
super(TestController, self).setUp()
|
||||
self.controller = api.Controller()
|
||||
|
||||
self.request = mock.MagicMock()
|
||||
self.request.headers = {'Authorization': 'Basic {encoded}'.format(
|
||||
encoded=base64.b64encode('test:test'))}
|
||||
|
||||
@mock.patch('murano.common.policy.check_is_admin')
|
||||
@mock.patch('murano.db.catalog.api.package_search')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.auth.authenticate')
|
||||
def test_list(self, mock_auth, mock_db_search, mock_policy):
|
||||
|
||||
pkg0 = mock.MagicMock()
|
||||
pkg0.id = 'xxx'
|
||||
pkg0.name = 'foo'
|
||||
pkg0.description = 'stub pkg'
|
||||
|
||||
mock_db_search.return_value = [pkg0]
|
||||
|
||||
answer = {'services': [{'bindable': True,
|
||||
'description': pkg0.description,
|
||||
'id': pkg0.id,
|
||||
'name': pkg0.name,
|
||||
'plans': [{'description': ('Default plan for '
|
||||
'the service '
|
||||
'{name}').format(
|
||||
name=pkg0.name),
|
||||
'id': 'xxx-1',
|
||||
'name': 'default'}],
|
||||
'tags': []}]}
|
||||
|
||||
resp = self.controller.list(self.request)
|
||||
self.assertEqual(answer, resp)
|
||||
|
||||
@mock.patch('murano.common.policy.check_is_admin')
|
||||
@mock.patch('murano.db.catalog.api.package_get')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.cfapi.muranoclient')
|
||||
@mock.patch('murano.db.services.cf_connections.set_instance_for_service')
|
||||
@mock.patch('murano.db.services.cf_connections.get_environment_for_space')
|
||||
@mock.patch('murano.db.services.cf_connections.get_tenant_for_org')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.auth.authenticate')
|
||||
def test_provision_from_scratch(self, mock_auth, mock_get_tenant,
|
||||
mock_get_environment, mock_is, mock_client,
|
||||
mock_package, mock_policy):
|
||||
|
||||
body = {"space_guid": "s1-p1",
|
||||
"organization_guid": "o1-r1",
|
||||
"plan_id": "p1-l1",
|
||||
"service_id": "s1-e1",
|
||||
"parameters": {'some_parameter': 'value',
|
||||
'?': {}}}
|
||||
self.request.body = json.dumps(body)
|
||||
|
||||
mock_get_environment.return_value = '555-555'
|
||||
mock_client.return_value = mock.MagicMock()
|
||||
mock_package.return_value = mock.MagicMock()
|
||||
|
||||
resp = self.controller.provision(self.request, {}, '111-111')
|
||||
|
||||
self.assertEqual({}, resp)
|
||||
|
||||
@mock.patch('murano.common.policy.check_is_admin')
|
||||
@mock.patch('murano.db.catalog.api.package_get')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.cfapi.muranoclient')
|
||||
@mock.patch('murano.db.services.cf_connections.set_instance_for_service')
|
||||
@mock.patch('murano.db.services.cf_connections.set_environment_for_space')
|
||||
@mock.patch('murano.db.services.cf_connections.set_tenant_for_org')
|
||||
@mock.patch('murano.db.services.cf_connections.get_environment_for_space')
|
||||
@mock.patch('murano.db.services.cf_connections.get_tenant_for_org')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.auth.authenticate')
|
||||
def test_provision_existent(self, mock_auth, mock_get_tenant,
|
||||
mock_get_environment, mock_set_tenant,
|
||||
mock_set_environment, mock_is, mock_client,
|
||||
mock_package, mock_policy):
|
||||
|
||||
body = {"space_guid": "s1-p1",
|
||||
"organization_guid": "o1-r1",
|
||||
"plan_id": "p1-l1",
|
||||
"service_id": "s1-e1",
|
||||
"parameters": {'some_parameter': 'value',
|
||||
'?': {}}}
|
||||
self.request.body = json.dumps(body)
|
||||
|
||||
mock_package.return_value = mock.MagicMock()
|
||||
mock_get_environment.side_effect = AttributeError
|
||||
mock_get_tenant.side_effect = AttributeError
|
||||
|
||||
resp = self.controller.provision(self.request, {}, '111-111')
|
||||
|
||||
self.assertEqual({}, resp)
|
||||
|
||||
@mock.patch('murano.api.v1.cloudfoundry.cfapi.muranoclient')
|
||||
@mock.patch('murano.api.v1.cloudfoundry.auth.authenticate')
|
||||
@mock.patch('murano.db.services.cf_connections.get_service_for_instance')
|
||||
def test_deprovision(self, mock_get_si, mock_auth, mock_client):
|
||||
service = mock.MagicMock()
|
||||
service.service_id = '111-111'
|
||||
service.tenant_id = '222-222'
|
||||
service.env_id = '333-333'
|
||||
mock_get_si.return_value = service
|
||||
|
||||
resp = self.controller.deprovision(self.request, '555-555')
|
||||
|
||||
self.assertEqual({}, resp)
|
Loading…
Reference in New Issue
Block a user