From 0caaa2b97979860427ccd1b2970729c8429958ab Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Wed, 30 Sep 2015 11:59:24 +0800 Subject: [PATCH] Site create API Implement site create API. This patch only covers database model creation and aggregate creation. Partially implements: blueprint implement-api Change-Id: I299f367900b7b15ea992fe6f0eaf614f83a1a70e --- devstack/plugin.sh | 9 +- doc/source/api_v1.rst | 118 ++++++++++++++ etc/api.conf | 39 +++++ tricircle/api/controllers/root.py | 116 ++++++++++++-- tricircle/db/client.py | 7 +- tricircle/db/core.py | 3 + .../db/migrate_repo/versions/001_init.py | 25 +-- tricircle/db/models.py | 26 +-- tricircle/tests/unit/api/__init__.py | 0 .../tests/unit/api/controllers/__init__.py | 0 .../tests/unit/api/controllers/test_root.py | 150 ++++++++++++++++++ tricircle/tests/unit/db/test_client.py | 36 ++++- tricircle/tests/unit/db/test_models.py | 6 - 13 files changed, 462 insertions(+), 73 deletions(-) create mode 100644 doc/source/api_v1.rst create mode 100644 tricircle/tests/unit/api/__init__.py create mode 100644 tricircle/tests/unit/api/controllers/__init__.py create mode 100644 tricircle/tests/unit/api/controllers/test_root.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 0582e97f..4068634d 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -77,8 +77,15 @@ function configure_tricircle_cascade_api { iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT verbose True iniset $TRICIRCLE_CASCADE_API_CONF DEFAULT use_syslog $SYSLOG + iniset $TRICIRCLE_CASCADE_API_CONF database connection `database_connection_url tricircle` - setup_colorized_logging $TRICIRCLE_CASCADE_API_CONF DEFAULT + iniset $TRICIRCLE_CASCADE_API_CONF client admin_username admin + iniset $TRICIRCLE_CASCADE_API_CONF client admin_password $ADMIN_PASSWORD + iniset $TRICIRCLE_CASCADE_API_CONF client admin_tenant demo + iniset $TRICIRCLE_CASCADE_API_CONF client auto_refresh_endpoint True + iniset $TRICIRCLE_CASCADE_API_CONF client top_site_name $OS_REGION_NAME + + setup_colorized_logging $TRICIRCLE_CASCADE_API_CONF DEFAULT tenant_name if is_service_enabled keystone; then diff --git a/doc/source/api_v1.rst b/doc/source/api_v1.rst new file mode 100644 index 00000000..7a39d394 --- /dev/null +++ b/doc/source/api_v1.rst @@ -0,0 +1,118 @@ +================ +Tricircle API v1 +================ +This API describes the ways of interacting with Tricircle(Cascade) service via +HTTP protocol using Representational State Transfer(ReST). + +Application Root [/] +==================== +Application Root provides links to all possible API methods for Tricircle. URLs +for other resources described below are relative to Application Root. + +API v1 Root [/v1/] +================== +All API v1 URLs are relative to API v1 root. + +Site [/sites/{site_id}] +======================= +A site represents a region in Keystone. When operating a site, Tricircle +decides the correct endpoints to send request based on the region of the site. +Considering the 2-layers architecture of Tricircle, we also have 2 kinds of +sites: top site and bottom site. A site has the following attributes: + +- site_id +- site_name +- az_id + +**site_id** is automatically generated when creating a site. **site_name** is +specified by user but **MUST** match the region name registered in Keystone. +When creating a bottom site, Tricircle automatically creates a host aggregate +and assigns the new availability zone id to **az_id**. Top site doesn't need a +host aggregate so **az_id** is left empty. + +URL Parameters +-------------- +- site_id: Site id + +Models +------ +:: + + { + "site_id": "302e02a6-523c-4a92-a8d1-4939b31a788c", + "site_name": "Site1", + "az_id": "az_Site1" + } + +Retrieve Site List [GET] +------------------------ +- URL: /sites +- Status: 200 +- Returns: List of Sites + +Response +:: + + { + "sites": [ + { + "site_id": "f91ca3a5-d5c6-45d6-be4c-763f5a2c4aa3", + "site_name": "RegionOne", + "az_id": "" + }, + { + "site_id": "302e02a6-523c-4a92-a8d1-4939b31a788c", + "site_name": "Site1", + "az_id": "az_Site1" + } + ] + } + +Retrieve a Single Site [GET] +---------------------------- +- URL: /sites/site_id +- Status: 200 +- Returns: Site + +Response +:: + + { + "site": { + "site_id": "302e02a6-523c-4a92-a8d1-4939b31a788c", + "site_name": "Site1", + "az_id": "az_Site1" + } + } + +Create a Site [POST] +-------------------- +- URL: /sites +- Status: 201 +- Returns: Created Site + +Request (application/json) + +.. csv-table:: + :header: "Parameter", "Type", "Description" + + name, string, name of the Site + top, bool, "indicate whether it's a top Site, optional, default false" + +:: + + { + "name": "RegionOne" + "top": true + } + +Response +:: + + { + "site": { + "site_id": "f91ca3a5-d5c6-45d6-be4c-763f5a2c4aa3", + "site_name": "RegionOne", + "az_id": "" + } + } diff --git a/etc/api.conf b/etc/api.conf index 27d85fa8..10bdb1b1 100755 --- a/etc/api.conf +++ b/etc/api.conf @@ -179,6 +179,45 @@ # If set, use this value for pool_timeout with sqlalchemy # pool_timeout = 10 +[client] + +# Keystone authentication URL +# auth_url = http://127.0.0.1:5000/v3 + +# Keystone service URL +# identity_url = http://127.0.0.1:35357/v3 + +# If set to True, endpoint will be automatically refreshed if timeout +# accessing endpoint. +# auto_refresh_endpoint = False + +# Name of top site which client needs to access +# top_site_name = + +# Username of admin account for synchronizing endpoint with Keystone +# admin_username = + +# Password of admin account for synchronizing endpoint with Keystone +# admin_password = + +# Tenant name of admin account for synchronizing endpoint with Keystone +# admin_tenant = + +# User domain name of admin account for synchronizing endpoint with Keystone +# admin_user_domain_name = default + +# Tenant domain name of admin account for synchronizing endpoint with Keystone +# admin_tenant_domain_name = default + +# Timeout for glance client in seconds +# glance_timeout = 60 + +# Timeout for neutron client in seconds +# neutron_timeout = 60 + +# Timeout for nova client in seconds +# nova_timeout = 60 + [oslo_concurrency] # Directory to use for lock files. For security, the specified directory should diff --git a/tricircle/api/controllers/root.py b/tricircle/api/controllers/root.py index 25b23702..4eb92ec2 100755 --- a/tricircle/api/controllers/root.py +++ b/tricircle/api/controllers/root.py @@ -13,9 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. +import uuid + +import oslo_log.log as logging import pecan +from pecan import request from pecan import rest +import tricircle.context as t_context +from tricircle.db import client +from tricircle.db import exception +from tricircle.db import models + +LOG = logging.getLogger(__name__) + def expose(*args, **kwargs): kwargs.setdefault('content_type', 'application/json') @@ -81,22 +92,101 @@ class V1Controller(object): } +def _extract_context_from_environ(environ): + context_paras = {'auth_token': 'HTTP_X_AUTH_TOKEN', + 'user': 'HTTP_X_USER_ID', + 'tenant': 'HTTP_X_TENANT_ID', + 'user_name': 'HTTP_X_USER_NAME', + 'tenant_name': 'HTTP_X_PROJECT_NAME', + 'domain': 'HTTP_X_DOMAIN_ID', + 'user_domain': 'HTTP_X_USER_DOMAIN_ID', + 'project_domain': 'HTTP_X_PROJECT_DOMAIN_ID', + 'request_id': 'openstack.request_id'} + for key in context_paras: + context_paras[key] = environ.get(context_paras[key]) + role = environ.get('HTTP_X_ROLE') + # TODO(zhiyuan): replace with policy check + context_paras['is_admin'] = role == 'admin' + return t_context.Context(**context_paras) + + +def _get_environment(): + return request.environ + + class SitesController(rest.RestController): + """ReST controller to handle CRUD operations of site resource""" - @expose(generic=True) - def index(self): - if pecan.request.method != 'GET': - pecan.abort(405) - return {'message': 'GET'} - - @when(index, method='PUT') - def put(self, **kw): + @expose() + def put(self, site_id, **kw): return {'message': 'PUT'} - @when(index, method='POST') - def post(self, **kw): - return {'message': 'POST'} + @expose() + def get_one(self, site_id): + context = _extract_context_from_environ(_get_environment()) + try: + return {'site': models.get_site(context, site_id)} + except exception.ResourceNotFound: + pecan.abort(404, 'Site with id %s not found' % site_id) - @when(index, method='DELETE') - def delete(self): + @expose() + def get_all(self): + context = _extract_context_from_environ(_get_environment()) + sites = models.list_sites(context, []) + return {'sites': sites} + + @expose() + def post(self, **kw): + context = _extract_context_from_environ(_get_environment()) + if not context.is_admin: + pecan.abort(400, 'Admin role required to create sites') + return + + site_name = kw.get('name') + is_top_site = kw.get('top', False) + + if not site_name: + pecan.abort(400, 'Name of site required') + return + + site_filters = [{'key': 'site_name', 'comparator': 'eq', + 'value': site_name}] + sites = models.list_sites(context, site_filters) + if sites: + pecan.abort(409, 'Site with name %s exists' % site_name) + return + + ag_name = 'ag_%s' % site_name + # top site doesn't need az + az_name = 'az_%s' % site_name if not is_top_site else '' + + try: + site_dict = {'site_id': str(uuid.uuid4()), + 'site_name': site_name, + 'az_id': az_name} + site = models.create_site(context, site_dict) + except Exception as e: + LOG.debug(e.message) + pecan.abort(500, 'Fail to create site') + return + + # top site doesn't need aggregate + if is_top_site: + pecan.response.status = 201 + return {'site': site} + else: + try: + top_client = client.Client() + top_client.create_aggregates(context, ag_name, az_name) + except Exception as e: + LOG.debug(e.message) + # delete previously created site + models.delete_site(context, site['site_id']) + pecan.abort(500, 'Fail to create aggregate') + return + pecan.response.status = 201 + return {'site': site} + + @expose() + def delete(self, site_id): return {'message': 'DELETE'} diff --git a/tricircle/db/client.py b/tricircle/db/client.py index b4c3aeac..91851233 100644 --- a/tricircle/db/client.py +++ b/tricircle/db/client.py @@ -156,7 +156,7 @@ class Client(object): return region_service_endpoint_map def _get_config_with_retry(self, cxt, filters, site, service, retry): - conf_list = models.list_site_service_configuration(cxt, filters) + conf_list = models.list_site_service_configurations(cxt, filters) if len(conf_list) > 1: raise exception.EndpointNotUnique(site, service) if len(conf_list) == 0: @@ -204,12 +204,12 @@ class Client(object): endpoint_map = self._get_endpoint_from_keystone(admin_context) else: endpoint_map = self._get_endpoint_from_keystone(cxt) + for region in endpoint_map: # use region name to query site site_filters = [{'key': 'site_name', 'comparator': 'eq', 'value': region}] site_list = models.list_sites(cxt, site_filters) - # skip region/site not registered in cascade service if len(site_list) != 1: continue @@ -219,7 +219,7 @@ class Client(object): 'value': site_id}, {'key': 'service_type', 'comparator': 'eq', 'value': service}] - config_list = models.list_site_service_configuration( + config_list = models.list_site_service_configurations( cxt, config_filters) if len(config_list) > 1: @@ -234,7 +234,6 @@ class Client(object): config_dict = { 'service_id': str(uuid.uuid4()), 'site_id': site_id, - 'service_name': '%s_%s' % (region, service), 'service_type': service, 'service_url': endpoint_map[region][service] } diff --git a/tricircle/db/core.py b/tricircle/db/core.py index 150d6639..23a5f444 100644 --- a/tricircle/db/core.py +++ b/tricircle/db/core.py @@ -74,6 +74,9 @@ def _get_resource(context, model, pk_value): def create_resource(context, model, res_dict): res_obj = model.from_dict(res_dict) context.session.add(res_obj) + context.session.flush() + # retrieve auto-generated fields + context.session.refresh(res_obj) return res_obj.to_dict() diff --git a/tricircle/db/migrate_repo/versions/001_init.py b/tricircle/db/migrate_repo/versions/001_init.py index de10ff86..db43b75d 100644 --- a/tricircle/db/migrate_repo/versions/001_init.py +++ b/tricircle/db/migrate_repo/versions/001_init.py @@ -34,18 +34,10 @@ def upgrade(migrate_engine): 'cascaded_site_service_configuration', meta, sql.Column('service_id', sql.String(length=64), primary_key=True), sql.Column('site_id', sql.String(length=64), nullable=False), - sql.Column('service_name', sql.String(length=64), unique=True, - nullable=False), sql.Column('service_type', sql.String(length=64), nullable=False), sql.Column('service_url', sql.String(length=512), nullable=False), mysql_engine='InnoDB', mysql_charset='utf8') - cascaded_service_types = sql.Table( - 'cascaded_service_types', meta, - sql.Column('id', sql.Integer, primary_key=True), - sql.Column('service_type', sql.String(length=64), unique=True), - mysql_engine='InnoDB', - mysql_charset='utf8') cascaded_site_services = sql.Table( 'cascaded_site_services', meta, sql.Column('site_id', sql.String(length=64), primary_key=True), @@ -53,20 +45,15 @@ def upgrade(migrate_engine): mysql_charset='utf8') tables = [cascaded_sites, cascaded_site_service_configuration, - cascaded_service_types, cascaded_site_services] + cascaded_site_services] for table in tables: table.create() - fkeys = [ - {'columns': [cascaded_site_service_configuration.c.site_id], - 'references': [cascaded_sites.c.site_id]}, - {'columns': [cascaded_site_service_configuration.c.service_type], - 'references': [cascaded_service_types.c.service_type]} - ] - for fkey in fkeys: - migrate.ForeignKeyConstraint(columns=fkey['columns'], - refcolumns=fkey['references'], - name=fkey.get('name')).create() + fkey = {'columns': [cascaded_site_service_configuration.c.site_id], + 'references': [cascaded_sites.c.site_id]} + migrate.ForeignKeyConstraint(columns=fkey['columns'], + refcolumns=fkey['references'], + name=fkey.get('name')).create() def downgrade(migrate_engine): diff --git a/tricircle/db/models.py b/tricircle/db/models.py index 14a69600..678590a7 100644 --- a/tricircle/db/models.py +++ b/tricircle/db/models.py @@ -44,11 +44,6 @@ def update_site(context, site_id, update_dict): return core.update_resource(context, Site, site_id, update_dict) -def create_service_type(context, type_dict): - with context.session.begin(): - return core.create_resource(context, ServiceType, type_dict) - - def create_site_service_configuration(context, config_dict): with context.session.begin(): return core.create_resource(context, SiteServiceConfiguration, @@ -61,7 +56,7 @@ def delete_site_service_configuration(context, config_id): SiteServiceConfiguration, config_id) -def list_site_service_configuration(context, filters): +def list_site_service_configurations(context, filters): with context.session.begin(): return core.query_resource(context, SiteServiceConfiguration, filters) @@ -83,31 +78,18 @@ class Site(core.ModelBase, core.DictBase): class SiteServiceConfiguration(core.ModelBase, core.DictBase): __tablename__ = 'cascaded_site_service_configuration' - attributes = ['service_id', 'site_id', 'service_name', - 'service_type', 'service_url'] + attributes = ['service_id', 'site_id', 'service_type', 'service_url'] service_id = sql.Column('service_id', sql.String(length=64), primary_key=True) site_id = sql.Column('site_id', sql.String(length=64), sql.ForeignKey('cascaded_sites.site_id'), nullable=False) - service_name = sql.Column('service_name', sql.String(length=64), - unique=True, nullable=False) - service_type = sql.Column( - 'service_type', sql.String(length=64), - sql.ForeignKey('cascaded_service_types.service_type'), - nullable=False) + service_type = sql.Column('service_type', sql.String(length=64), + nullable=False) service_url = sql.Column('service_url', sql.String(length=512), nullable=False) -class ServiceType(core.ModelBase, core.DictBase): - __tablename__ = 'cascaded_service_types' - attributes = ['id', 'service_type'] - id = sql.Column('id', sql.Integer, primary_key=True) - service_type = sql.Column('service_type', sql.String(length=64), - unique=True) - - class SiteService(core.ModelBase, core.DictBase): __tablename__ = 'cascaded_site_services' attributes = ['site_id'] diff --git a/tricircle/tests/unit/api/__init__.py b/tricircle/tests/unit/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tricircle/tests/unit/api/controllers/__init__.py b/tricircle/tests/unit/api/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tricircle/tests/unit/api/controllers/test_root.py b/tricircle/tests/unit/api/controllers/test_root.py new file mode 100644 index 00000000..d231211e --- /dev/null +++ b/tricircle/tests/unit/api/controllers/test_root.py @@ -0,0 +1,150 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# All Rights Reserved +# +# 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 mock +from mock import patch +import unittest + +import pecan + +import tricircle.api.controllers.root as root_controller +from tricircle import context +from tricircle.db import client +from tricircle.db import core +from tricircle.db import models + + +class ControllerTest(unittest.TestCase): + def setUp(self): + core.initialize() + core.ModelBase.metadata.create_all(core.get_engine()) + self.context = context.Context() + self.context.is_admin = True + + root_controller._get_environment = mock.Mock(return_value={}) + root_controller._extract_context_from_environ = mock.Mock( + return_value=self.context) + + pecan.abort = mock.Mock() + pecan.response = mock.Mock() + + def tearDown(self): + core.ModelBase.metadata.drop_all(core.get_engine()) + + +class SitesControllerTest(ControllerTest): + def setUp(self): + super(SitesControllerTest, self).setUp() + self.controller = root_controller.SitesController() + + def test_post_top_site(self): + kw = {'name': 'TopSite', 'top': True} + site_id = self.controller.post(**kw)['site']['site_id'] + site = models.get_site(self.context, site_id) + self.assertEqual(site['site_name'], 'TopSite') + self.assertEqual(site['az_id'], '') + + @patch.object(client.Client, 'create_resources') + def test_post_bottom_site(self, mock_method): + kw = {'name': 'BottomSite'} + site_id = self.controller.post(**kw)['site']['site_id'] + site = models.get_site(self.context, site_id) + self.assertEqual(site['site_name'], 'BottomSite') + self.assertEqual(site['az_id'], 'az_BottomSite') + mock_method.assert_called_once_with('aggregate', self.context, + 'ag_BottomSite', 'az_BottomSite') + + def test_post_site_name_missing(self): + kw = {'top': True} + self.controller.post(**kw) + pecan.abort.assert_called_once_with(400, 'Name of site required') + + def test_post_conflict(self): + kw = {'name': 'TopSite', 'top': True} + self.controller.post(**kw) + self.controller.post(**kw) + pecan.abort.assert_called_once_with(409, + 'Site with name TopSite exists') + + def test_post_not_admin(self): + self.context.is_admin = False + kw = {'name': 'TopSite', 'top': True} + self.controller.post(**kw) + pecan.abort.assert_called_once_with( + 400, 'Admin role required to create sites') + + @patch.object(client.Client, 'create_resources') + def test_post_decide_top(self, mock_method): + # 'top' default to False + # top site + kw = {'name': 'Site1', 'top': True} + self.controller.post(**kw) + # bottom site + kw = {'name': 'Site2', 'top': False} + self.controller.post(**kw) + kw = {'name': 'Site3'} + self.controller.post(**kw) + calls = [mock.call('aggregate', self.context, 'ag_Site%d' % i, + 'az_Site%d' % i) for i in xrange(2, 4)] + mock_method.assert_has_calls(calls) + + @patch.object(models, 'create_site') + def test_post_create_site_exception(self, mock_method): + mock_method.side_effect = Exception + kw = {'name': 'BottomSite'} + self.controller.post(**kw) + pecan.abort.assert_called_once_with(500, 'Fail to create site') + + @patch.object(client.Client, 'create_resources') + def test_post_create_aggregate_exception(self, mock_method): + mock_method.side_effect = Exception + kw = {'name': 'BottomSite'} + self.controller.post(**kw) + pecan.abort.assert_called_once_with(500, 'Fail to create aggregate') + + # make sure site is deleted + site_filter = [{'key': 'site_name', + 'comparator': 'eq', + 'value': 'BottomSite'}] + sites = models.list_sites(self.context, site_filter) + self.assertEqual(len(sites), 0) + + def test_get_one(self): + kw = {'name': 'TopSite', 'top': True} + site_id = self.controller.post(**kw)['site']['site_id'] + return_site = self.controller.get_one(site_id)['site'] + self.assertEqual(return_site, {'site_id': site_id, + 'site_name': 'TopSite', + 'az_id': ''}) + + def test_get_one_not_found(self): + self.controller.get_one('fake_id') + pecan.abort.assert_called_once_with(404, + 'Site with id fake_id not found') + + @patch.object(client.Client, 'create_resources', new=mock.Mock) + def test_get_all(self): + kw1 = {'name': 'TopSite', 'top': True} + kw2 = {'name': 'BottomSite'} + self.controller.post(**kw1) + self.controller.post(**kw2) + sites = self.controller.get_all() + actual_result = [(site['site_name'], + site['az_id']) for site in sites['sites']] + expect_result = [('BottomSite', 'az_BottomSite'), ('TopSite', '')] + self.assertItemsEqual(actual_result, expect_result) + + def tearDown(self): + core.ModelBase.metadata.drop_all(core.get_engine()) diff --git a/tricircle/tests/unit/db/test_client.py b/tricircle/tests/unit/db/test_client.py index 99954363..b25628a5 100644 --- a/tricircle/tests/unit/db/test_client.py +++ b/tricircle/tests/unit/db/test_client.py @@ -15,6 +15,7 @@ import unittest +import uuid import mock from oslo_config import cfg @@ -31,7 +32,6 @@ FAKE_RESOURCE = 'fake_res' FAKE_SITE_ID = 'fake_site_id' FAKE_SITE_NAME = 'fake_site_name' FAKE_SERVICE_ID = 'fake_service_id' -FAKE_SERVICE_NAME = 'fake_service_name' FAKE_TYPE = 'fake_type' FAKE_URL = 'http://127.0.0.1:12345' FAKE_URL_INVALID = 'http://127.0.0.1:23456' @@ -105,6 +105,8 @@ class ClientTest(unittest.TestCase): def setUp(self): core.initialize() core.ModelBase.metadata.create_all(core.get_engine()) + # enforce foreign key constraint for sqlite + core.get_engine().execute('pragma foreign_keys=on') self.context = context.Context() site_dict = { @@ -112,19 +114,13 @@ class ClientTest(unittest.TestCase): 'site_name': FAKE_SITE_NAME, 'az_id': FAKE_AZ } - type_dict = { - 'id': 1, - 'service_type': FAKE_TYPE - } config_dict = { 'service_id': FAKE_SERVICE_ID, 'site_id': FAKE_SITE_ID, - 'service_name': FAKE_SERVICE_NAME, 'service_type': FAKE_TYPE, 'service_url': FAKE_URL } models.create_site(self.context, site_dict) - models.create_service_type(self.context, type_dict) models.create_site_service_configuration(self.context, config_dict) global FAKE_RESOURCES @@ -207,7 +203,6 @@ class ClientTest(unittest.TestCase): config_dict = { 'service_id': FAKE_SERVICE_ID + '_new', 'site_id': FAKE_SITE_ID, - 'service_name': FAKE_SERVICE_NAME + '_new', 'service_type': FAKE_TYPE, 'service_url': FAKE_URL } @@ -249,6 +244,31 @@ class ClientTest(unittest.TestCase): FAKE_RESOURCE, self.context, []) self.assertEqual(resources, [{'name': 'res1'}, {'name': 'res2'}]) + def test_update_endpoint_from_keystone(self): + self.client._get_admin_token = mock.Mock() + self.client._get_endpoint_from_keystone = mock.Mock() + self.client._get_endpoint_from_keystone.return_value = { + FAKE_SITE_NAME: {FAKE_TYPE: FAKE_URL, + 'another_fake_type': 'http://127.0.0.1:34567'}, + 'not_registered_site': {FAKE_TYPE: FAKE_URL} + } + models.create_site_service_configuration = mock.Mock() + models.update_site_service_configuration = mock.Mock() + uuid.uuid4 = mock.Mock() + uuid.uuid4.return_value = 'another_fake_service_id' + + self.client.update_endpoint_from_keystone(self.context) + update_dict = {'service_url': FAKE_URL} + create_dict = {'service_id': 'another_fake_service_id', + 'site_id': FAKE_SITE_ID, + 'service_type': 'another_fake_type', + 'service_url': 'http://127.0.0.1:34567'} + # not registered site is skipped + models.update_site_service_configuration.assert_called_once_with( + self.context, FAKE_SERVICE_ID, update_dict) + models.create_site_service_configuration.assert_called_once_with( + self.context, create_dict) + def test_get_endpoint(self): cfg.CONF.set_override(name='auto_refresh_endpoint', override=False, group='client') diff --git a/tricircle/tests/unit/db/test_models.py b/tricircle/tests/unit/db/test_models.py index b00ca1f1..c5c9fa44 100644 --- a/tricircle/tests/unit/db/test_models.py +++ b/tricircle/tests/unit/db/test_models.py @@ -43,15 +43,9 @@ class ModelsTest(unittest.TestCase): site_ret = models.create_site(self.context, site) self.assertEqual(site_ret, site) - service_type = {'id': 1, - 'service_type': 'nova'} - type_ret = models.create_service_type(self.context, service_type) - self.assertEqual(type_ret, service_type) - configuration = { 'service_id': 'test_config_uuid', 'site_id': 'test_site_uuid', - 'service_name': 'nova_service', 'service_type': 'nova', 'service_url': 'http://test_url' }