From b4e8c418a275ef590314243f48ed77124c038971 Mon Sep 17 00:00:00 2001 From: Zhenguo Niu Date: Mon, 20 Mar 2017 20:08:12 +0800 Subject: [PATCH] Split scheduler out of engine service Change-Id: Ib57f295b0362e4fc17a3ff5bfb539830f6ac1875 --- devstack/plugin.sh | 5 +- devstack/settings | 4 +- mogan/cmd/engine.py | 5 +- mogan/cmd/scheduler.py | 40 ++++++++ mogan/common/constants.py | 3 +- mogan/common/service.py | 4 +- mogan/conf/engine.py | 4 - mogan/conf/scheduler.py | 7 +- mogan/engine/base_manager.py | 5 +- mogan/engine/flows/create_instance.py | 9 +- mogan/engine/manager.py | 4 - mogan/engine/rpcapi.py | 2 +- mogan/{engine => }/scheduler/__init__.py | 0 mogan/{engine => }/scheduler/base_filter.py | 2 +- mogan/{engine => }/scheduler/base_handler.py | 0 mogan/{engine => }/scheduler/base_weight.py | 2 +- mogan/{engine => }/scheduler/driver.py | 0 .../scheduler/filter_scheduler.py | 37 +++++--- .../scheduler/filters/__init__.py | 2 +- .../filters/availability_zone_filter.py | 2 +- .../scheduler/filters/capabilities_filter.py | 4 +- .../scheduler/filters/extra_specs_ops.py | 0 .../scheduler/filters/instance_type_filter.py | 2 +- .../scheduler/filters/json_filter.py | 2 +- .../scheduler/filters/ports_filter.py | 2 +- mogan/scheduler/manager.py | 58 ++++++++++++ mogan/{engine => }/scheduler/node_manager.py | 8 +- mogan/scheduler/rpcapi.py | 58 ++++++++++++ .../scheduler/scheduler_options.py | 0 .../scheduler/weights/__init__.py | 2 +- mogan/{engine => }/scheduler/weights/port.py | 2 +- mogan/tests/unit/common/test_service.py | 7 +- .../engine/flows/test_create_instance_flow.py | 7 +- .../unit/{engine => }/scheduler/__init__.py | 0 .../unit/{engine => }/scheduler/fakes.py | 4 +- .../scheduler/test_base_filter.py | 2 +- .../scheduler/test_node_manager.py | 8 +- mogan/tests/unit/scheduler/test_rpcapi.py | 94 +++++++++++++++++++ .../scheduler/test_scheduler_options.py | 2 +- .../{engine => }/scheduler/test_weights.py | 2 +- setup.cfg | 19 ++-- 41 files changed, 336 insertions(+), 84 deletions(-) create mode 100644 mogan/cmd/scheduler.py rename mogan/{engine => }/scheduler/__init__.py (100%) rename mogan/{engine => }/scheduler/base_filter.py (99%) rename mogan/{engine => }/scheduler/base_handler.py (100%) rename mogan/{engine => }/scheduler/base_weight.py (98%) rename mogan/{engine => }/scheduler/driver.py (100%) rename mogan/{engine => }/scheduler/filter_scheduler.py (83%) rename mogan/{engine => }/scheduler/filters/__init__.py (96%) rename mogan/{engine => }/scheduler/filters/availability_zone_filter.py (96%) rename mogan/{engine => }/scheduler/filters/capabilities_filter.py (96%) rename mogan/{engine => }/scheduler/filters/extra_specs_ops.py (100%) rename mogan/{engine => }/scheduler/filters/instance_type_filter.py (96%) rename mogan/{engine => }/scheduler/filters/json_filter.py (99%) rename mogan/{engine => }/scheduler/filters/ports_filter.py (98%) create mode 100644 mogan/scheduler/manager.py rename mogan/{engine => }/scheduler/node_manager.py (95%) create mode 100644 mogan/scheduler/rpcapi.py rename mogan/{engine => }/scheduler/scheduler_options.py (100%) rename mogan/{engine => }/scheduler/weights/__init__.py (96%) rename mogan/{engine => }/scheduler/weights/port.py (96%) rename mogan/tests/unit/{engine => }/scheduler/__init__.py (100%) rename mogan/tests/unit/{engine => }/scheduler/fakes.py (91%) rename mogan/tests/unit/{engine => }/scheduler/test_base_filter.py (99%) rename mogan/tests/unit/{engine => }/scheduler/test_node_manager.py (94%) create mode 100644 mogan/tests/unit/scheduler/test_rpcapi.py rename mogan/tests/unit/{engine => }/scheduler/test_scheduler_options.py (98%) rename mogan/tests/unit/{engine => }/scheduler/test_weights.py (97%) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 9cd99f1d..ed02a411 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -144,13 +144,14 @@ function install_mogan_pythonclient { # start_mogan - Start running processes, including screen function start_mogan { - if is_service_enabled mogan-api && is_service_enabled mogan-engine ; then + if is_service_enabled mogan-api && is_service_enabled mogan-engine && is_service_enabled mogan-scheduler; then echo_summary "Installing all mogan services in separate processes" run_process mogan-api "${MOGAN_BIN_DIR}/mogan-api --config-file ${MOGAN_CONF_DIR}/mogan.conf" if ! wait_for_service ${SERVICE_TIMEOUT} ${MOGAN_SERVICE_PROTOCOL}://${MOGAN_SERVICE_HOST}:${MOGAN_SERVICE_PORT}; then die $LINENO "mogan-api did not start" fi run_process mogan-engine "${MOGAN_BIN_DIR}/mogan-engine --config-file ${MOGAN_CONF_DIR}/mogan.conf" + run_process mogan-scheduler "${MOGAN_BIN_DIR}/mogan-scheduler --config-file ${MOGAN_CONF_DIR}/mogan.conf" fi } @@ -158,7 +159,7 @@ function start_mogan { # stop_mogan - Stop running processes function stop_mogan { # Kill the Mogan screen windows - for serv in mogan-api mogan-engine; do + for serv in mogan-api mogan-engine mogan-scheduler; do stop_process $serv done } diff --git a/devstack/settings b/devstack/settings index 2086167e..a38afa57 100644 --- a/devstack/settings +++ b/devstack/settings @@ -2,8 +2,8 @@ # We have to add Mogan to enabled services for run_process to work # Now we just support to run services in separate processes and screens: -# enable_service mogan mogan-api mogan-engine -enable_service mogan mogan-api mogan-engine +# enable_service mogan mogan-api mogan-engine mogan-scheduler +enable_service mogan mogan-api mogan-engine mogan-scheduler # Set up default repos MOGAN_REPO=${MOGAN_REPO:-${GIT_BASE}/openstack/mogan.git} diff --git a/mogan/cmd/engine.py b/mogan/cmd/engine.py index 1323301e..7ef34af1 100644 --- a/mogan/cmd/engine.py +++ b/mogan/cmd/engine.py @@ -32,10 +32,9 @@ def main(): # Parse config file and command line options, then start logging mogan_service.prepare_service(sys.argv) - mgr = mogan_service.RPCService(CONF.host, - 'mogan.engine.manager', + mgr = mogan_service.RPCService('mogan.engine.manager', 'EngineManager', - constants.MANAGER_TOPIC) + constants.ENGINE_TOPIC) launcher = service.launch(CONF, mgr) launcher.wait() diff --git a/mogan/cmd/scheduler.py b/mogan/cmd/scheduler.py new file mode 100644 index 00000000..7fd287b1 --- /dev/null +++ b/mogan/cmd/scheduler.py @@ -0,0 +1,40 @@ +# Copyright 2017 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. + +""" +The Mogan Scheduler Service +""" + +import sys + +from oslo_config import cfg +from oslo_service import service + +from mogan.common import constants +from mogan.common import service as mogan_service + +CONF = cfg.CONF + + +def main(): + # Parse config file and command line options, then start logging + mogan_service.prepare_service(sys.argv) + + mgr = mogan_service.RPCService('mogan.scheduler.manager', + 'SchedulerManager', + constants.SCHEDULER_TOPIC) + + launcher = service.launch(CONF, mgr) + launcher.wait() diff --git a/mogan/common/constants.py b/mogan/common/constants.py index d2f3abfa..c2c8614c 100644 --- a/mogan/common/constants.py +++ b/mogan/common/constants.py @@ -14,4 +14,5 @@ # under the License. -MANAGER_TOPIC = 'mogan.engine_manager' +ENGINE_TOPIC = 'mogan-engine' +SCHEDULER_TOPIC = 'mogan-scheduler' diff --git a/mogan/common/service.py b/mogan/common/service.py index be768ec3..4fce60c8 100644 --- a/mogan/common/service.py +++ b/mogan/common/service.py @@ -36,9 +36,9 @@ LOG = log.getLogger(__name__) class RPCService(service.Service): - def __init__(self, host, manager_module, manager_class, topic): + def __init__(self, manager_module, manager_class, topic, host=None): super(RPCService, self).__init__() - self.host = host + self.host = host or CONF.host manager_module = importutils.try_import(manager_module) manager_class = getattr(manager_module, manager_class) self.manager = manager_class(host, topic) diff --git a/mogan/conf/engine.py b/mogan/conf/engine.py index afe8cb91..3013a085 100644 --- a/mogan/conf/engine.py +++ b/mogan/conf/engine.py @@ -34,10 +34,6 @@ opts = [ default=60, help=_('Interval between syncing the resources from underlying ' 'hypervisor, in seconds.')), - cfg.StrOpt('scheduler_driver', - default='mogan.engine.scheduler.filter_scheduler.' - 'FilterScheduler', - help=_('Default scheduler driver to use')), cfg.StrOpt('default_schedule_zone', help=_("Availability zone to use for scheduling when user " "doesn't specify one.")), diff --git a/mogan/conf/scheduler.py b/mogan/conf/scheduler.py index 220c39d0..6ea809e4 100644 --- a/mogan/conf/scheduler.py +++ b/mogan/conf/scheduler.py @@ -19,11 +19,10 @@ from mogan.common.i18n import _ opts = [ cfg.StrOpt('scheduler_driver', - default='mogan.engine.scheduler.filter_scheduler.' - 'FilterScheduler', + default='mogan.scheduler.filter_scheduler.FilterScheduler', help=_('Default scheduler driver to use')), cfg.StrOpt('scheduler_node_manager', - default='mogan.engine.scheduler.node_manager.NodeManager', + default='mogan.scheduler.node_manager.NodeManager', help=_('The scheduler node manager class to use')), cfg.IntOpt('scheduler_max_attempts', default=3, @@ -47,7 +46,7 @@ opts = [ help=_('Which weigher class names to use for weighing ' 'nodes.')), cfg.StrOpt('scheduler_weight_handler', - default='mogan.engine.scheduler.weights.' + default='mogan.scheduler.weights.' 'OrderedNodeWeightHandler', help=_('Which handler to use for selecting the node after ' 'weighing')), diff --git a/mogan/engine/base_manager.py b/mogan/engine/base_manager.py index 5408e265..469d79e8 100644 --- a/mogan/engine/base_manager.py +++ b/mogan/engine/base_manager.py @@ -17,7 +17,6 @@ from eventlet import greenpool from oslo_service import periodic_task -from oslo_utils import importutils from mogan.common.i18n import _ from mogan.conf import CONF @@ -25,6 +24,7 @@ from mogan.db import api as dbapi from mogan.engine.baremetal import driver from mogan.engine import rpcapi from mogan import network +from mogan.scheduler import rpcapi as scheduler_rpcapi class BaseEngineManager(periodic_task.PeriodicTasks): @@ -36,8 +36,7 @@ class BaseEngineManager(periodic_task.PeriodicTasks): self.host = host self.topic = topic self.network_api = network.API() - scheduler_driver = CONF.scheduler.scheduler_driver - self.scheduler = importutils.import_object(scheduler_driver) + self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI() self.driver = driver.load_engine_driver(CONF.engine.engine_driver) self.engine_rpcapi = rpcapi.EngineAPI() self._sync_power_pool = greenpool.GreenPool( diff --git a/mogan/engine/flows/create_instance.py b/mogan/engine/flows/create_instance.py index 219fb1ef..8f892c9c 100644 --- a/mogan/engine/flows/create_instance.py +++ b/mogan/engine/flows/create_instance.py @@ -47,11 +47,10 @@ class ScheduleCreateInstanceTask(flow_utils.MoganTask): self.manager = manager def execute(self, context, instance, request_spec, filter_properties): - with self.manager._lock: - top_node = self.manager.scheduler.schedule( - context, - request_spec, - filter_properties) + top_node = self.manager.scheduler_rpcapi.select_destinations( + context, + request_spec, + filter_properties) instance.node_uuid = top_node instance.save() diff --git a/mogan/engine/manager.py b/mogan/engine/manager.py index 1a9e985f..b275c704 100644 --- a/mogan/engine/manager.py +++ b/mogan/engine/manager.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import threading - from oslo_log import log import oslo_messaging as messaging from oslo_service import periodic_task @@ -45,8 +43,6 @@ class EngineManager(base_manager.BaseEngineManager): RPC_API_VERSION = '1.0' target = messaging.Target(version=RPC_API_VERSION) - # TODO(zhenguo): Move lock to scheduler - _lock = threading.Lock() def _get_compute_port(self, context, port_uuid): """Gets compute port by the uuid.""" diff --git a/mogan/engine/rpcapi.py b/mogan/engine/rpcapi.py index 0656f049..32699493 100644 --- a/mogan/engine/rpcapi.py +++ b/mogan/engine/rpcapi.py @@ -40,7 +40,7 @@ class EngineAPI(object): super(EngineAPI, self).__init__() self.topic = topic if self.topic is None: - self.topic = constants.MANAGER_TOPIC + self.topic = constants.ENGINE_TOPIC target = messaging.Target(topic=self.topic, version='1.0') diff --git a/mogan/engine/scheduler/__init__.py b/mogan/scheduler/__init__.py similarity index 100% rename from mogan/engine/scheduler/__init__.py rename to mogan/scheduler/__init__.py diff --git a/mogan/engine/scheduler/base_filter.py b/mogan/scheduler/base_filter.py similarity index 99% rename from mogan/engine/scheduler/base_filter.py rename to mogan/scheduler/base_filter.py index e21bee8d..5bf13a46 100644 --- a/mogan/engine/scheduler/base_filter.py +++ b/mogan/scheduler/base_filter.py @@ -20,7 +20,7 @@ from oslo_log import log as logging import six from mogan.common.i18n import _LI -from mogan.engine.scheduler import base_handler +from mogan.scheduler import base_handler LOG = logging.getLogger(__name__) diff --git a/mogan/engine/scheduler/base_handler.py b/mogan/scheduler/base_handler.py similarity index 100% rename from mogan/engine/scheduler/base_handler.py rename to mogan/scheduler/base_handler.py diff --git a/mogan/engine/scheduler/base_weight.py b/mogan/scheduler/base_weight.py similarity index 98% rename from mogan/engine/scheduler/base_weight.py rename to mogan/scheduler/base_weight.py index ea39f5e5..e6badcf2 100644 --- a/mogan/engine/scheduler/base_weight.py +++ b/mogan/scheduler/base_weight.py @@ -21,7 +21,7 @@ import abc import six -from mogan.engine.scheduler import base_handler +from mogan.scheduler import base_handler def normalize(weight_list, minval=None, maxval=None): diff --git a/mogan/engine/scheduler/driver.py b/mogan/scheduler/driver.py similarity index 100% rename from mogan/engine/scheduler/driver.py rename to mogan/scheduler/driver.py diff --git a/mogan/engine/scheduler/filter_scheduler.py b/mogan/scheduler/filter_scheduler.py similarity index 83% rename from mogan/engine/scheduler/filter_scheduler.py rename to mogan/scheduler/filter_scheduler.py index b0932f11..557f2b6e 100644 --- a/mogan/engine/scheduler/filter_scheduler.py +++ b/mogan/scheduler/filter_scheduler.py @@ -24,8 +24,9 @@ from mogan.common import exception from mogan.common.i18n import _ from mogan.common.i18n import _LE from mogan.common.i18n import _LW -from mogan.engine.scheduler import driver -from mogan.engine.scheduler import scheduler_options +from mogan.common import utils +from mogan.scheduler import driver +from mogan.scheduler import scheduler_options CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -170,18 +171,28 @@ class FilterScheduler(driver.Scheduler): return weighed_nodes def schedule(self, context, request_spec, filter_properties=None): - weighed_nodes = self._get_weighted_candidates(context, request_spec, - filter_properties) - if not weighed_nodes: - LOG.warning(_LW('No weighed nodes found for instance ' - 'with properties: %s'), - request_spec.get('instance_type')) - raise exception.NoValidNode(_("No weighed nodes available")) - top_node = self._choose_top_node(weighed_nodes, request_spec) - top_node.obj.consume_from_request(context) - self._add_retry_node(filter_properties, top_node.obj.node) - return top_node.obj.node + # TODO(zhenguo): Scheduler API is inherently multi-threaded as every + # incoming RPC message will be dispatched in it's own green thread. + # So we add a syncronized here to make sure the shared node states + # consistent, but lock the whole schedule process is not a good choice, + # we need to improve this. + @utils.synchronized('schedule') + def _schedule(self, context, request_spec, filter_properties): + weighed_nodes = self._get_weighted_candidates( + context, request_spec, filter_properties) + if not weighed_nodes: + LOG.warning(_LW('No weighed nodes found for instance ' + 'with properties: %s'), + request_spec.get('instance_type')) + raise exception.NoValidNode(_("No weighed nodes available")) + + top_node = self._choose_top_node(weighed_nodes, request_spec) + top_node.obj.consume_from_request(context) + self._add_retry_node(filter_properties, top_node.obj.node) + return top_node.obj.node + + return _schedule(self, context, request_spec, filter_properties) def _choose_top_node(self, weighed_nodes, request_spec): return weighed_nodes[0] diff --git a/mogan/engine/scheduler/filters/__init__.py b/mogan/scheduler/filters/__init__.py similarity index 96% rename from mogan/engine/scheduler/filters/__init__.py rename to mogan/scheduler/filters/__init__.py index b468f30f..8b9c8bec 100644 --- a/mogan/engine/scheduler/filters/__init__.py +++ b/mogan/scheduler/filters/__init__.py @@ -17,7 +17,7 @@ Scheduler node filters """ -from mogan.engine.scheduler import base_filter +from mogan.scheduler import base_filter class BaseNodeFilter(base_filter.BaseFilter): diff --git a/mogan/engine/scheduler/filters/availability_zone_filter.py b/mogan/scheduler/filters/availability_zone_filter.py similarity index 96% rename from mogan/engine/scheduler/filters/availability_zone_filter.py rename to mogan/scheduler/filters/availability_zone_filter.py index fd7f64f6..1f827166 100644 --- a/mogan/engine/scheduler/filters/availability_zone_filter.py +++ b/mogan/scheduler/filters/availability_zone_filter.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from mogan.engine.scheduler import filters +from mogan.scheduler import filters class AvailabilityZoneFilter(filters.BaseNodeFilter): diff --git a/mogan/engine/scheduler/filters/capabilities_filter.py b/mogan/scheduler/filters/capabilities_filter.py similarity index 96% rename from mogan/engine/scheduler/filters/capabilities_filter.py rename to mogan/scheduler/filters/capabilities_filter.py index 1c6b5bf6..38a83f74 100644 --- a/mogan/engine/scheduler/filters/capabilities_filter.py +++ b/mogan/scheduler/filters/capabilities_filter.py @@ -15,8 +15,8 @@ from oslo_log import log as logging -from mogan.engine.scheduler import filters -from mogan.engine.scheduler.filters import extra_specs_ops +from mogan.scheduler import filters +from mogan.scheduler.filters import extra_specs_ops LOG = logging.getLogger(__name__) diff --git a/mogan/engine/scheduler/filters/extra_specs_ops.py b/mogan/scheduler/filters/extra_specs_ops.py similarity index 100% rename from mogan/engine/scheduler/filters/extra_specs_ops.py rename to mogan/scheduler/filters/extra_specs_ops.py diff --git a/mogan/engine/scheduler/filters/instance_type_filter.py b/mogan/scheduler/filters/instance_type_filter.py similarity index 96% rename from mogan/engine/scheduler/filters/instance_type_filter.py rename to mogan/scheduler/filters/instance_type_filter.py index 2c4d0e90..e05dd6dc 100644 --- a/mogan/engine/scheduler/filters/instance_type_filter.py +++ b/mogan/scheduler/filters/instance_type_filter.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from mogan.engine.scheduler import filters +from mogan.scheduler import filters class InstanceTypeFilter(filters.BaseNodeFilter): diff --git a/mogan/engine/scheduler/filters/json_filter.py b/mogan/scheduler/filters/json_filter.py similarity index 99% rename from mogan/engine/scheduler/filters/json_filter.py rename to mogan/scheduler/filters/json_filter.py index 02fc7a08..a94da852 100644 --- a/mogan/engine/scheduler/filters/json_filter.py +++ b/mogan/scheduler/filters/json_filter.py @@ -18,7 +18,7 @@ import operator from oslo_serialization import jsonutils import six -from mogan.engine.scheduler import filters +from mogan.scheduler import filters class JsonFilter(filters.BaseNodeFilter): diff --git a/mogan/engine/scheduler/filters/ports_filter.py b/mogan/scheduler/filters/ports_filter.py similarity index 98% rename from mogan/engine/scheduler/filters/ports_filter.py rename to mogan/scheduler/filters/ports_filter.py index 56474f03..447be923 100644 --- a/mogan/engine/scheduler/filters/ports_filter.py +++ b/mogan/scheduler/filters/ports_filter.py @@ -15,7 +15,7 @@ from oslo_log import log as logging -from mogan.engine.scheduler import filters +from mogan.scheduler import filters LOG = logging.getLogger(__name__) diff --git a/mogan/scheduler/manager.py b/mogan/scheduler/manager.py new file mode 100644 index 00000000..94cff717 --- /dev/null +++ b/mogan/scheduler/manager.py @@ -0,0 +1,58 @@ +# Copyright 2017 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 eventlet +import oslo_messaging as messaging +from oslo_service import periodic_task +from oslo_utils import importutils + +from mogan.common import exception +from mogan.conf import CONF + + +class SchedulerManager(periodic_task.PeriodicTasks): + """Mogan Scheduler manager main class.""" + + RPC_API_VERSION = '1.0' + + target = messaging.Target(version=RPC_API_VERSION) + + def __init__(self, topic, host=None): + super(SchedulerManager, self).__init__(CONF) + self.host = host or CONF.host + self.topic = topic + scheduler_driver = CONF.scheduler.scheduler_driver + self.driver = importutils.import_object(scheduler_driver) + self._startup_delay = True + + def init_host(self): + self._startup_delay = False + + def _wait_for_scheduler(self): + while self._startup_delay and not self.driver.is_ready(): + eventlet.sleep(1) + + @messaging.expected_exceptions(exception.NoValidNode) + def select_destinations(self, ctxt, request_spec, filter_properties): + self._wait_for_scheduler() + dests = self.driver.schedule( + ctxt, request_spec, filter_properties) + return dests + + def del_host(self): + pass + + def periodic_tasks(self, context, raise_on_error=False): + return self.run_periodic_tasks(context, raise_on_error=raise_on_error) diff --git a/mogan/engine/scheduler/node_manager.py b/mogan/scheduler/node_manager.py similarity index 95% rename from mogan/engine/scheduler/node_manager.py rename to mogan/scheduler/node_manager.py index 652b71c4..f6b7db26 100644 --- a/mogan/engine/scheduler/node_manager.py +++ b/mogan/scheduler/node_manager.py @@ -22,8 +22,8 @@ from oslo_log import log as logging from oslo_utils import importutils from mogan.common import exception -from mogan.engine.scheduler import filters from mogan import objects +from mogan.scheduler import filters CONF = cfg.CONF @@ -52,12 +52,12 @@ class NodeManager(object): node_state_cls = NodeState def __init__(self): - self.filter_handler = filters.NodeFilterHandler('mogan.engine.' - 'scheduler.filters') + self.filter_handler = filters.NodeFilterHandler( + 'mogan.scheduler.filters') self.filter_classes = self.filter_handler.get_all_classes() self.weight_handler = importutils.import_object( CONF.scheduler.scheduler_weight_handler, - 'mogan.engine.scheduler.weights') + 'mogan.scheduler.weights') self.weight_classes = self.weight_handler.get_all_classes() def _choose_node_filters(self, filter_cls_names): diff --git a/mogan/scheduler/rpcapi.py b/mogan/scheduler/rpcapi.py new file mode 100644 index 00000000..c78b2971 --- /dev/null +++ b/mogan/scheduler/rpcapi.py @@ -0,0 +1,58 @@ +# Copyright 2017 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. + +""" +Client side of the scheduler manager RPC API. +""" + +from oslo_config import cfg +import oslo_messaging as messaging + +from mogan.common import constants +from mogan.common import rpc +from mogan.objects import base as objects_base + +CONF = cfg.CONF + + +class SchedulerAPI(object): + """Client side of the scheduler RPC API. + + API version history: + + | 1.0 - Initial version. + + """ + + RPC_API_VERSION = '1.0' + + def __init__(self, topic=None): + super(SchedulerAPI, self).__init__() + self.topic = topic + if self.topic is None: + self.topic = constants.SCHEDULER_TOPIC + + target = messaging.Target(topic=self.topic, + version='1.0') + serializer = objects_base.MoganObjectSerializer() + self.client = rpc.get_client(target, + version_cap=self.RPC_API_VERSION, + serializer=serializer) + + def select_destinations(self, context, request_spec, filter_properties): + cctxt = self.client.prepare(topic=self.topic, server=CONF.host) + return cctxt.call(context, 'select_destinations', + request_spec=request_spec, + filter_properties=filter_properties) diff --git a/mogan/engine/scheduler/scheduler_options.py b/mogan/scheduler/scheduler_options.py similarity index 100% rename from mogan/engine/scheduler/scheduler_options.py rename to mogan/scheduler/scheduler_options.py diff --git a/mogan/engine/scheduler/weights/__init__.py b/mogan/scheduler/weights/__init__.py similarity index 96% rename from mogan/engine/scheduler/weights/__init__.py rename to mogan/scheduler/weights/__init__.py index 477a83f1..0a9f59e4 100644 --- a/mogan/engine/scheduler/weights/__init__.py +++ b/mogan/scheduler/weights/__init__.py @@ -17,7 +17,7 @@ Scheduler node weights """ -from mogan.engine.scheduler import base_weight +from mogan.scheduler import base_weight class WeighedNode(base_weight.WeighedObject): diff --git a/mogan/engine/scheduler/weights/port.py b/mogan/scheduler/weights/port.py similarity index 96% rename from mogan/engine/scheduler/weights/port.py rename to mogan/scheduler/weights/port.py index 3ae7ab00..7209f0c5 100644 --- a/mogan/engine/scheduler/weights/port.py +++ b/mogan/scheduler/weights/port.py @@ -22,7 +22,7 @@ to a positive number and the weighing has the opposite effect of the default. from oslo_config import cfg -from mogan.engine.scheduler import weights +from mogan.scheduler import weights CONF = cfg.CONF diff --git a/mogan/tests/unit/common/test_service.py b/mogan/tests/unit/common/test_service.py index b1c7c049..ce565743 100644 --- a/mogan/tests/unit/common/test_service.py +++ b/mogan/tests/unit/common/test_service.py @@ -32,11 +32,10 @@ class TestRPCService(base.TestCase): def setUp(self): super(TestRPCService, self).setUp() - host = "fake_host" mgr_module = "mogan.engine.manager" mgr_class = "EngineManager" - self.rpc_svc = service.RPCService(host, mgr_module, mgr_class, - constants.MANAGER_TOPIC) + self.rpc_svc = service.RPCService(mgr_module, mgr_class, + constants.ENGINE_TOPIC) @mock.patch.object(oslo_messaging, 'Target', autospec=True) @mock.patch.object(objects_base, 'MoganObjectSerializer', autospec=True) @@ -47,7 +46,7 @@ class TestRPCService(base.TestCase): self.rpc_svc.handle_signal = mock.MagicMock() self.rpc_svc.start() mock_target.assert_called_once_with(topic=self.rpc_svc.topic, - server="fake_host") + server="fake-mini") mock_ios.assert_called_once_with() mock_init_method.assert_called_once_with(self.rpc_svc.manager) diff --git a/mogan/tests/unit/engine/flows/test_create_instance_flow.py b/mogan/tests/unit/engine/flows/test_create_instance_flow.py index 7dc44c33..33ab5750 100644 --- a/mogan/tests/unit/engine/flows/test_create_instance_flow.py +++ b/mogan/tests/unit/engine/flows/test_create_instance_flow.py @@ -21,8 +21,8 @@ from oslo_utils import uuidutils from mogan.engine.baremetal.ironic import IronicDriver from mogan.engine.flows import create_instance from mogan.engine import manager -from mogan.engine.scheduler import filter_scheduler as scheduler from mogan import objects +from mogan.scheduler import rpcapi as scheduler_rpcapi from mogan.tests import base from mogan.tests.unit.objects import utils as obj_utils @@ -34,13 +34,14 @@ class CreateInstanceFlowTestCase(base.TestCase): self.ctxt = context.get_admin_context() @mock.patch.object(objects.instance.Instance, 'save') - @mock.patch.object(scheduler.FilterScheduler, 'schedule') + @mock.patch.object(scheduler_rpcapi.SchedulerAPI, 'select_destinations') def test_schedule_task_execute(self, mock_schedule, mock_save): fake_uuid = uuidutils.generate_uuid() fake_engine_manager = mock.MagicMock() + sche_rpcapi = scheduler_rpcapi.SchedulerAPI() + fake_engine_manager.scheduler_rpcapi = sche_rpcapi fake_request_spec = mock.MagicMock() fake_filter_props = mock.MagicMock() - fake_engine_manager.scheduler = scheduler.FilterScheduler() task = create_instance.ScheduleCreateInstanceTask( fake_engine_manager) instance_obj = obj_utils.get_test_instance(self.ctxt) diff --git a/mogan/tests/unit/engine/scheduler/__init__.py b/mogan/tests/unit/scheduler/__init__.py similarity index 100% rename from mogan/tests/unit/engine/scheduler/__init__.py rename to mogan/tests/unit/scheduler/__init__.py diff --git a/mogan/tests/unit/engine/scheduler/fakes.py b/mogan/tests/unit/scheduler/fakes.py similarity index 91% rename from mogan/tests/unit/engine/scheduler/fakes.py rename to mogan/tests/unit/scheduler/fakes.py index 9ccbcec6..6cd16d01 100644 --- a/mogan/tests/unit/engine/scheduler/fakes.py +++ b/mogan/tests/unit/scheduler/fakes.py @@ -17,8 +17,8 @@ Fakes For Scheduler tests. """ -from mogan.engine.scheduler import filter_scheduler -from mogan.engine.scheduler import node_manager +from mogan.scheduler import filter_scheduler +from mogan.scheduler import node_manager class FakeFilterScheduler(filter_scheduler.FilterScheduler): diff --git a/mogan/tests/unit/engine/scheduler/test_base_filter.py b/mogan/tests/unit/scheduler/test_base_filter.py similarity index 99% rename from mogan/tests/unit/engine/scheduler/test_base_filter.py rename to mogan/tests/unit/scheduler/test_base_filter.py index 0288e9ba..6fc1775a 100644 --- a/mogan/tests/unit/engine/scheduler/test_base_filter.py +++ b/mogan/tests/unit/scheduler/test_base_filter.py @@ -15,7 +15,7 @@ import mock -from mogan.engine.scheduler import base_filter +from mogan.scheduler import base_filter from mogan.tests import base as test diff --git a/mogan/tests/unit/engine/scheduler/test_node_manager.py b/mogan/tests/unit/scheduler/test_node_manager.py similarity index 94% rename from mogan/tests/unit/engine/scheduler/test_node_manager.py rename to mogan/tests/unit/scheduler/test_node_manager.py index f4dae79d..b33cf10c 100644 --- a/mogan/tests/unit/engine/scheduler/test_node_manager.py +++ b/mogan/tests/unit/scheduler/test_node_manager.py @@ -21,10 +21,10 @@ from oslo_context import context from oslo_versionedobjects import base as object_base from mogan.common import exception -from mogan.engine.scheduler import filters -from mogan.engine.scheduler import node_manager -from mogan.engine.scheduler.node_manager import NodeState from mogan.objects import compute_port +from mogan.scheduler import filters +from mogan.scheduler import node_manager +from mogan.scheduler.node_manager import NodeState from mogan.tests import base as test from mogan.tests.unit.objects import utils as obj_utils @@ -73,7 +73,7 @@ class NodeManagerTestCase(test.TestCase): self.assertEqual(1, len(filter_classes)) self.assertEqual('FakeFilterClass2', filter_classes[0].__name__) - @mock.patch('mogan.engine.scheduler.node_manager.NodeManager.' + @mock.patch('mogan.scheduler.node_manager.NodeManager.' '_choose_node_filters') def test_get_filtered_nodes(self, _mock_choose_node_filters): filter_class = FakeFilterClass1 diff --git a/mogan/tests/unit/scheduler/test_rpcapi.py b/mogan/tests/unit/scheduler/test_rpcapi.py new file mode 100644 index 00000000..034b0b77 --- /dev/null +++ b/mogan/tests/unit/scheduler/test_rpcapi.py @@ -0,0 +1,94 @@ +# +# 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. +""" +Unit Tests for :py:class:`mogan.scheduler.rpcapi.SchedulerAPI`. +""" + +import copy + +import mock +from oslo_config import cfg +from oslo_messaging import _utils as messaging_utils + +from mogan.scheduler import manager as scheduler_manager +from mogan.scheduler import rpcapi as scheduler_rpcapi +from mogan.tests import base as tests_base +from mogan.tests.unit.db import base + +CONF = cfg.CONF + + +class SchedulerRPCAPITestCase(tests_base.TestCase): + + def test_versions_in_sync(self): + self.assertEqual( + scheduler_manager.SchedulerManager.RPC_API_VERSION, + scheduler_rpcapi.SchedulerAPI.RPC_API_VERSION) + + +class RPCAPITestCase(base.DbTestCase): + + def _test_rpcapi(self, method, rpc_method, **kwargs): + rpcapi = scheduler_rpcapi.SchedulerAPI(topic='fake-topic') + + expected_retval = 'hello world' if rpc_method == 'call' else None + + expected_topic = 'fake-topic' + + target = { + "topic": expected_topic, + "server": CONF.host, + "version": kwargs.pop('version', rpcapi.RPC_API_VERSION) + } + expected_msg = copy.deepcopy(kwargs) + + self.fake_args = None + self.fake_kwargs = None + + def _fake_can_send_version_method(version): + return messaging_utils.version_is_compatible( + rpcapi.RPC_API_VERSION, version) + + def _fake_prepare_method(*args, **kwargs): + for kwd in kwargs: + self.assertEqual(kwargs[kwd], target[kwd]) + return rpcapi.client + + def _fake_rpc_method(*args, **kwargs): + self.fake_args = args + self.fake_kwargs = kwargs + if expected_retval: + return expected_retval + + with mock.patch.object(rpcapi.client, + "can_send_version") as mock_can_send_version: + mock_can_send_version.side_effect = _fake_can_send_version_method + with mock.patch.object(rpcapi.client, "prepare") as mock_prepared: + mock_prepared.side_effect = _fake_prepare_method + + with mock.patch.object(rpcapi.client, + rpc_method) as mock_method: + mock_method.side_effect = _fake_rpc_method + retval = getattr(rpcapi, method)(self.context, **kwargs) + self.assertEqual(retval, expected_retval) + expected_args = [self.context, method, expected_msg] + for arg, expected_arg in zip(self.fake_args, + expected_args): + self.assertEqual(arg, expected_arg) + + def test_select_destinations(self): + self._test_rpcapi('select_destinations', + 'call', + version='1.0', + request_spec=None, + filter_properties=None) diff --git a/mogan/tests/unit/engine/scheduler/test_scheduler_options.py b/mogan/tests/unit/scheduler/test_scheduler_options.py similarity index 98% rename from mogan/tests/unit/engine/scheduler/test_scheduler_options.py rename to mogan/tests/unit/scheduler/test_scheduler_options.py index 39ac9386..30297a36 100644 --- a/mogan/tests/unit/engine/scheduler/test_scheduler_options.py +++ b/mogan/tests/unit/scheduler/test_scheduler_options.py @@ -21,7 +21,7 @@ import datetime from oslo_serialization import jsonutils import six -from mogan.engine.scheduler import scheduler_options +from mogan.scheduler import scheduler_options from mogan.tests import base as test diff --git a/mogan/tests/unit/engine/scheduler/test_weights.py b/mogan/tests/unit/scheduler/test_weights.py similarity index 97% rename from mogan/tests/unit/engine/scheduler/test_weights.py rename to mogan/tests/unit/scheduler/test_weights.py index 4f7527a5..fab12aa6 100644 --- a/mogan/tests/unit/engine/scheduler/test_weights.py +++ b/mogan/tests/unit/scheduler/test_weights.py @@ -17,7 +17,7 @@ Tests For Scheduler weights. """ -from mogan.engine.scheduler import base_weight +from mogan.scheduler import base_weight from mogan.tests import base as test diff --git a/setup.cfg b/setup.cfg index efe0eb54..1901e564 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,14 +24,14 @@ packages = mogan [entry_points] -mogan.engine.scheduler.filters = - AvailabilityZoneFilter = mogan.engine.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter - InstanceTypeFilter = mogan.engine.scheduler.filters.instance_type_filter:InstanceTypeFilter - CapabilitiesFilter = mogan.engine.scheduler.filters.capabilities_filter:CapabilitiesFilter - PortsFilter = mogan.engine.scheduler.filters.ports_filter:PortsFilter - JsonFilter = mogan.engine.scheduler.filters.json_filter:JsonFilter -mogan.engine.scheduler.weights = - PortWeigher = mogan.engine.scheduler.weights.port:PortWeigher +mogan.scheduler.filters = + AvailabilityZoneFilter = mogan.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter + InstanceTypeFilter = mogan.scheduler.filters.instance_type_filter:InstanceTypeFilter + CapabilitiesFilter = mogan.scheduler.filters.capabilities_filter:CapabilitiesFilter + PortsFilter = mogan.scheduler.filters.ports_filter:PortsFilter + JsonFilter = mogan.scheduler.filters.json_filter:JsonFilter +mogan.scheduler.weights = + PortWeigher = mogan.scheduler.weights.port:PortWeigher oslo.config.opts = mogan = mogan.conf.opts:list_opts @@ -41,8 +41,9 @@ oslo.policy.policies = console_scripts = mogan-api = mogan.cmd.api:main - mogan-engine = mogan.cmd.engine:main mogan-dbsync = mogan.cmd.dbsync:main + mogan-engine = mogan.cmd.engine:main + mogan-scheduler = mogan.cmd.scheduler:main mogan.database.migration_backend = sqlalchemy = mogan.db.sqlalchemy.migration