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
This commit is contained in:
parent
a5fa51f6e4
commit
0caaa2b979
@ -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
|
||||
|
||||
|
118
doc/source/api_v1.rst
Normal file
118
doc/source/api_v1.rst
Normal file
@ -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": ""
|
||||
}
|
||||
}
|
39
etc/api.conf
39
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
|
||||
|
@ -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'}
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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']
|
||||
|
0
tricircle/tests/unit/api/__init__.py
Normal file
0
tricircle/tests/unit/api/__init__.py
Normal file
0
tricircle/tests/unit/api/controllers/__init__.py
Normal file
0
tricircle/tests/unit/api/controllers/__init__.py
Normal file
150
tricircle/tests/unit/api/controllers/test_root.py
Normal file
150
tricircle/tests/unit/api/controllers/test_root.py
Normal file
@ -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())
|
@ -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')
|
||||
|
@ -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'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user