Site create RPC
Implement site create RPC. Now tricircle api can utilize RPC to notify cascade service to start rpc server for new site. Partially implements: blueprint implement-api Ref: https://blueprints.launchpad.net/tricircle/+spec/implement-api Change-Id: I73879a84d31b5ac9004cfe3f18cb9a984d53099c
This commit is contained in:
parent
0caaa2b979
commit
fd5e79451e
@ -34,7 +34,7 @@ import nova.objects as nova_objects
|
|||||||
from nova.objects import base as objects_base
|
from nova.objects import base as objects_base
|
||||||
import nova.rpc as nova_rpc
|
import nova.rpc as nova_rpc
|
||||||
|
|
||||||
import tricircle.cascade_service.service as service
|
import tricircle.dispatcher.service as service
|
||||||
|
|
||||||
|
|
||||||
def block_db_access():
|
def block_db_access():
|
||||||
@ -62,7 +62,7 @@ def process_command_line_arguments():
|
|||||||
logging.register_options(cfg.CONF)
|
logging.register_options(cfg.CONF)
|
||||||
logging.set_defaults()
|
logging.set_defaults()
|
||||||
cfg.CONF(sys.argv[1:])
|
cfg.CONF(sys.argv[1:])
|
||||||
logging.setup(cfg.CONF, "cascade_service", version='0.1')
|
logging.setup(cfg.CONF, "dispatcher", version='0.1')
|
||||||
|
|
||||||
|
|
||||||
def _set_up_nova_objects():
|
def _set_up_nova_objects():
|
@ -18,9 +18,9 @@ function create_tricircle_accounts {
|
|||||||
create_service_user "tricircle"
|
create_service_user "tricircle"
|
||||||
|
|
||||||
if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then
|
if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then
|
||||||
local tricircle_cascade_service=$(get_or_create_service "tricircle" \
|
local tricircle_dispatcher=$(get_or_create_service "tricircle" \
|
||||||
"Cascading" "OpenStack Cascading Service")
|
"Cascading" "OpenStack Cascading Service")
|
||||||
get_or_create_endpoint $tricircle_cascade_service \
|
get_or_create_endpoint $tricircle_dispatcher \
|
||||||
"$REGION_NAME" \
|
"$REGION_NAME" \
|
||||||
"$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \
|
"$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \
|
||||||
"$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \
|
"$SERVICE_PROTOCOL://$TRICIRCLE_CASCADE_API_HOST:$TRICIRCLE_CASCADE_API_PORT/v1.0" \
|
||||||
@ -54,18 +54,18 @@ function configure_tricircle_plugin {
|
|||||||
if is_service_enabled t-svc ; then
|
if is_service_enabled t-svc ; then
|
||||||
echo "Configuring Neutron for Tricircle Cascade Service"
|
echo "Configuring Neutron for Tricircle Cascade Service"
|
||||||
sudo install -d -o $STACK_USER -m 755 $TRICIRCLE_CONF_DIR
|
sudo install -d -o $STACK_USER -m 755 $TRICIRCLE_CONF_DIR
|
||||||
cp -p $TRICIRCLE_DIR/etc/cascade_service.conf $TRICIRCLE_CASCADE_CONF
|
cp -p $TRICIRCLE_DIR/etc/dispatcher.conf $TRICIRCLE_DISPATCHER_CONF
|
||||||
|
|
||||||
TRICIRCLE_POLICY_FILE=$TRICIRCLE_CONF_DIR/policy.json
|
TRICIRCLE_POLICY_FILE=$TRICIRCLE_CONF_DIR/policy.json
|
||||||
cp $TRICIRCLE_DIR/etc/policy.json $TRICIRCLE_POLICY_FILE
|
cp $TRICIRCLE_DIR/etc/policy.json $TRICIRCLE_POLICY_FILE
|
||||||
|
|
||||||
iniset $TRICIRCLE_CASCADE_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
|
iniset $TRICIRCLE_DISPATCHER_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
|
||||||
iniset $TRICIRCLE_CASCADE_CONF DEFAULT verbose True
|
iniset $TRICIRCLE_DISPATCHER_CONF DEFAULT verbose True
|
||||||
setup_colorized_logging $TRICIRCLE_CASCADE_CONF DEFAULT
|
setup_colorized_logging $TRICIRCLE_DISPATCHER_CONF DEFAULT tenant_name
|
||||||
iniset $TRICIRCLE_CASCADE_CONF DEFAULT bind_host $TRICIRCLE_CASCADE_LISTEN_ADDRESS
|
iniset $TRICIRCLE_DISPATCHER_CONF DEFAULT bind_host $TRICIRCLE_DISPATCHER_LISTEN_ADDRESS
|
||||||
iniset $TRICIRCLE_CASCADE_CONF DEFAULT use_syslog $SYSLOG
|
iniset $TRICIRCLE_DISPATCHER_CONF DEFAULT use_syslog $SYSLOG
|
||||||
iniset_rpc_backend tricircle $TRICIRCLE_CASCADE_CONF
|
iniset_rpc_backend tricircle $TRICIRCLE_DISPATCHER_CONF
|
||||||
iniset $TRICIRCLE_CASCADE_CONF database connection `database_connection_url tricircle`
|
iniset $TRICIRCLE_DISPATCHER_CONF database connection `database_connection_url tricircle`
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,13 +121,13 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then
|
|||||||
echo export PYTHONPATH=\$PYTHONPATH:$TRICIRCLE_DIR >> $RC_DIR/.localrc.auto
|
echo export PYTHONPATH=\$PYTHONPATH:$TRICIRCLE_DIR >> $RC_DIR/.localrc.auto
|
||||||
|
|
||||||
recreate_database tricircle
|
recreate_database tricircle
|
||||||
python "$TRICIRCLE_DIR/cmd/manage.py" "$TRICIRCLE_CASCADE_CONF"
|
python "$TRICIRCLE_DIR/cmd/manage.py" "$TRICIRCLE_DISPATCHER_CONF"
|
||||||
|
|
||||||
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
||||||
echo_summary "Initializing Cascading Service"
|
echo_summary "Initializing Cascading Service"
|
||||||
|
|
||||||
if is_service_enabled t-svc; then
|
if is_service_enabled t-svc; then
|
||||||
run_process t-svc "python $TRICIRCLE_CASCADE_SERVICE --config-file $TRICIRCLE_CASCADE_CONF --config-dir $TRICIRCLE_CONF_DIR"
|
run_process t-svc "python $TRICIRCLE_DISPATCHER --config-file $TRICIRCLE_DISPATCHER_CONF --config-dir $TRICIRCLE_CONF_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if is_service_enabled t-svc-api; then
|
if is_service_enabled t-svc-api; then
|
||||||
|
@ -7,9 +7,9 @@ TRICIRCLE_BRANCH=${TRICIRCLE_BRANCH:-master}
|
|||||||
TRICIRCLE_CONF_DIR=${TRICIRCLE_CONF_DIR:-/etc/tricircle}
|
TRICIRCLE_CONF_DIR=${TRICIRCLE_CONF_DIR:-/etc/tricircle}
|
||||||
|
|
||||||
# cascade service
|
# cascade service
|
||||||
TRICIRCLE_CASCADE_SERVICE=$TRICIRCLE_DIR/cmd/cascade_service.py
|
TRICIRCLE_DISPATCHER=$TRICIRCLE_DIR/cmd/dispatcher.py
|
||||||
TRICIRCLE_CASCADE_CONF=$TRICIRCLE_CONF_DIR/cascade_service.conf
|
TRICIRCLE_DISPATCHER_CONF=$TRICIRCLE_CONF_DIR/dispatcher.conf
|
||||||
TRICIRCLE_CASCADE_LISTEN_ADDRESS=${TRICIRCLE_CASCADE_LISTEN_ADDRESS:-0.0.0.0}
|
TRICIRCLE_DISPATCHER_LISTEN_ADDRESS=${TRICIRCLE_DISPATCHER_LISTEN_ADDRESS:-0.0.0.0}
|
||||||
|
|
||||||
# cascade rest api
|
# cascade rest api
|
||||||
TRICIRCLE_CASCADE_API=$TRICIRCLE_DIR/cmd/api.py
|
TRICIRCLE_CASCADE_API=$TRICIRCLE_DIR/cmd/api.py
|
||||||
|
0
etc/api.conf
Executable file → Normal file
0
etc/api.conf
Executable file → Normal file
@ -20,6 +20,8 @@ import pecan
|
|||||||
from pecan import request
|
from pecan import request
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
|
|
||||||
|
from tricircle.common import cascading_site_api
|
||||||
|
from tricircle.common import utils
|
||||||
import tricircle.context as t_context
|
import tricircle.context as t_context
|
||||||
from tricircle.db import client
|
from tricircle.db import client
|
||||||
from tricircle.db import exception
|
from tricircle.db import exception
|
||||||
@ -156,9 +158,9 @@ class SitesController(rest.RestController):
|
|||||||
pecan.abort(409, 'Site with name %s exists' % site_name)
|
pecan.abort(409, 'Site with name %s exists' % site_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
ag_name = 'ag_%s' % site_name
|
ag_name = utils.get_ag_name(site_name)
|
||||||
# top site doesn't need az
|
# top site doesn't need az
|
||||||
az_name = 'az_%s' % site_name if not is_top_site else ''
|
az_name = utils.get_az_name(site_name) if not is_top_site else ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
site_dict = {'site_id': str(uuid.uuid4()),
|
site_dict = {'site_id': str(uuid.uuid4()),
|
||||||
@ -178,6 +180,8 @@ class SitesController(rest.RestController):
|
|||||||
try:
|
try:
|
||||||
top_client = client.Client()
|
top_client = client.Client()
|
||||||
top_client.create_aggregates(context, ag_name, az_name)
|
top_client.create_aggregates(context, ag_name, az_name)
|
||||||
|
site_api = cascading_site_api.CascadingSiteNotifyAPI()
|
||||||
|
site_api.create_site(context, site_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug(e.message)
|
LOG.debug(e.message)
|
||||||
# delete previously created site
|
# delete previously created site
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
# Copyright 2015 Huawei Technologies Co., Ltd.
|
|
||||||
#
|
|
||||||
# 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 socket import gethostname
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from nova import exception
|
|
||||||
from nova import objects
|
|
||||||
from nova.scheduler import driver
|
|
||||||
from nova.scheduler.manager import SchedulerManager
|
|
||||||
|
|
||||||
from tricircle.common.utils import get_import_path
|
|
||||||
|
|
||||||
from tricircle.cascade_service import site_manager
|
|
||||||
from tricircle.cascade_service.compute import NovaService
|
|
||||||
|
|
||||||
cfg.CONF.import_opt('scheduler_topic', 'nova.scheduler.rpcapi')
|
|
||||||
|
|
||||||
_REPORT_INTERVAL = 30
|
|
||||||
_REPORT_INTERVAL_MAX = 60
|
|
||||||
|
|
||||||
|
|
||||||
def _get_import_path(klass):
|
|
||||||
return "%s.%s" % (klass.__module__, klass.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def create_server():
|
|
||||||
return NovaService(
|
|
||||||
host=gethostname(),
|
|
||||||
binary="nova-scheduler",
|
|
||||||
topic="scheduler", # TODO(saggi): get from conf
|
|
||||||
db_allowed=False,
|
|
||||||
periodic_enable=True,
|
|
||||||
report_interval=_REPORT_INTERVAL,
|
|
||||||
periodic_interval_max=_REPORT_INTERVAL_MAX,
|
|
||||||
manager=get_import_path(SchedulerManager),
|
|
||||||
scheduler_driver=get_import_path(TricircleSchedulerDriver),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _AvailabilityZone(object):
|
|
||||||
def __init__(self, name, host_manager):
|
|
||||||
self.name = name
|
|
||||||
self._host_manager = host_manager
|
|
||||||
self._site_manager = site_manager.get_instance()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_aggregates(self):
|
|
||||||
for aggregate in self._host_manager.aggregates:
|
|
||||||
if aggregate.metadata[u'availability_zone'] == self.name:
|
|
||||||
yield aggregate
|
|
||||||
|
|
||||||
@property
|
|
||||||
def member_hosts(self):
|
|
||||||
for aggregate in self.host_aggregates:
|
|
||||||
for host in aggregate.hosts:
|
|
||||||
yield host
|
|
||||||
|
|
||||||
@property
|
|
||||||
def valid_sites(self):
|
|
||||||
for host in self.member_hosts:
|
|
||||||
yield self._site_manager.get_site(host)
|
|
||||||
|
|
||||||
|
|
||||||
class _HostManager(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.aggregates = []
|
|
||||||
|
|
||||||
# Required methods from OpenStack interface
|
|
||||||
|
|
||||||
def update_aggregates(self, aggregates):
|
|
||||||
# This is not called reliably enough to trust
|
|
||||||
# we just reload the aggregates on every call
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_aggregate(self, aggregate):
|
|
||||||
# This is not called reliably enough to trust
|
|
||||||
# we just reload the aggregates on every call
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_instance_info(self, context, host_name, instance_info):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_instance_info(self, context, host_name, instance_uuid):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def sync_instance_info(self, context, host_name, instance_uuids):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Tricircle only methods
|
|
||||||
|
|
||||||
def get_availability_zone(self, az_name):
|
|
||||||
return _AvailabilityZone(az_name, self)
|
|
||||||
|
|
||||||
def reload_aggregates(self, context):
|
|
||||||
self.aggregates = objects.AggregateList.get_all(context)
|
|
||||||
|
|
||||||
|
|
||||||
class TricircleSchedulerDriver(driver.Scheduler):
|
|
||||||
def __init__(self):
|
|
||||||
super(TricircleSchedulerDriver, self).__init__()
|
|
||||||
self.host_manager = _HostManager()
|
|
||||||
self._site_manager = site_manager.get_instance()
|
|
||||||
|
|
||||||
def select_destinations(self, ctxt, request_spec, filter_properties):
|
|
||||||
self.host_manager.reload_aggregates(ctxt)
|
|
||||||
availability_zone = self.host_manager.get_availability_zone(
|
|
||||||
request_spec[u'instance_properties'][u'availability_zone'])
|
|
||||||
|
|
||||||
for site in availability_zone.valid_sites:
|
|
||||||
site.prepare_for_instance(request_spec, filter_properties)
|
|
||||||
return [{
|
|
||||||
'host': site.name,
|
|
||||||
'nodename': site.get_nodes()[0].hypervisor_hostname,
|
|
||||||
'limits': None,
|
|
||||||
}]
|
|
||||||
else:
|
|
||||||
raise exception.NoValidHost(
|
|
||||||
"No sites match requested availability zone")
|
|
50
tricircle/common/cascading_site_api.py
Normal file
50
tricircle/common/cascading_site_api.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||||
|
#
|
||||||
|
# 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_log import log as logging
|
||||||
|
import oslo_messaging
|
||||||
|
|
||||||
|
from tricircle.common import rpc
|
||||||
|
from tricircle.common import topics
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CascadingSiteNotifyAPI(object):
|
||||||
|
"""API for to notify Cascading service for the site API."""
|
||||||
|
|
||||||
|
def __init__(self, topic=topics.CASCADING_SERVICE):
|
||||||
|
target = oslo_messaging.Target(topic=topic,
|
||||||
|
exchange="tricircle",
|
||||||
|
namespace="site",
|
||||||
|
version='1.0',
|
||||||
|
fanout=True)
|
||||||
|
self.client = rpc.create_client(target)
|
||||||
|
|
||||||
|
def _cast_message(self, context, method, payload):
|
||||||
|
"""Cast the payload to the running cascading service instances."""
|
||||||
|
|
||||||
|
cctx = self.client.prepare()
|
||||||
|
LOG.debug('Fanout notify at %(topic)s.%(namespace)s the message '
|
||||||
|
'%(method)s for CascadingSite. payload: %(payload)s',
|
||||||
|
{'topic': cctx.target.topic,
|
||||||
|
'namespace': cctx.target.namespace,
|
||||||
|
'payload': payload,
|
||||||
|
'method': method})
|
||||||
|
cctx.cast(context, method, payload=payload)
|
||||||
|
|
||||||
|
def create_site(self, context, site_name):
|
||||||
|
self._cast_message(context, "create_site", site_name)
|
@ -22,7 +22,6 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import oslo_messaging
|
import oslo_messaging
|
||||||
|
|
||||||
from tricircle.common import topics
|
|
||||||
from tricircle.common.serializer import CascadeSerializer as Serializer
|
from tricircle.common.serializer import CascadeSerializer as Serializer
|
||||||
|
|
||||||
TRANSPORT = oslo_messaging.get_transport(cfg.CONF)
|
TRANSPORT = oslo_messaging.get_transport(cfg.CONF)
|
||||||
@ -58,15 +57,7 @@ class NetworkingRpcApi(object):
|
|||||||
'update_port_down', port_id=port_id)
|
'update_port_down', port_id=port_id)
|
||||||
|
|
||||||
|
|
||||||
def create_client(component_name):
|
def create_client(target):
|
||||||
topic = topics.CASCADING_SERVICE
|
|
||||||
target = oslo_messaging.Target(
|
|
||||||
exchange="tricircle",
|
|
||||||
topic=topic,
|
|
||||||
namespace=component_name,
|
|
||||||
version='1.0',
|
|
||||||
)
|
|
||||||
|
|
||||||
return oslo_messaging.RPCClient(
|
return oslo_messaging.RPCClient(
|
||||||
TRANSPORT,
|
TRANSPORT,
|
||||||
target,
|
target,
|
||||||
|
@ -17,6 +17,8 @@ import six
|
|||||||
from oslo_messaging import Serializer
|
from oslo_messaging import Serializer
|
||||||
from neutron.api.v2.attributes import ATTR_NOT_SPECIFIED
|
from neutron.api.v2.attributes import ATTR_NOT_SPECIFIED
|
||||||
|
|
||||||
|
import tricircle.context as t_context
|
||||||
|
|
||||||
|
|
||||||
class Mapping(object):
|
class Mapping(object):
|
||||||
def __init__(self, mapping):
|
def __init__(self, mapping):
|
||||||
@ -70,13 +72,7 @@ class CascadeSerializer(Serializer):
|
|||||||
return entity
|
return entity
|
||||||
|
|
||||||
def serialize_context(self, context):
|
def serialize_context(self, context):
|
||||||
if self._base is not None:
|
return context.to_dict()
|
||||||
context = self._base.serialize_context(context)
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def deserialize_context(self, context):
|
def deserialize_context(self, context):
|
||||||
if self._base is not None:
|
return t_context.Context.from_dict(context)
|
||||||
context = self._base.deserialize_context(context)
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
@ -16,3 +16,11 @@
|
|||||||
|
|
||||||
def get_import_path(cls):
|
def get_import_path(cls):
|
||||||
return cls.__module__ + "." + cls.__name__
|
return cls.__module__ + "." + cls.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def get_ag_name(site_name):
|
||||||
|
return 'ag_%s' % site_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_az_name(site_name):
|
||||||
|
return 'az_%s' % site_name
|
||||||
|
@ -18,6 +18,16 @@ from oslo_context import context as oslo_ctx
|
|||||||
from tricircle.db import core
|
from tricircle.db import core
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_context():
|
||||||
|
return Context()
|
||||||
|
|
||||||
|
|
||||||
|
def get_admin_context():
|
||||||
|
ctx = Context()
|
||||||
|
ctx.is_admin = True
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class ContextBase(oslo_ctx.RequestContext):
|
class ContextBase(oslo_ctx.RequestContext):
|
||||||
def __init__(self, auth_token=None, user_id=None, tenant_id=None,
|
def __init__(self, auth_token=None, user_id=None, tenant_id=None,
|
||||||
is_admin=False, request_id=None, overwrite=True,
|
is_admin=False, request_id=None, overwrite=True,
|
||||||
|
@ -119,7 +119,7 @@ class Client(object):
|
|||||||
getattr(self, '%s_resources' % operation),
|
getattr(self, '%s_resources' % operation),
|
||||||
resource))
|
resource))
|
||||||
|
|
||||||
def _get_admin_token(self):
|
def _get_keystone_session(self):
|
||||||
auth = auth_identity.Password(
|
auth = auth_identity.Password(
|
||||||
auth_url=cfg.CONF.client.identity_url,
|
auth_url=cfg.CONF.client.identity_url,
|
||||||
username=cfg.CONF.client.admin_username,
|
username=cfg.CONF.client.admin_username,
|
||||||
@ -127,8 +127,13 @@ class Client(object):
|
|||||||
project_name=cfg.CONF.client.admin_tenant,
|
project_name=cfg.CONF.client.admin_tenant,
|
||||||
user_domain_name=cfg.CONF.client.admin_user_domain_name,
|
user_domain_name=cfg.CONF.client.admin_user_domain_name,
|
||||||
project_domain_name=cfg.CONF.client.admin_tenant_domain_name)
|
project_domain_name=cfg.CONF.client.admin_tenant_domain_name)
|
||||||
sess = session.Session(auth=auth)
|
return session.Session(auth=auth)
|
||||||
return sess.get_token()
|
|
||||||
|
def _get_admin_token(self):
|
||||||
|
return self._get_keystone_session().get_token()
|
||||||
|
|
||||||
|
def _get_admin_project_id(self):
|
||||||
|
return self._get_keystone_session().get_project_id()
|
||||||
|
|
||||||
def _get_endpoint_from_keystone(self, cxt):
|
def _get_endpoint_from_keystone(self, cxt):
|
||||||
auth = token_endpoint.Token(cfg.CONF.client.identity_url,
|
auth = token_endpoint.Token(cfg.CONF.client.identity_url,
|
||||||
@ -285,6 +290,10 @@ class Client(object):
|
|||||||
:return: list of dict containing resources information
|
:return: list of dict containing resources information
|
||||||
:raises: EndpointNotAvailable
|
:raises: EndpointNotAvailable
|
||||||
"""
|
"""
|
||||||
|
if cxt.is_admin and not cxt.auth_token:
|
||||||
|
cxt.auth_token = self._get_admin_token()
|
||||||
|
cxt.tenant = self._get_admin_project_id()
|
||||||
|
|
||||||
service = self.resource_service_map[resource]
|
service = self.resource_service_map[resource]
|
||||||
handle = self.service_handle_map[service]
|
handle = self.service_handle_map[service]
|
||||||
filters = filters or []
|
filters = filters or []
|
||||||
@ -310,6 +319,10 @@ class Client(object):
|
|||||||
:return: a dict containing resource information
|
:return: a dict containing resource information
|
||||||
:raises: EndpointNotAvailable
|
:raises: EndpointNotAvailable
|
||||||
"""
|
"""
|
||||||
|
if cxt.is_admin and not cxt.auth_token:
|
||||||
|
cxt.auth_token = self._get_admin_token()
|
||||||
|
cxt.tenant = self._get_admin_project_id()
|
||||||
|
|
||||||
service = self.resource_service_map[resource]
|
service = self.resource_service_map[resource]
|
||||||
handle = self.service_handle_map[service]
|
handle = self.service_handle_map[service]
|
||||||
return handle.handle_create(cxt, resource, *args, **kwargs)
|
return handle.handle_create(cxt, resource, *args, **kwargs)
|
||||||
@ -328,6 +341,39 @@ class Client(object):
|
|||||||
:return: None
|
:return: None
|
||||||
:raises: EndpointNotAvailable
|
:raises: EndpointNotAvailable
|
||||||
"""
|
"""
|
||||||
|
if cxt.is_admin and not cxt.auth_token:
|
||||||
|
cxt.auth_token = self._get_admin_token()
|
||||||
|
cxt.tenant = self._get_admin_project_id()
|
||||||
|
|
||||||
service = self.resource_service_map[resource]
|
service = self.resource_service_map[resource]
|
||||||
handle = self.service_handle_map[service]
|
handle = self.service_handle_map[service]
|
||||||
handle.handle_delete(cxt, resource, resource_id)
|
handle.handle_delete(cxt, resource, resource_id)
|
||||||
|
|
||||||
|
@_safe_operation('action')
|
||||||
|
def action_resources(self, resource, cxt, action, *args, **kwargs):
|
||||||
|
"""Apply action on resource in site of top layer
|
||||||
|
|
||||||
|
Directly invoke this method to apply action, or use
|
||||||
|
action_(resource)s (self, cxt, action, *args, **kwargs). These methods
|
||||||
|
are automatically generated according to the supported resources of
|
||||||
|
each ResourceHandle class.
|
||||||
|
|
||||||
|
:param resource: resource type
|
||||||
|
:param cxt: context object
|
||||||
|
:param action: action applied on resource
|
||||||
|
:param args, kwargs: passed according to resource type
|
||||||
|
--------------------------
|
||||||
|
resource -> action -> args -> kwargs
|
||||||
|
--------------------------
|
||||||
|
aggregate -> add_host -> aggregate, host -> none
|
||||||
|
--------------------------
|
||||||
|
:return: None
|
||||||
|
:raises: EndpointNotAvailable
|
||||||
|
"""
|
||||||
|
if cxt.is_admin and not cxt.auth_token:
|
||||||
|
cxt.auth_token = self._get_admin_token()
|
||||||
|
cxt.tenant = self._get_admin_project_id()
|
||||||
|
|
||||||
|
service = self.resource_service_map[resource]
|
||||||
|
handle = self.service_handle_map[service]
|
||||||
|
return handle.handle_action(cxt, resource, action, *args, **kwargs)
|
||||||
|
@ -40,8 +40,9 @@ client_opts = [
|
|||||||
cfg.CONF.register_opts(client_opts, group='client')
|
cfg.CONF.register_opts(client_opts, group='client')
|
||||||
|
|
||||||
|
|
||||||
LIST, CREATE, DELETE = 1, 2, 4
|
LIST, CREATE, DELETE, ACTION = 1, 2, 4, 8
|
||||||
operation_index_map = {'list': LIST, 'create': CREATE, 'delete': DELETE}
|
operation_index_map = {'list': LIST, 'create': CREATE,
|
||||||
|
'delete': DELETE, 'action': ACTION}
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -126,7 +127,7 @@ class NovaResourceHandle(ResourceHandle):
|
|||||||
service_type = 'nova'
|
service_type = 'nova'
|
||||||
support_resource = {'flavor': LIST,
|
support_resource = {'flavor': LIST,
|
||||||
'server': LIST,
|
'server': LIST,
|
||||||
'aggregate': LIST | CREATE | DELETE}
|
'aggregate': LIST | CREATE | DELETE | ACTION}
|
||||||
|
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
cli = n_client.Client('2',
|
cli = n_client.Client('2',
|
||||||
@ -177,3 +178,14 @@ class NovaResourceHandle(ResourceHandle):
|
|||||||
except n_exceptions.NotFound:
|
except n_exceptions.NotFound:
|
||||||
LOG.debug("Delete %(resource)s %(resource_id)s which not found",
|
LOG.debug("Delete %(resource)s %(resource_id)s which not found",
|
||||||
{'resource': resource, 'resource_id': resource_id})
|
{'resource': resource, 'resource_id': resource_id})
|
||||||
|
|
||||||
|
def handle_action(self, cxt, resource, action, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
client = self._get_client(cxt)
|
||||||
|
collection = '%ss' % resource
|
||||||
|
resource_manager = getattr(client, collection)
|
||||||
|
getattr(resource_manager, action)(*args, **kwargs)
|
||||||
|
except r_exceptions.ConnectTimeout:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable('nova',
|
||||||
|
client.client.management_url)
|
||||||
|
32
tricircle/dispatcher/endpoints/site.py
Normal file
32
tricircle/dispatcher/endpoints/site.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||||
|
#
|
||||||
|
# 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 oslo_messaging
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tricircle.dispatcher import site_manager
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CascadeSiteServiceEndpoint(object):
|
||||||
|
|
||||||
|
target = oslo_messaging.Target(namespace="site",
|
||||||
|
version='1.0')
|
||||||
|
|
||||||
|
def create_site(self, ctx, payload):
|
||||||
|
site_manager.get_instance().create_site(ctx, payload)
|
@ -22,11 +22,13 @@ import oslo_messaging
|
|||||||
|
|
||||||
from tricircle.common import topics
|
from tricircle.common import topics
|
||||||
from tricircle.common.serializer import CascadeSerializer as Serializer
|
from tricircle.common.serializer import CascadeSerializer as Serializer
|
||||||
from tricircle.cascade_service import scheduler
|
from tricircle.dispatcher import site_manager
|
||||||
|
|
||||||
# import endpoints here
|
# import endpoints here
|
||||||
from tricircle.cascade_service.endpoints.networking import (
|
from tricircle.dispatcher.endpoints.networking import (
|
||||||
CascadeNetworkingServiceEndpoint)
|
CascadeNetworkingServiceEndpoint)
|
||||||
|
from tricircle.dispatcher.endpoints.site import (
|
||||||
|
CascadeSiteServiceEndpoint)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ def _create_main_cascade_server():
|
|||||||
endpoints = [
|
endpoints = [
|
||||||
server_control_endpoint,
|
server_control_endpoint,
|
||||||
CascadeNetworkingServiceEndpoint(),
|
CascadeNetworkingServiceEndpoint(),
|
||||||
|
CascadeSiteServiceEndpoint()
|
||||||
]
|
]
|
||||||
server = oslo_messaging.get_rpc_server(
|
server = oslo_messaging.get_rpc_server(
|
||||||
transport,
|
transport,
|
||||||
@ -64,10 +67,11 @@ def _create_main_cascade_server():
|
|||||||
)
|
)
|
||||||
server_control_endpoint.server = server
|
server_control_endpoint.server = server
|
||||||
|
|
||||||
|
# init _SiteManager to start fake nodes
|
||||||
|
site_manager.get_instance()
|
||||||
|
|
||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def setup_server():
|
def setup_server():
|
||||||
scheduler_server = scheduler.create_server()
|
|
||||||
scheduler_server.start()
|
|
||||||
return _create_main_cascade_server()
|
return _create_main_cascade_server()
|
@ -16,7 +16,11 @@
|
|||||||
from oslo_serialization import jsonutils as json
|
from oslo_serialization import jsonutils as json
|
||||||
|
|
||||||
from tricircle.common.singleton import Singleton
|
from tricircle.common.singleton import Singleton
|
||||||
from tricircle.cascade_service.compute import ComputeHostManager
|
from tricircle.common import utils
|
||||||
|
import tricircle.context as t_context
|
||||||
|
from tricircle.dispatcher.compute import ComputeHostManager
|
||||||
|
from tricircle.db import client
|
||||||
|
from tricircle.db import models
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
@ -101,22 +105,38 @@ class _SiteManager(object):
|
|||||||
self._sites = {}
|
self._sites = {}
|
||||||
self.compute_host_manager = ComputeHostManager(self)
|
self.compute_host_manager = ComputeHostManager(self)
|
||||||
|
|
||||||
# create fake data
|
sites = models.list_sites(t_context.get_db_context(), [])
|
||||||
# NOTE(saggi) replace with DAL access when available
|
for site in sites:
|
||||||
self.create_site("Fake01")
|
# skip top site
|
||||||
self.create_site("Fake02")
|
if not site['az_id']:
|
||||||
|
continue
|
||||||
|
self.create_site(t_context.get_admin_context(), site['site_name'])
|
||||||
|
|
||||||
def create_site(self, site_name):
|
def create_site(self, context, site_name):
|
||||||
"""creates a fake site, in reality the information about available
|
"""creates a fake site, in reality the information about available
|
||||||
sites should be pulled from the DAL and not created at will.
|
sites should be pulled from the DAL and not created at will.
|
||||||
"""
|
"""
|
||||||
# TODO(saggi): thread safty
|
# TODO(saggi): thread safety
|
||||||
if site_name in self._sites:
|
if site_name in self._sites:
|
||||||
raise RuntimeError("Site already exists in site map")
|
raise RuntimeError("Site already exists in site map")
|
||||||
|
|
||||||
|
# TODO(zhiyuan): use DHT to judge whether host this site or not
|
||||||
self._sites[site_name] = Site(site_name)
|
self._sites[site_name] = Site(site_name)
|
||||||
self.compute_host_manager.create_host_adapter(site_name)
|
self.compute_host_manager.create_host_adapter(site_name)
|
||||||
|
|
||||||
|
ag_name = utils.get_ag_name(site_name)
|
||||||
|
top_client = client.Client()
|
||||||
|
aggregates = top_client.list_resources('aggregate', context)
|
||||||
|
for aggregate in aggregates:
|
||||||
|
if aggregate['name'] == ag_name:
|
||||||
|
if site_name in aggregate['hosts']:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
top_client.action_resources('aggregate', context,
|
||||||
|
'add_host', aggregate['id'],
|
||||||
|
site_name)
|
||||||
|
return
|
||||||
|
|
||||||
def get_site(self, site_name):
|
def get_site(self, site_name):
|
||||||
return self._sites[site_name]
|
return self._sites[site_name]
|
||||||
|
|
@ -70,6 +70,14 @@ class FakeClient(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def action_fake_res(self, name, rename):
|
||||||
|
if self.endpoint != FAKE_URL:
|
||||||
|
raise FakeException()
|
||||||
|
for res in FAKE_RESOURCES:
|
||||||
|
if res['name'] == name:
|
||||||
|
res['name'] = rename
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class FakeResHandle(resource_handle.ResourceHandle):
|
class FakeResHandle(resource_handle.ResourceHandle):
|
||||||
def _get_client(self, cxt):
|
def _get_client(self, cxt):
|
||||||
@ -100,6 +108,14 @@ class FakeResHandle(resource_handle.ResourceHandle):
|
|||||||
self.endpoint_url = None
|
self.endpoint_url = None
|
||||||
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
||||||
|
|
||||||
|
def handle_action(self, cxt, resource, action, name, rename):
|
||||||
|
try:
|
||||||
|
cli = self._get_client(cxt)
|
||||||
|
cli.action_fake_res(name, rename)
|
||||||
|
except FakeException:
|
||||||
|
self.endpoint_url = None
|
||||||
|
raise exception.EndpointNotAvailable(FAKE_TYPE, cli.endpoint)
|
||||||
|
|
||||||
|
|
||||||
class ClientTest(unittest.TestCase):
|
class ClientTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -133,6 +149,7 @@ class ClientTest(unittest.TestCase):
|
|||||||
self.client.operation_resources_map['list'].add(FAKE_RESOURCE)
|
self.client.operation_resources_map['list'].add(FAKE_RESOURCE)
|
||||||
self.client.operation_resources_map['create'].add(FAKE_RESOURCE)
|
self.client.operation_resources_map['create'].add(FAKE_RESOURCE)
|
||||||
self.client.operation_resources_map['delete'].add(FAKE_RESOURCE)
|
self.client.operation_resources_map['delete'].add(FAKE_RESOURCE)
|
||||||
|
self.client.operation_resources_map['action'].add(FAKE_RESOURCE)
|
||||||
self.client.service_handle_map[FAKE_TYPE] = FakeResHandle(None)
|
self.client.service_handle_map[FAKE_TYPE] = FakeResHandle(None)
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
@ -160,6 +177,12 @@ class ClientTest(unittest.TestCase):
|
|||||||
resources = self.client.list_resources(FAKE_RESOURCE, self.context)
|
resources = self.client.list_resources(FAKE_RESOURCE, self.context)
|
||||||
self.assertEqual(resources, [{'name': 'res2'}])
|
self.assertEqual(resources, [{'name': 'res2'}])
|
||||||
|
|
||||||
|
def test_action(self):
|
||||||
|
self.client.action_resources(FAKE_RESOURCE, self.context,
|
||||||
|
'rename', 'res1', 'res3')
|
||||||
|
resources = self.client.list_resources(FAKE_RESOURCE, self.context)
|
||||||
|
self.assertEqual(resources, [{'name': 'res3'}, {'name': 'res2'}])
|
||||||
|
|
||||||
def test_list_endpoint_not_found(self):
|
def test_list_endpoint_not_found(self):
|
||||||
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
|
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
|
||||||
group='client')
|
group='client')
|
||||||
|
Loading…
Reference in New Issue
Block a user