From e3198d25a571b36a3715f5df1bc54718f3ad79ce Mon Sep 17 00:00:00 2001 From: Jean-Emile DARTOIS Date: Tue, 26 Jan 2016 15:31:18 +0100 Subject: [PATCH] Add Voluptuous to validate the action parameters We want a simplest way to validate the input parameters of an Action through a schema. APIImpact DocImpact Partially implements: blueprint watcher-add-actions-via-conf Change-Id: I139775f467fe7778c7354b0cfacf796fc27ffcb2 --- requirements.txt | 1 + watcher/api/controllers/v1/action.py | 5 +- watcher/applier/actions/base.py | 23 +- .../actions/change_nova_service_state.py | 21 +- watcher/applier/actions/factory.py | 7 +- watcher/applier/actions/migration.py | 41 +++- watcher/applier/actions/nop.py | 13 +- watcher/applier/actions/sleep.py | 14 +- watcher/common/exception.py | 4 + watcher/db/sqlalchemy/models.py | 1 - watcher/decision_engine/planner/default.py | 4 - watcher/decision_engine/solution/base.py | 4 +- watcher/decision_engine/solution/default.py | 19 +- .../strategies/basic_consolidation.py | 8 +- .../strategy/strategies/dummy_strategy.py | 10 +- .../strategies/outlet_temp_control.py | 34 +-- watcher/locale/fr/LC_MESSAGES/watcher.po | 186 +++++++++------ watcher/locale/watcher.pot | 162 ++++++++----- .../cluster_model_collector/nova.py | 32 ++- watcher/objects/action.py | 1 - .../actions/test_change_nova_service_state.py | 54 +++++ .../tests/applier/actions/test_migration.py | 78 ++++++ watcher/tests/applier/actions/test_sleep.py | 42 ++++ .../test_default_workflow_engine.py | 32 +-- watcher/tests/db/utils.py | 10 +- .../decision_engine/model/test_mapping.py | 45 ++-- .../planner/test_default_planner.py | 6 +- .../solution/test_default_solution.py | 16 +- .../strategies/faker_cluster_state.py | 223 ++++-------------- .../strategies/test_basic_consolidation.py | 81 ++++--- .../strategies/test_dummy_strategy.py | 27 ++- .../strategies/test_outlet_temp_control.py | 70 +++--- 32 files changed, 771 insertions(+), 503 deletions(-) create mode 100644 watcher/tests/applier/actions/test_change_nova_service_state.py create mode 100644 watcher/tests/applier/actions/test_migration.py create mode 100644 watcher/tests/applier/actions/test_sleep.py diff --git a/requirements.txt b/requirements.txt index 81104e6ff..842d3c3f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ oslo.utils>=3.4.0 # Apache-2.0 PasteDeploy>=1.5.0 pbr>=1.6 pecan>=1.0.0 +voluptuous>=0.8.6 # BSD License python-ceilometerclient>=2.2.1 python-cinderclient>=1.3.1 python-glanceclient>=1.2.0 diff --git a/watcher/api/controllers/v1/action.py b/watcher/api/controllers/v1/action.py index 91e5c973e..5cbc89f6d 100644 --- a/watcher/api/controllers/v1/action.py +++ b/watcher/api/controllers/v1/action.py @@ -129,13 +129,10 @@ class Action(base.APIBase): alarm = types.uuid """An alarm UUID related to this action""" - applies_to = wtypes.text - """Applies to""" - action_type = wtypes.text """Action type""" - input_parameters = wtypes.DictType(wtypes.text, wtypes.text) + input_parameters = types.jsontype """One or more key/value pairs """ next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid, diff --git a/watcher/applier/actions/base.py b/watcher/applier/actions/base.py index e70020c65..533ddeae5 100644 --- a/watcher/applier/actions/base.py +++ b/watcher/applier/actions/base.py @@ -27,10 +27,15 @@ from watcher.common import clients @six.add_metaclass(abc.ABCMeta) class BaseAction(object): + # NOTE(jed) by convention we decided + # that the attribute "resource_id" is the unique id of + # the resource to which the Action applies to allow us to use it in the + # watcher dashboard and will be nested in input_parameters + RESOURCE_ID = 'resource_id' + def __init__(self, osc=None): """:param osc: an OpenStackClients instance""" self._input_parameters = {} - self._applies_to = "" self._osc = osc @property @@ -48,12 +53,8 @@ class BaseAction(object): self._input_parameters = p @property - def applies_to(self): - return self._applies_to - - @applies_to.setter - def applies_to(self, a): - self._applies_to = a + def resource_id(self): + return self.input_parameters[self.RESOURCE_ID] @abc.abstractmethod def execute(self): @@ -70,3 +71,11 @@ class BaseAction(object): @abc.abstractmethod def postcondition(self): raise NotImplementedError() + + @abc.abstractproperty + def schema(self): + raise NotImplementedError() + + def validate_parameters(self): + self.schema(self.input_parameters) + return True diff --git a/watcher/applier/actions/change_nova_service_state.py b/watcher/applier/actions/change_nova_service_state.py index ecb61edb0..53eb02822 100644 --- a/watcher/applier/actions/change_nova_service_state.py +++ b/watcher/applier/actions/change_nova_service_state.py @@ -16,7 +16,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +import six +import voluptuous from watcher._i18n import _ from watcher.applier.actions import base @@ -27,13 +28,27 @@ from watcher.decision_engine.model import hypervisor_state as hstate class ChangeNovaServiceState(base.BaseAction): + STATE = 'state' + + @property + def schema(self): + return voluptuous.Schema({ + voluptuous.Required(self.RESOURCE_ID): + voluptuous.All( + voluptuous.Any(*six.string_types), + voluptuous.Length(min=1)), + voluptuous.Required(self.STATE): + voluptuous.Any(*[state.value + for state in list(hstate.HypervisorState)]), + }) + @property def host(self): - return self.applies_to + return self.resource_id @property def state(self): - return self.input_parameters.get('state') + return self.input_parameters.get(self.STATE) def execute(self): target_state = None diff --git a/watcher/applier/actions/factory.py b/watcher/applier/actions/factory.py index 24e0ecaba..23dd8f158 100644 --- a/watcher/applier/actions/factory.py +++ b/watcher/applier/actions/factory.py @@ -33,5 +33,10 @@ class ActionFactory(object): loaded_action = self.action_loader.load(name=object_action.action_type, osc=osc) loaded_action.input_parameters = object_action.input_parameters - loaded_action.applies_to = object_action.applies_to + LOG.debug("Checking the input parameters") + # NOTE(jed) if we change the schema of an action and we try to reload + # an older version of the Action, the validation can fail. + # We need to add the versioning of an Action or a migration tool. + # We can also create an new Action which extends the previous one. + loaded_action.validate_parameters() return loaded_action diff --git a/watcher/applier/actions/migration.py b/watcher/applier/actions/migration.py index f25dc0aa3..7594d81ab 100644 --- a/watcher/applier/actions/migration.py +++ b/watcher/applier/actions/migration.py @@ -18,30 +18,63 @@ # from oslo_log import log +import six +import voluptuous +from watcher._i18n import _ from watcher.applier.actions import base from watcher.common import exception from watcher.common import nova_helper +from watcher.common import utils LOG = log.getLogger(__name__) class Migrate(base.BaseAction): + + # input parameters constants + MIGRATION_TYPE = 'migration_type' + LIVE_MIGRATION = 'live' + DST_HYPERVISOR = 'dst_hypervisor' + SRC_HYPERVISOR = 'src_hypervisor' + + def check_resource_id(self, value): + if (value is not None and + len(value) > 0 and not + utils.is_uuid_like(value)): + raise voluptuous.Invalid(_("The parameter" + " resource_id is invalid.")) + + @property + def schema(self): + return voluptuous.Schema({ + voluptuous.Required(self.RESOURCE_ID): self.check_resource_id, + voluptuous.Required(self.MIGRATION_TYPE, + default=self.LIVE_MIGRATION): + voluptuous.Any(*[self.LIVE_MIGRATION]), + voluptuous.Required(self.DST_HYPERVISOR): + voluptuous.All(voluptuous.Any(*six.string_types), + voluptuous.Length(min=1)), + voluptuous.Required(self.SRC_HYPERVISOR): + voluptuous.All(voluptuous.Any(*six.string_types), + voluptuous.Length(min=1)), + }) + @property def instance_uuid(self): - return self.applies_to + return self.resource_id @property def migration_type(self): - return self.input_parameters.get('migration_type') + return self.input_parameters.get(self.MIGRATION_TYPE) @property def dst_hypervisor(self): - return self.input_parameters.get('dst_hypervisor') + return self.input_parameters.get(self.DST_HYPERVISOR) @property def src_hypervisor(self): - return self.input_parameters.get('src_hypervisor') + return self.input_parameters.get(self.SRC_HYPERVISOR) def migrate(self, destination): nova = nova_helper.NovaHelper(osc=self.osc) diff --git a/watcher/applier/actions/nop.py b/watcher/applier/actions/nop.py index afefc0d7d..64764762c 100644 --- a/watcher/applier/actions/nop.py +++ b/watcher/applier/actions/nop.py @@ -18,6 +18,8 @@ # from oslo_log import log +import six +import voluptuous from watcher.applier.actions import base @@ -27,9 +29,18 @@ LOG = log.getLogger(__name__) class Nop(base.BaseAction): + MESSAGE = 'message' + + @property + def schema(self): + return voluptuous.Schema({ + voluptuous.Required(self.MESSAGE): voluptuous.Any( + voluptuous.Any(*six.string_types), None) + }) + @property def message(self): - return self.input_parameters.get('message') + return self.input_parameters.get(self.MESSAGE) def execute(self): LOG.debug("executing action NOP message:%s ", self.message) diff --git a/watcher/applier/actions/sleep.py b/watcher/applier/actions/sleep.py index 8256e8c28..4bb94e2ff 100644 --- a/watcher/applier/actions/sleep.py +++ b/watcher/applier/actions/sleep.py @@ -18,19 +18,29 @@ # import time + from oslo_log import log +import voluptuous from watcher.applier.actions import base - LOG = log.getLogger(__name__) class Sleep(base.BaseAction): + DURATION = 'duration' + + @property + def schema(self): + return voluptuous.Schema({ + voluptuous.Required(self.DURATION, default=1): + voluptuous.All(float, voluptuous.Range(min=0)) + }) + @property def duration(self): - return int(self.input_parameters.get('duration')) + return int(self.input_parameters.get(self.DURATION)) def execute(self): LOG.debug("Starting action Sleep duration:%s ", self.duration) diff --git a/watcher/common/exception.py b/watcher/common/exception.py index 7043ccb46..45a657481 100644 --- a/watcher/common/exception.py +++ b/watcher/common/exception.py @@ -279,3 +279,7 @@ class HypervisorNotFound(WatcherException): class LoadingError(WatcherException): msg_fmt = _("Error loading plugin '%(name)s'") + + +class ReservedWord(WatcherException): + msg_fmt = _("The identifier '%(name)s' is a reserved word") diff --git a/watcher/db/sqlalchemy/models.py b/watcher/db/sqlalchemy/models.py index 858e4adff..7ae7e53cf 100644 --- a/watcher/db/sqlalchemy/models.py +++ b/watcher/db/sqlalchemy/models.py @@ -160,7 +160,6 @@ class Action(Base): nullable=False) # only for the first version action_type = Column(String(255), nullable=False) - applies_to = Column(String(255), nullable=True) input_parameters = Column(JSONEncodedDict, nullable=True) state = Column(String(20), nullable=True) # todo(jed) remove parameter alarm diff --git a/watcher/decision_engine/planner/default.py b/watcher/decision_engine/planner/default.py index 42fcfd14b..c341e45ad 100644 --- a/watcher/decision_engine/planner/default.py +++ b/watcher/decision_engine/planner/default.py @@ -38,14 +38,12 @@ class DefaultPlanner(base.BasePlanner): def create_action(self, action_plan_id, action_type, - applies_to, input_parameters=None): uuid = utils.generate_uuid() action = { 'uuid': uuid, 'action_plan_id': int(action_plan_id), 'action_type': action_type, - 'applies_to': applies_to, 'input_parameters': input_parameters, 'state': objects.action.State.PENDING, 'alarm': None, @@ -63,8 +61,6 @@ class DefaultPlanner(base.BasePlanner): json_action = self.create_action(action_plan_id=action_plan.id, action_type=action.get( 'action_type'), - applies_to=action.get( - 'applies_to'), input_parameters=action.get( 'input_parameters')) to_schedule.append((self.priorities[action.get('action_type')], diff --git a/watcher/decision_engine/solution/base.py b/watcher/decision_engine/solution/base.py index ff107cbb9..cb0b5fcc9 100644 --- a/watcher/decision_engine/solution/base.py +++ b/watcher/decision_engine/solution/base.py @@ -87,13 +87,13 @@ class BaseSolution(object): @abc.abstractmethod def add_action(self, action_type, - applies_to, + resource_id, input_parameters=None): """Add a new Action in the Action Plan :param action_type: the unique id of an action type defined in entry point 'watcher_actions' - :param applies_to: the unique id of the resource to which the + :param resource_id: the unique id of the resource to which the `Action` applies. :param input_parameters: An array of input parameters provided as key-value pairs of strings. Each key-pair contains names and diff --git a/watcher/decision_engine/solution/default.py b/watcher/decision_engine/solution/default.py index 2b4b7f9b6..aa0a06337 100644 --- a/watcher/decision_engine/solution/default.py +++ b/watcher/decision_engine/solution/default.py @@ -18,12 +18,14 @@ # from oslo_log import log -from watcher.decision_engine.solution.base import BaseSolution +from watcher.applier.actions import base as baction +from watcher.common import exception +from watcher.decision_engine.solution import base LOG = log.getLogger(__name__) -class DefaultSolution(BaseSolution): +class DefaultSolution(base.BaseSolution): def __init__(self): """Stores a set of actions generated by a strategy @@ -34,12 +36,17 @@ class DefaultSolution(BaseSolution): self._actions = [] def add_action(self, action_type, - applies_to, - input_parameters=None): - # todo(jed) add https://pypi.python.org/pypi/schema + input_parameters=None, + resource_id=None): + + if input_parameters is not None: + if baction.BaseAction.RESOURCE_ID in input_parameters.keys(): + raise exception.ReservedWord(name=baction.BaseAction. + RESOURCE_ID) + if resource_id is not None: + input_parameters[baction.BaseAction.RESOURCE_ID] = resource_id action = { 'action_type': action_type, - 'applies_to': applies_to, 'input_parameters': input_parameters } self._actions.append(action) diff --git a/watcher/decision_engine/strategy/strategies/basic_consolidation.py b/watcher/decision_engine/strategy/strategies/basic_consolidation.py index 2a8cfdfc5..a5cbd6339 100644 --- a/watcher/decision_engine/strategy/strategies/basic_consolidation.py +++ b/watcher/decision_engine/strategy/strategies/basic_consolidation.py @@ -329,14 +329,14 @@ class BasicConsolidation(BaseStrategy): 0, 0) - def add_change_service_state(self, applies_to, state): + def add_change_service_state(self, resource_id, state): parameters = {'state': state} self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE, - applies_to=applies_to, + resource_id=resource_id, input_parameters=parameters) def add_migration(self, - applies_to, + resource_id, migration_type, src_hypervisor, dst_hypervisor): @@ -344,7 +344,7 @@ class BasicConsolidation(BaseStrategy): 'src_hypervisor': src_hypervisor, 'dst_hypervisor': dst_hypervisor} self.solution.add_action(action_type=self.MIGRATION, - applies_to=applies_to, + resource_id=resource_id, input_parameters=parameters) def score_of_nodes(self, current_model, score): diff --git a/watcher/decision_engine/strategy/strategies/dummy_strategy.py b/watcher/decision_engine/strategy/strategies/dummy_strategy.py index 6a6eeff67..0402acbbc 100644 --- a/watcher/decision_engine/strategy/strategies/dummy_strategy.py +++ b/watcher/decision_engine/strategy/strategies/dummy_strategy.py @@ -18,12 +18,12 @@ # from oslo_log import log -from watcher.decision_engine.strategy.strategies.base import BaseStrategy +from watcher.decision_engine.strategy.strategies import base LOG = log.getLogger(__name__) -class DummyStrategy(BaseStrategy): +class DummyStrategy(base.BaseStrategy): DEFAULT_NAME = "dummy" DEFAULT_DESCRIPTION = "Dummy Strategy" @@ -35,17 +35,15 @@ class DummyStrategy(BaseStrategy): super(DummyStrategy, self).__init__(name, description, osc) def execute(self, model): + LOG.debug("Executing Dummy strategy") parameters = {'message': 'hello World'} self.solution.add_action(action_type=self.NOP, - applies_to="", input_parameters=parameters) parameters = {'message': 'Welcome'} self.solution.add_action(action_type=self.NOP, - applies_to="", input_parameters=parameters) self.solution.add_action(action_type=self.SLEEP, - applies_to="", - input_parameters={'duration': '5'}) + input_parameters={'duration': 5.0}) return self.solution diff --git a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py index 4b8a86589..fc914d74c 100644 --- a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py +++ b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py @@ -20,16 +20,16 @@ from oslo_log import log from watcher._i18n import _LE from watcher.common import exception as wexc -from watcher.decision_engine.model.resource import ResourceType -from watcher.decision_engine.model.vm_state import VMState -from watcher.decision_engine.strategy.strategies.base import BaseStrategy -from watcher.metrics_engine.cluster_history.ceilometer import \ - CeilometerClusterHistory +from watcher.decision_engine.model import resource +from watcher.decision_engine.model import vm_state +from watcher.decision_engine.strategy.strategies import base +from watcher.metrics_engine.cluster_history import ceilometer as ceil + LOG = log.getLogger(__name__) -class OutletTempControl(BaseStrategy): +class OutletTempControl(base.BaseStrategy): DEFAULT_NAME = "outlet_temp_control" DEFAULT_DESCRIPTION = "outlet temperature based migration strategy" @@ -81,7 +81,7 @@ class OutletTempControl(BaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = CeilometerClusterHistory(osc=self.osc) + self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) return self._ceilometer @ceilometer.setter @@ -104,7 +104,7 @@ class OutletTempControl(BaseStrategy): return vcpus_used, memory_mb_used, disk_gb_used def group_hosts_by_outlet_temp(self, model): - '''Group hosts based on outlet temp meters''' + """Group hosts based on outlet temp meters""" hypervisors = model.get_all_hypervisors() size_cluster = len(hypervisors) @@ -137,7 +137,7 @@ class OutletTempControl(BaseStrategy): return hosts_need_release, hosts_target def choose_vm_to_migrate(self, model, hosts): - '''pick up an active vm instance to migrate from provided hosts''' + """pick up an active vm instance to migrate from provided hosts""" for hvmap in hosts: mig_src_hypervisor = hvmap['hv'] @@ -147,7 +147,7 @@ class OutletTempControl(BaseStrategy): try: # select the first active VM to migrate vm = model.get_vm_from_id(vm_id) - if vm.state != VMState.ACTIVE.value: + if vm.state != vm_state.VMState.ACTIVE.value: LOG.info(_LE("VM not active, skipped: %s"), vm.uuid) continue @@ -159,11 +159,11 @@ class OutletTempControl(BaseStrategy): return None def filter_dest_servers(self, model, hosts, vm_to_migrate): - '''Only return hosts with sufficient available resources''' + """Only return hosts with sufficient available resources""" - cap_cores = model.get_resource_from_id(ResourceType.cpu_cores) - cap_disk = model.get_resource_from_id(ResourceType.disk) - cap_mem = model.get_resource_from_id(ResourceType.memory) + cap_cores = model.get_resource_from_id(resource.ResourceType.cpu_cores) + cap_disk = model.get_resource_from_id(resource.ResourceType.disk) + cap_mem = model.get_resource_from_id(resource.ResourceType.memory) required_cores = cap_cores.get_capacity(vm_to_migrate) required_disk = cap_disk.get_capacity(vm_to_migrate) @@ -239,10 +239,10 @@ class OutletTempControl(BaseStrategy): mig_src_hypervisor, mig_dst_hypervisor): parameters = {'migration_type': 'live', - 'src_hypervisor': mig_src_hypervisor, - 'dst_hypervisor': mig_dst_hypervisor} + 'src_hypervisor': mig_src_hypervisor.uuid, + 'dst_hypervisor': mig_dst_hypervisor.uuid} self.solution.add_action(action_type=self.MIGRATION, - applies_to=vm_src, + resource_id=vm_src.uuid, input_parameters=parameters) self.solution.model = current_model diff --git a/watcher/locale/fr/LC_MESSAGES/watcher.po b/watcher/locale/fr/LC_MESSAGES/watcher.po index cf0b29c85..cf28a4370 100644 --- a/watcher/locale/fr/LC_MESSAGES/watcher.po +++ b/watcher/locale/fr/LC_MESSAGES/watcher.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: python-watcher 0.21.1.dev32\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-26 11:26+0100\n" +"POT-Creation-Date: 2016-02-09 09:07+0100\n" "PO-Revision-Date: 2015-12-11 15:42+0100\n" "Last-Translator: FULL NAME \n" "Language: fr\n" @@ -24,7 +24,7 @@ msgstr "" msgid "Invalid state: %(state)s" msgstr "État invalide : %(state)s" -#: watcher/api/controllers/v1/action_plan.py:420 +#: watcher/api/controllers/v1/action_plan.py:422 #, python-format msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)" msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)" @@ -85,21 +85,30 @@ msgstr "" msgid "Error parsing HTTP response: %s" msgstr "" -#: watcher/applier/actions/change_nova_service_state.py:58 +#: watcher/applier/actions/change_nova_service_state.py:69 msgid "The target state is not defined" msgstr "" -#: watcher/applier/workflow_engine/default.py:126 +#: watcher/applier/actions/migration.py:43 +msgid "The parameter resource_id is invalid." +msgstr "Le paramètre resource_id est invalide" + +#: watcher/applier/actions/migration.py:86 +#, python-format +msgid "Migration of type %(migration_type)s is not supported." +msgstr "" + +#: watcher/applier/workflow_engine/default.py:128 #, python-format msgid "The WorkFlow Engine has failed to execute the action %s" msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s" -#: watcher/applier/workflow_engine/default.py:144 +#: watcher/applier/workflow_engine/default.py:146 #, python-format msgid "Revert action %s" msgstr "Annulation de l'action %s" -#: watcher/applier/workflow_engine/default.py:150 +#: watcher/applier/workflow_engine/default.py:152 msgid "Oops! We need disaster recover plan" msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité" @@ -119,184 +128,210 @@ msgstr "Sert sur 0.0.0.0:%(port)s, accessible à http://127.0.0.1:%(port)s" msgid "serving on http://%(host)s:%(port)s" msgstr "Sert sur http://%(host)s:%(port)s" -#: watcher/common/exception.py:51 +#: watcher/common/clients.py:29 +msgid "Version of Nova API to use in novaclient." +msgstr "" + +#: watcher/common/clients.py:34 +msgid "Version of Glance API to use in glanceclient." +msgstr "" + +#: watcher/common/clients.py:39 +msgid "Version of Cinder API to use in cinderclient." +msgstr "" + +#: watcher/common/clients.py:44 +msgid "Version of Ceilometer API to use in ceilometerclient." +msgstr "" + +#: watcher/common/clients.py:50 +msgid "Version of Neutron API to use in neutronclient." +msgstr "" + +#: watcher/common/exception.py:59 +#, python-format +msgid "Unexpected keystone client error occurred: %s" +msgstr "" + +#: watcher/common/exception.py:72 msgid "An unknown exception occurred" msgstr "" -#: watcher/common/exception.py:71 +#: watcher/common/exception.py:92 msgid "Exception in string format operation" msgstr "" -#: watcher/common/exception.py:101 +#: watcher/common/exception.py:122 msgid "Not authorized" msgstr "" -#: watcher/common/exception.py:106 +#: watcher/common/exception.py:127 msgid "Operation not permitted" msgstr "" -#: watcher/common/exception.py:110 +#: watcher/common/exception.py:131 msgid "Unacceptable parameters" msgstr "" -#: watcher/common/exception.py:115 +#: watcher/common/exception.py:136 #, python-format msgid "The %(name)s %(id)s could not be found" msgstr "" -#: watcher/common/exception.py:119 +#: watcher/common/exception.py:140 #, fuzzy msgid "Conflict" msgstr "Conflit" -#: watcher/common/exception.py:124 +#: watcher/common/exception.py:145 #, python-format msgid "The %(name)s resource %(id)s could not be found" msgstr "La ressource %(name)s / %(id)s est introuvable" -#: watcher/common/exception.py:129 +#: watcher/common/exception.py:150 #, python-format msgid "Expected an uuid or int but received %(identity)s" msgstr "" -#: watcher/common/exception.py:133 +#: watcher/common/exception.py:154 #, python-format msgid "Goal %(goal)s is not defined in Watcher configuration file" msgstr "" -#: watcher/common/exception.py:139 -#, python-format -msgid "%(err)s" -msgstr "%(err)s" - -#: watcher/common/exception.py:143 +#: watcher/common/exception.py:158 #, python-format msgid "Expected a uuid but received %(uuid)s" msgstr "" -#: watcher/common/exception.py:147 +#: watcher/common/exception.py:162 #, python-format msgid "Expected a logical name but received %(name)s" msgstr "" -#: watcher/common/exception.py:151 +#: watcher/common/exception.py:166 #, python-format msgid "Expected a logical name or uuid but received %(name)s" msgstr "" -#: watcher/common/exception.py:155 +#: watcher/common/exception.py:170 #, python-format msgid "AuditTemplate %(audit_template)s could not be found" msgstr "" -#: watcher/common/exception.py:159 +#: watcher/common/exception.py:174 #, python-format msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists" msgstr "" -#: watcher/common/exception.py:164 +#: watcher/common/exception.py:179 #, python-format msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit" msgstr "" -#: watcher/common/exception.py:169 +#: watcher/common/exception.py:184 #, python-format msgid "Audit %(audit)s could not be found" msgstr "" -#: watcher/common/exception.py:173 +#: watcher/common/exception.py:188 #, python-format msgid "An audit with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:177 +#: watcher/common/exception.py:192 #, python-format msgid "Audit %(audit)s is referenced by one or multiple action plans" msgstr "" -#: watcher/common/exception.py:182 +#: watcher/common/exception.py:197 #, python-format msgid "ActionPlan %(action_plan)s could not be found" msgstr "" -#: watcher/common/exception.py:186 +#: watcher/common/exception.py:201 #, python-format msgid "An action plan with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:190 +#: watcher/common/exception.py:205 #, python-format msgid "Action Plan %(action_plan)s is referenced by one or multiple actions" msgstr "" -#: watcher/common/exception.py:195 +#: watcher/common/exception.py:210 #, python-format msgid "Action %(action)s could not be found" msgstr "" -#: watcher/common/exception.py:199 +#: watcher/common/exception.py:214 #, python-format msgid "An action with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:203 +#: watcher/common/exception.py:218 #, python-format msgid "Action plan %(action_plan)s is referenced by one or multiple goals" msgstr "" -#: watcher/common/exception.py:208 +#: watcher/common/exception.py:223 msgid "Filtering actions on both audit and action-plan is prohibited" msgstr "" -#: watcher/common/exception.py:217 +#: watcher/common/exception.py:232 #, python-format msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgstr "" -#: watcher/common/exception.py:224 +#: watcher/common/exception.py:239 msgid "Illegal argument" msgstr "" -#: watcher/common/exception.py:228 +#: watcher/common/exception.py:243 msgid "No such metric" msgstr "" -#: watcher/common/exception.py:232 +#: watcher/common/exception.py:247 msgid "No rows were returned" msgstr "" -#: watcher/common/exception.py:236 +#: watcher/common/exception.py:251 +#, python-format +msgid "%(client)s connection failed. Reason: %(reason)s" +msgstr "" + +#: watcher/common/exception.py:255 msgid "'Keystone API endpoint is missing''" msgstr "" -#: watcher/common/exception.py:240 +#: watcher/common/exception.py:259 msgid "The list of hypervisor(s) in the cluster is empty" msgstr "" -#: watcher/common/exception.py:244 +#: watcher/common/exception.py:263 msgid "The metrics resource collector is not defined" msgstr "" -#: watcher/common/exception.py:248 +#: watcher/common/exception.py:267 msgid "the cluster state is not defined" msgstr "" -#: watcher/common/exception.py:254 +#: watcher/common/exception.py:273 #, python-format msgid "The instance '%(name)s' is not found" msgstr "L'instance '%(name)s' n'a pas été trouvée" -#: watcher/common/exception.py:258 +#: watcher/common/exception.py:277 msgid "The hypervisor is not found" msgstr "" -#: watcher/common/exception.py:262 +#: watcher/common/exception.py:281 #, fuzzy, python-format msgid "Error loading plugin '%(name)s'" msgstr "Erreur lors du chargement du module '%(name)s'" -#: watcher/common/keystone.py:59 -msgid "No Keystone service catalog loaded" +#: watcher/common/exception.py:285 +#, fuzzy, python-format +msgid "The identifier '%(name)s' is a reserved word" msgstr "" #: watcher/common/service.py:83 @@ -347,18 +382,22 @@ msgid "" "template uuid instead" msgstr "" -#: watcher/db/sqlalchemy/api.py:277 -msgid "Cannot overwrite UUID for an existing AuditTemplate." +#: watcher/db/sqlalchemy/api.py:278 +msgid "Cannot overwrite UUID for an existing Audit Template." msgstr "" -#: watcher/db/sqlalchemy/api.py:386 watcher/db/sqlalchemy/api.py:586 +#: watcher/db/sqlalchemy/api.py:388 msgid "Cannot overwrite UUID for an existing Audit." msgstr "" -#: watcher/db/sqlalchemy/api.py:477 +#: watcher/db/sqlalchemy/api.py:480 msgid "Cannot overwrite UUID for an existing Action." msgstr "" +#: watcher/db/sqlalchemy/api.py:590 +msgid "Cannot overwrite UUID for an existing Action Plan." +msgstr "" + #: watcher/db/sqlalchemy/migration.py:73 msgid "" "Watcher database schema is already under version control; use upgrade() " @@ -370,44 +409,44 @@ msgstr "" msgid "'obj' argument type is not valid" msgstr "" -#: watcher/decision_engine/planner/default.py:76 +#: watcher/decision_engine/planner/default.py:72 msgid "The action plan is empty" msgstr "" -#: watcher/decision_engine/strategy/selection/default.py:59 +#: watcher/decision_engine/strategy/selection/default.py:60 #, python-format msgid "Incorrect mapping: could not find associated strategy for '%s'" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:267 -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:314 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:269 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:316 #, python-format msgid "No values returned by %(resource_id)s for %(metric_name)s" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:424 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:426 msgid "Initializing Sercon Consolidation" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:468 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:470 msgid "The workloads of the compute nodes of the cluster is zero" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:125 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:127 #, python-format msgid "%s: no outlet temp data" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:149 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:151 #, python-format msgid "VM not active, skipped: %s" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:206 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:208 msgid "No hosts under outlet temp threshold found" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:229 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:231 msgid "No proper target host could be found" msgstr "" @@ -582,9 +621,20 @@ msgstr "" #~ msgid "Description cannot be empty" #~ msgstr "" -#~ msgid "" -#~ "Failed to remove trailing character. " -#~ "Returning original object. Supplied object " -#~ "is not a string: %s," +#~ msgid "The hypervisor state is invalid." +#~ msgstr "L'état de l'hyperviseur est invalide" + +#~ msgid "%(err)s" +#~ msgstr "%(err)s" + +#~ msgid "No Keystone service catalog loaded" +#~ msgstr "" + +#~ msgid "Cannot overwrite UUID for an existing AuditTemplate." +#~ msgstr "" + +#~ msgid "" +#~ "This identifier is reserved word and " +#~ "cannot be used as variables '%(name)s'" #~ msgstr "" diff --git a/watcher/locale/watcher.pot b/watcher/locale/watcher.pot index 60fab8762..6a5700218 100644 --- a/watcher/locale/watcher.pot +++ b/watcher/locale/watcher.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-watcher 0.23.2.dev1\n" +"Project-Id-Version: python-watcher 0.23.3.dev2\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-26 11:26+0100\n" +"POT-Creation-Date: 2016-02-09 09:07+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -23,7 +23,7 @@ msgstr "" msgid "Invalid state: %(state)s" msgstr "" -#: watcher/api/controllers/v1/action_plan.py:420 +#: watcher/api/controllers/v1/action_plan.py:422 #, python-format msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)" msgstr "" @@ -84,25 +84,30 @@ msgstr "" msgid "Error parsing HTTP response: %s" msgstr "" -#: watcher/applier/actions/change_nova_service_state.py:58 +#: watcher/applier/actions/change_nova_service_state.py:69 msgid "The target state is not defined" msgstr "" -#: watcher/applier/actions/migration.py:60 +#: watcher/applier/actions/migration.py:43 +msgid "The parameter resource_id is invalid." +msgstr "" + +#: watcher/applier/actions/migration.py:86 +#, python-format msgid "Migration of type %(migration_type)s is not supported." msgstr "" -#: watcher/applier/workflow_engine/default.py:126 +#: watcher/applier/workflow_engine/default.py:128 #, python-format msgid "The WorkFlow Engine has failed to execute the action %s" msgstr "" -#: watcher/applier/workflow_engine/default.py:144 +#: watcher/applier/workflow_engine/default.py:146 #, python-format msgid "Revert action %s" msgstr "" -#: watcher/applier/workflow_engine/default.py:150 +#: watcher/applier/workflow_engine/default.py:152 msgid "Oops! We need disaster recover plan" msgstr "" @@ -122,178 +127,209 @@ msgstr "" msgid "serving on http://%(host)s:%(port)s" msgstr "" -#: watcher/common/exception.py:51 +#: watcher/common/clients.py:29 +msgid "Version of Nova API to use in novaclient." +msgstr "" + +#: watcher/common/clients.py:34 +msgid "Version of Glance API to use in glanceclient." +msgstr "" + +#: watcher/common/clients.py:39 +msgid "Version of Cinder API to use in cinderclient." +msgstr "" + +#: watcher/common/clients.py:44 +msgid "Version of Ceilometer API to use in ceilometerclient." +msgstr "" + +#: watcher/common/clients.py:50 +msgid "Version of Neutron API to use in neutronclient." +msgstr "" + +#: watcher/common/exception.py:59 +#, python-format +msgid "Unexpected keystone client error occurred: %s" +msgstr "" + +#: watcher/common/exception.py:72 msgid "An unknown exception occurred" msgstr "" -#: watcher/common/exception.py:71 +#: watcher/common/exception.py:92 msgid "Exception in string format operation" msgstr "" -#: watcher/common/exception.py:101 +#: watcher/common/exception.py:122 msgid "Not authorized" msgstr "" -#: watcher/common/exception.py:106 +#: watcher/common/exception.py:127 msgid "Operation not permitted" msgstr "" -#: watcher/common/exception.py:110 +#: watcher/common/exception.py:131 msgid "Unacceptable parameters" msgstr "" -#: watcher/common/exception.py:115 +#: watcher/common/exception.py:136 #, python-format msgid "The %(name)s %(id)s could not be found" msgstr "" -#: watcher/common/exception.py:119 +#: watcher/common/exception.py:140 msgid "Conflict" msgstr "" -#: watcher/common/exception.py:124 +#: watcher/common/exception.py:145 #, python-format msgid "The %(name)s resource %(id)s could not be found" msgstr "" -#: watcher/common/exception.py:129 +#: watcher/common/exception.py:150 #, python-format msgid "Expected an uuid or int but received %(identity)s" msgstr "" -#: watcher/common/exception.py:133 +#: watcher/common/exception.py:154 #, python-format msgid "Goal %(goal)s is not defined in Watcher configuration file" msgstr "" -#: watcher/common/exception.py:143 +#: watcher/common/exception.py:158 #, python-format msgid "Expected a uuid but received %(uuid)s" msgstr "" -#: watcher/common/exception.py:147 +#: watcher/common/exception.py:162 #, python-format msgid "Expected a logical name but received %(name)s" msgstr "" -#: watcher/common/exception.py:151 +#: watcher/common/exception.py:166 #, python-format msgid "Expected a logical name or uuid but received %(name)s" msgstr "" -#: watcher/common/exception.py:155 +#: watcher/common/exception.py:170 #, python-format msgid "AuditTemplate %(audit_template)s could not be found" msgstr "" -#: watcher/common/exception.py:159 +#: watcher/common/exception.py:174 #, python-format msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists" msgstr "" -#: watcher/common/exception.py:164 +#: watcher/common/exception.py:179 #, python-format msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit" msgstr "" -#: watcher/common/exception.py:169 +#: watcher/common/exception.py:184 #, python-format msgid "Audit %(audit)s could not be found" msgstr "" -#: watcher/common/exception.py:173 +#: watcher/common/exception.py:188 #, python-format msgid "An audit with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:177 +#: watcher/common/exception.py:192 #, python-format msgid "Audit %(audit)s is referenced by one or multiple action plans" msgstr "" -#: watcher/common/exception.py:182 +#: watcher/common/exception.py:197 #, python-format msgid "ActionPlan %(action_plan)s could not be found" msgstr "" -#: watcher/common/exception.py:186 +#: watcher/common/exception.py:201 #, python-format msgid "An action plan with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:190 +#: watcher/common/exception.py:205 #, python-format msgid "Action Plan %(action_plan)s is referenced by one or multiple actions" msgstr "" -#: watcher/common/exception.py:195 +#: watcher/common/exception.py:210 #, python-format msgid "Action %(action)s could not be found" msgstr "" -#: watcher/common/exception.py:199 +#: watcher/common/exception.py:214 #, python-format msgid "An action with UUID %(uuid)s already exists" msgstr "" -#: watcher/common/exception.py:203 +#: watcher/common/exception.py:218 #, python-format msgid "Action plan %(action_plan)s is referenced by one or multiple goals" msgstr "" -#: watcher/common/exception.py:208 +#: watcher/common/exception.py:223 msgid "Filtering actions on both audit and action-plan is prohibited" msgstr "" -#: watcher/common/exception.py:217 +#: watcher/common/exception.py:232 #, python-format msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgstr "" -#: watcher/common/exception.py:224 +#: watcher/common/exception.py:239 msgid "Illegal argument" msgstr "" -#: watcher/common/exception.py:228 +#: watcher/common/exception.py:243 msgid "No such metric" msgstr "" -#: watcher/common/exception.py:232 +#: watcher/common/exception.py:247 msgid "No rows were returned" msgstr "" -#: watcher/common/exception.py:236 +#: watcher/common/exception.py:251 +#, python-format +msgid "%(client)s connection failed. Reason: %(reason)s" +msgstr "" + +#: watcher/common/exception.py:255 msgid "'Keystone API endpoint is missing''" msgstr "" -#: watcher/common/exception.py:240 +#: watcher/common/exception.py:259 msgid "The list of hypervisor(s) in the cluster is empty" msgstr "" -#: watcher/common/exception.py:244 +#: watcher/common/exception.py:263 msgid "The metrics resource collector is not defined" msgstr "" -#: watcher/common/exception.py:248 +#: watcher/common/exception.py:267 msgid "the cluster state is not defined" msgstr "" -#: watcher/common/exception.py:254 +#: watcher/common/exception.py:273 #, python-format msgid "The instance '%(name)s' is not found" msgstr "" -#: watcher/common/exception.py:258 +#: watcher/common/exception.py:277 msgid "The hypervisor is not found" msgstr "" -#: watcher/common/exception.py:262 +#: watcher/common/exception.py:281 #, python-format msgid "Error loading plugin '%(name)s'" msgstr "" -#: watcher/common/keystone.py:59 -msgid "No Keystone service catalog loaded" +#: watcher/common/exception.py:285 +#, python-format +msgid "The identifier '%(name)s' is a reserved word" msgstr "" #: watcher/common/service.py:83 @@ -344,18 +380,22 @@ msgid "" "template uuid instead" msgstr "" -#: watcher/db/sqlalchemy/api.py:277 -msgid "Cannot overwrite UUID for an existing AuditTemplate." +#: watcher/db/sqlalchemy/api.py:278 +msgid "Cannot overwrite UUID for an existing Audit Template." msgstr "" -#: watcher/db/sqlalchemy/api.py:386 watcher/db/sqlalchemy/api.py:586 +#: watcher/db/sqlalchemy/api.py:388 msgid "Cannot overwrite UUID for an existing Audit." msgstr "" -#: watcher/db/sqlalchemy/api.py:477 +#: watcher/db/sqlalchemy/api.py:480 msgid "Cannot overwrite UUID for an existing Action." msgstr "" +#: watcher/db/sqlalchemy/api.py:590 +msgid "Cannot overwrite UUID for an existing Action Plan." +msgstr "" + #: watcher/db/sqlalchemy/migration.py:73 msgid "" "Watcher database schema is already under version control; use upgrade() " @@ -367,44 +407,44 @@ msgstr "" msgid "'obj' argument type is not valid" msgstr "" -#: watcher/decision_engine/planner/default.py:76 +#: watcher/decision_engine/planner/default.py:72 msgid "The action plan is empty" msgstr "" -#: watcher/decision_engine/strategy/selection/default.py:59 +#: watcher/decision_engine/strategy/selection/default.py:60 #, python-format msgid "Incorrect mapping: could not find associated strategy for '%s'" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:267 -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:314 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:269 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:316 #, python-format msgid "No values returned by %(resource_id)s for %(metric_name)s" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:424 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:426 msgid "Initializing Sercon Consolidation" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:468 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:470 msgid "The workloads of the compute nodes of the cluster is zero" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:125 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:127 #, python-format msgid "%s: no outlet temp data" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:149 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:151 #, python-format msgid "VM not active, skipped: %s" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:206 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:208 msgid "No hosts under outlet temp threshold found" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:229 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:231 msgid "No proper target host could be found" msgstr "" diff --git a/watcher/metrics_engine/cluster_model_collector/nova.py b/watcher/metrics_engine/cluster_model_collector/nova.py index 63c8a8e5c..e92e18da2 100644 --- a/watcher/metrics_engine/cluster_model_collector/nova.py +++ b/watcher/metrics_engine/cluster_model_collector/nova.py @@ -17,32 +17,30 @@ # limitations under the License. # - -from oslo_config import cfg from oslo_log import log -from watcher.decision_engine.model.hypervisor import Hypervisor -from watcher.decision_engine.model.model_root import ModelRoot -from watcher.decision_engine.model.resource import Resource -from watcher.decision_engine.model.resource import ResourceType -from watcher.decision_engine.model.vm import VM -from watcher.metrics_engine.cluster_model_collector.api import \ - BaseClusterModelCollector +from watcher.decision_engine.model import hypervisor as obj_hypervisor +from watcher.decision_engine.model import model_root +from watcher.decision_engine.model import resource +from watcher.decision_engine.model import vm as obj_vm +from watcher.metrics_engine.cluster_model_collector import api + -CONF = cfg.CONF LOG = log.getLogger(__name__) -class NovaClusterModelCollector(BaseClusterModelCollector): +class NovaClusterModelCollector(api.BaseClusterModelCollector): def __init__(self, wrapper): + super(NovaClusterModelCollector, self).__init__() self.wrapper = wrapper def get_latest_cluster_data_model(self): + LOG.debug("Getting latest cluster data model") - cluster = ModelRoot() - mem = Resource(ResourceType.memory) - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) + cluster = model_root.ModelRoot() + mem = resource.Resource(resource.ResourceType.memory) + num_cores = resource.Resource(resource.ResourceType.cpu_cores) + disk = resource.Resource(resource.ResourceType.disk) cluster.create_resource(mem) cluster.create_resource(num_cores) cluster.create_resource(disk) @@ -52,7 +50,7 @@ class NovaClusterModelCollector(BaseClusterModelCollector): for h in hypervisors: service = self.wrapper.nova.services.find(id=h.service['id']) # create hypervisor in cluster_model_collector - hypervisor = Hypervisor() + hypervisor = obj_hypervisor.Hypervisor() hypervisor.uuid = service.host hypervisor.hostname = h.hypervisor_hostname # set capacity @@ -65,7 +63,7 @@ class NovaClusterModelCollector(BaseClusterModelCollector): vms = self.wrapper.get_vms_by_hypervisor(str(service.host)) for v in vms: # create VM in cluster_model_collector - vm = VM() + vm = obj_vm.VM() vm.uuid = v.id # nova/nova/compute/vm_states.py vm.state = getattr(v, 'OS-EXT-STS:vm_state') diff --git a/watcher/objects/action.py b/watcher/objects/action.py index 80a64d062..0cf821a99 100644 --- a/watcher/objects/action.py +++ b/watcher/objects/action.py @@ -42,7 +42,6 @@ class Action(base.WatcherObject): 'uuid': obj_utils.str_or_none, 'action_plan_id': obj_utils.int_or_none, 'action_type': obj_utils.str_or_none, - 'applies_to': obj_utils.str_or_none, 'input_parameters': obj_utils.dict_or_none, 'state': obj_utils.str_or_none, # todo(jed) remove parameter alarm diff --git a/watcher/tests/applier/actions/test_change_nova_service_state.py b/watcher/tests/applier/actions/test_change_nova_service_state.py new file mode 100644 index 000000000..a2fdc8158 --- /dev/null +++ b/watcher/tests/applier/actions/test_change_nova_service_state.py @@ -0,0 +1,54 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# 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 voluptuous + +from watcher.applier.actions import base as baction +from watcher.applier.actions import change_nova_service_state +from watcher.decision_engine.model import hypervisor_state as hstate +from watcher.tests import base + + +class TestChangeNovaServiceState(base.TestCase): + def setUp(self): + super(TestChangeNovaServiceState, self).setUp() + self.a = change_nova_service_state.ChangeNovaServiceState() + + def test_parameters_down(self): + self.a.input_parameters = { + baction.BaseAction.RESOURCE_ID: "compute-1", + self.a.STATE: hstate.HypervisorState.OFFLINE.value} + self.assertEqual(True, self.a.validate_parameters()) + + def test_parameters_up(self): + self.a.input_parameters = { + baction.BaseAction.RESOURCE_ID: "compute-1", + self.a.STATE: hstate.HypervisorState.ONLINE.value} + self.assertEqual(True, self.a.validate_parameters()) + + def test_parameters_exception_wrong_state(self): + self.a.input_parameters = { + baction.BaseAction.RESOURCE_ID: "compute-1", + self.a.STATE: 'error'} + self.assertRaises(voluptuous.Invalid, self.a.validate_parameters) + + def test_parameters_resource_id_empty(self): + self.a.input_parameters = { + self.a.STATE: None} + self.assertRaises(voluptuous.Invalid, self.a.validate_parameters) + + def test_parameters_applies_add_extra(self): + self.a.input_parameters = {"extra": "failed"} + self.assertRaises(voluptuous.Invalid, self.a.validate_parameters) diff --git a/watcher/tests/applier/actions/test_migration.py b/watcher/tests/applier/actions/test_migration.py new file mode 100644 index 000000000..aa92a5839 --- /dev/null +++ b/watcher/tests/applier/actions/test_migration.py @@ -0,0 +1,78 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# 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 voluptuous + +from watcher.applier.actions import base as baction +from watcher.applier.actions import migration +from watcher.tests import base + + +class TestMigration(base.TestCase): + def setUp(self): + super(TestMigration, self).setUp() + self.mig = migration.Migrate() + + def test_parameters(self): + params = {baction.BaseAction.RESOURCE_ID: + "45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba", + self.mig.MIGRATION_TYPE: 'live', + self.mig.DST_HYPERVISOR: 'compute-2', + self.mig.SRC_HYPERVISOR: 'compute3'} + self.mig.input_parameters = params + self.assertEqual(True, self.mig.validate_parameters()) + + def test_parameters_exception_resource_id(self): + parameters = {baction.BaseAction.RESOURCE_ID: "EFEF", + 'migration_type': 'live', + 'src_hypervisor': 'compute-2', + 'dst_hypervisor': 'compute3'} + self.mig.input_parameters = parameters + self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters) + + def test_parameters_exception_migration_type(self): + parameters = {baction.BaseAction.RESOURCE_ID: + "45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba", + 'migration_type': 'cold', + 'src_hypervisor': 'compute-2', + 'dst_hypervisor': 'compute3'} + self.mig.input_parameters = parameters + self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters) + + def test_parameters_exception_src_hypervisor(self): + parameters = {baction.BaseAction.RESOURCE_ID: + "45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba", + 'migration_type': 'cold', + 'src_hypervisor': None, + 'dst_hypervisor': 'compute3'} + self.mig.input_parameters = parameters + self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters) + + def test_parameters_exception_dst_hypervisor(self): + parameters = {baction.BaseAction.RESOURCE_ID: + "45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba", + 'migration_type': 'cold', + 'src_hypervisor': 'compute-1', + 'dst_hypervisor': None} + self.mig.input_parameters = parameters + self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters) + + def test_parameters_exception_empty_fields(self): + parameters = {baction.BaseAction.RESOURCE_ID: None, + 'migration_type': None, + 'src_hypervisor': None, + 'dst_hypervisor': None} + self.mig.input_parameters = parameters + self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters) diff --git a/watcher/tests/applier/actions/test_sleep.py b/watcher/tests/applier/actions/test_sleep.py new file mode 100644 index 000000000..b28e4f0ec --- /dev/null +++ b/watcher/tests/applier/actions/test_sleep.py @@ -0,0 +1,42 @@ + +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# 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 voluptuous + +from watcher.applier.actions import sleep +from watcher.tests import base + + +class TestSleep(base.TestCase): + def setUp(self): + super(TestSleep, self).setUp() + self.s = sleep.Sleep() + + def test_parameters_duration(self): + self.s.input_parameters = {self.s.DURATION: 1.0} + self.assertEqual(True, self.s.validate_parameters()) + + def test_parameters_duration_empty(self): + self.s.input_parameters = {self.s.DURATION: None} + self.assertRaises(voluptuous.Invalid, self.s.validate_parameters) + + def test_parameters_wrong_parameter(self): + self.s.input_parameters = {self.s.DURATION: "ef"} + self.assertRaises(voluptuous.Invalid, self.s.validate_parameters) + + def test_parameters_add_field(self): + self.s.input_parameters = {self.s.DURATION: 1.0, "not_required": "nop"} + self.assertRaises(voluptuous.Invalid, self.s.validate_parameters) diff --git a/watcher/tests/applier/workflow_engine/test_default_workflow_engine.py b/watcher/tests/applier/workflow_engine/test_default_workflow_engine.py index f48ea92f7..99e1aa903 100644 --- a/watcher/tests/applier/workflow_engine/test_default_workflow_engine.py +++ b/watcher/tests/applier/workflow_engine/test_default_workflow_engine.py @@ -32,6 +32,12 @@ from watcher.tests.db import base @six.add_metaclass(abc.ABCMeta) class FakeAction(abase.BaseAction): + def schema(self): + pass + + def postcondition(self): + pass + def precondition(self): pass @@ -62,12 +68,11 @@ class TestDefaultWorkFlowEngine(base.DbTestCase): result = self.engine.execute(actions) self.assertEqual(result, True) - def create_action(self, action_type, applies_to, parameters, next): + def create_action(self, action_type, parameters, next): action = { 'uuid': utils.generate_uuid(), 'action_plan_id': 0, 'action_type': action_type, - 'applies_to': applies_to, 'input_parameters': parameters, 'state': objects.action.State.PENDING, 'alarm': None, @@ -92,15 +97,15 @@ class TestDefaultWorkFlowEngine(base.DbTestCase): self.assertEqual(result, True) def test_execute_with_one_action(self): - actions = [self.create_action("nop", "", {'message': 'test'}, None)] + actions = [self.create_action("nop", {'message': 'test'}, None)] result = self.engine.execute(actions) self.assertEqual(result, True) self.check_actions_state(actions, objects.action.State.SUCCEEDED) def test_execute_with_two_actions(self): actions = [] - next = self.create_action("sleep", "", {'duration': '0'}, None) - first = self.create_action("nop", "", {'message': 'test'}, next.id) + next = self.create_action("sleep", {'duration': 0.0}, None) + first = self.create_action("nop", {'message': 'test'}, next.id) actions.append(first) actions.append(next) @@ -111,9 +116,9 @@ class TestDefaultWorkFlowEngine(base.DbTestCase): def test_execute_with_three_actions(self): actions = [] - next2 = self.create_action("nop", "vm1", {'message': 'next'}, None) - next = self.create_action("sleep", "vm1", {'duration': '0'}, next2.id) - first = self.create_action("nop", "vm1", {'message': 'hello'}, next.id) + next2 = self.create_action("nop", {'message': 'next'}, None) + next = self.create_action("sleep", {'duration': 0.0}, next2.id) + first = self.create_action("nop", {'message': 'hello'}, next.id) self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(next, objects.action.State.PENDING) self.check_action_state(next2, objects.action.State.PENDING) @@ -128,12 +133,9 @@ class TestDefaultWorkFlowEngine(base.DbTestCase): def test_execute_with_exception(self): actions = [] - next2 = self.create_action("no_exist", - "vm1", {'message': 'next'}, None) - next = self.create_action("sleep", "vm1", - {'duration': '0'}, next2.id) - first = self.create_action("nop", "vm1", - {'message': 'hello'}, next.id) + next2 = self.create_action("no_exist", {'message': 'next'}, None) + next = self.create_action("sleep", {'duration': 0.0}, next2.id) + first = self.create_action("nop", {'message': 'hello'}, next.id) self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(next, objects.action.State.PENDING) @@ -158,7 +160,7 @@ class TestDefaultWorkFlowEngine(base.DbTestCase): plugin=FakeAction, obj=None), namespace=FakeAction.namespace()) - actions = [self.create_action("dontcare", "vm1", {}, None)] + actions = [self.create_action("dontcare", {}, None)] result = self.engine.execute(actions) self.assertEqual(result, False) self.check_action_state(actions[0], objects.action.State.FAILED) diff --git a/watcher/tests/db/utils.py b/watcher/tests/db/utils.py index a3d382853..54aae4a11 100644 --- a/watcher/tests/db/utils.py +++ b/watcher/tests/db/utils.py @@ -84,10 +84,12 @@ def get_test_action(**kwargs): 'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'), 'action_plan_id': kwargs.get('action_plan_id', 1), 'action_type': kwargs.get('action_type', 'nop'), - 'applies_to': kwargs.get('applies_to', - '10a47dd1-4874-4298-91cf-eff046dbdb8d'), - 'input_parameters': kwargs.get('input_parameters', {'key1': 'val1', - 'key2': 'val2'}), + 'input_parameters': + kwargs.get('input_parameters', + {'key1': 'val1', + 'key2': 'val2', + 'resource_id': + '10a47dd1-4874-4298-91cf-eff046dbdb8d'}), 'state': kwargs.get('state', 'PENDING'), 'alarm': kwargs.get('alarm', None), 'next': kwargs.get('next', 2), diff --git a/watcher/tests/decision_engine/model/test_mapping.py b/watcher/tests/decision_engine/model/test_mapping.py index fb9b300cc..9101b89c5 100644 --- a/watcher/tests/decision_engine/model/test_mapping.py +++ b/watcher/tests/decision_engine/model/test_mapping.py @@ -18,55 +18,60 @@ # import uuid -from watcher.decision_engine.model.hypervisor import Hypervisor -from watcher.decision_engine.model.vm_state import VMState +from watcher.decision_engine.model import hypervisor as modelhyp +from watcher.decision_engine.model import vm_state from watcher.tests import base -from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state import \ - FakerModelCollector +from watcher.tests.decision_engine.strategy.strategies import \ + faker_cluster_state class TestMapping(base.BaseTestCase): + + VM1_UUID = "73b09e16-35b7-4922-804e-e8f5d9b740fc" + VM2_UUID = "a4cab39b-9828-413a-bf88-f76921bf1517" + def test_get_node_from_vm(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() vms = model.get_all_vms() keys = list(vms.keys()) vm = vms[keys[0]] - if vm.uuid != 'VM_0': + if vm.uuid != self.VM1_UUID: vm = vms[keys[1]] node = model.mapping.get_node_from_vm(vm) self.assertEqual(node.uuid, 'Node_0') def test_get_node_from_vm_id(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() hyps = model.mapping.get_node_vms_from_id("BLABLABLA") self.assertEqual(hyps.__len__(), 0) def test_get_all_vms(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() vms = model.get_all_vms() self.assertEqual(vms.__len__(), 2) - self.assertEqual(vms['VM_0'].state, VMState.ACTIVE.value) - self.assertEqual(vms['VM_0'].uuid, 'VM_0') - self.assertEqual(vms['VM_1'].state, VMState.ACTIVE.value) - self.assertEqual(vms['VM_1'].uuid, 'VM_1') + self.assertEqual(vms[self.VM1_UUID].state, + vm_state.VMState.ACTIVE.value) + self.assertEqual(vms[self.VM1_UUID].uuid, self.VM1_UUID) + self.assertEqual(vms[self.VM2_UUID].state, + vm_state.VMState.ACTIVE.value) + self.assertEqual(vms[self.VM2_UUID].uuid, self.VM2_UUID) def test_get_mapping(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() - mapping_vm = model.mapping.get_mapping_vm() self.assertEqual(mapping_vm.__len__(), 2) - self.assertEqual(mapping_vm['VM_0'], 'Node_0') - self.assertEqual(mapping_vm['VM_1'], 'Node_1') + self.assertEqual(mapping_vm[self.VM1_UUID], 'Node_0') + self.assertEqual(mapping_vm[self.VM2_UUID], 'Node_1') def test_migrate_vm(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() vms = model.get_all_vms() keys = list(vms.keys()) @@ -81,13 +86,13 @@ class TestMapping(base.BaseTestCase): self.assertEqual(model.mapping.migrate_vm(vm1, hyp0, hyp1), True) def test_unmap_from_id_log_warning(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() vms = model.get_all_vms() keys = list(vms.keys()) vm0 = vms[keys[0]] id = "{0}".format(uuid.uuid4()) - hypervisor = Hypervisor() + hypervisor = modelhyp.Hypervisor() hypervisor.uuid = id model.mapping.unmap_from_id(hypervisor.uuid, vm0.uuid) @@ -95,7 +100,7 @@ class TestMapping(base.BaseTestCase): # hypervisor.uuid)), 1) def test_unmap_from_id(self): - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() vms = model.get_all_vms() keys = list(vms.keys()) diff --git a/watcher/tests/decision_engine/planner/test_default_planner.py b/watcher/tests/decision_engine/planner/test_default_planner.py index e5ab5f2f5..b9e206e4d 100644 --- a/watcher/tests/decision_engine/planner/test_default_planner.py +++ b/watcher/tests/decision_engine/planner/test_default_planner.py @@ -68,7 +68,7 @@ class TestActionScheduling(base.DbTestCase): "dst_uuid_hypervisor": "server2", } solution.add_action(action_type="migrate", - applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", + resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36", input_parameters=parameters) with mock.patch.object( @@ -94,11 +94,11 @@ class TestActionScheduling(base.DbTestCase): "dst_uuid_hypervisor": "server2", } solution.add_action(action_type="migrate", - applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", + resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36", input_parameters=parameters) solution.add_action(action_type="nop", - applies_to="", + resource_id="", input_parameters={}) with mock.patch.object( diff --git a/watcher/tests/decision_engine/solution/test_default_solution.py b/watcher/tests/decision_engine/solution/test_default_solution.py index 060e54585..7632644a3 100644 --- a/watcher/tests/decision_engine/solution/test_default_solution.py +++ b/watcher/tests/decision_engine/solution/test_default_solution.py @@ -13,27 +13,29 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -from watcher.decision_engine.solution.default import DefaultSolution + +from watcher.decision_engine.solution import default from watcher.tests import base class TestDefaultSolution(base.BaseTestCase): def test_default_solution(self): - solution = DefaultSolution() + solution = default.DefaultSolution() parameters = { "src_uuid_hypervisor": "server1", "dst_uuid_hypervisor": "server2", } solution.add_action(action_type="nop", - applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", + resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36", input_parameters=parameters) self.assertEqual(len(solution.actions), 1) expected_action_type = "nop" - expected_applies_to = "b199db0c-1408-4d52-b5a5-5ca14de0ff36" - expected_parameters = parameters + expected_parameters = { + "src_uuid_hypervisor": "server1", + "dst_uuid_hypervisor": "server2", + "resource_id": "b199db0c-1408-4d52-b5a5-5ca14de0ff36" + } self.assertEqual(solution.actions[0].get('action_type'), expected_action_type) - self.assertEqual(solution.actions[0].get('applies_to'), - expected_applies_to) self.assertEqual(solution.actions[0].get('input_parameters'), expected_parameters) diff --git a/watcher/tests/decision_engine/strategy/strategies/faker_cluster_state.py b/watcher/tests/decision_engine/strategy/strategies/faker_cluster_state.py index 22cccf78c..a5e46beff 100644 --- a/watcher/tests/decision_engine/strategy/strategies/faker_cluster_state.py +++ b/watcher/tests/decision_engine/strategy/strategies/faker_cluster_state.py @@ -16,81 +16,24 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -import random - -from watcher.decision_engine.model.hypervisor import Hypervisor -from watcher.decision_engine.model.model_root import ModelRoot -from watcher.decision_engine.model.resource import Resource -from watcher.decision_engine.model.resource import ResourceType -from watcher.decision_engine.model.vm import VM -from watcher.metrics_engine.cluster_model_collector.api import \ - BaseClusterModelCollector +from watcher.decision_engine.model import hypervisor +from watcher.decision_engine.model import model_root as modelroot +from watcher.decision_engine.model import resource +from watcher.decision_engine.model import vm as modelvm +from watcher.metrics_engine.cluster_model_collector import api -class FakerModelCollector(BaseClusterModelCollector): +class FakerModelCollector(api.BaseClusterModelCollector): def __init__(self): pass def get_latest_cluster_data_model(self): return self.generate_scenario_1() - def generate_random(self, count_nodes, number_of_vm_per_node): - vms = [] - - current_state_cluster = ModelRoot() - # number of nodes - count_node = count_nodes - # number max of vm per hypervisor - node_count_vm = number_of_vm_per_node - # total number of virtual machine - count_vm = (count_node * node_count_vm) - - # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) - # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) - - current_state_cluster.create_resource(mem) - current_state_cluster.create_resource(num_cores) - current_state_cluster.create_resource(disk) - - for i in range(0, count_node): - node_uuid = "Node_{0}".format(i) - - hypervisor = Hypervisor() - hypervisor.uuid = node_uuid - hypervisor.hostname = "host_{0}".format(i) - - mem.set_capacity(hypervisor, 132) - disk.set_capacity(hypervisor, 250) - num_cores.set_capacity(hypervisor, 40) - current_state_cluster.add_hypervisor(hypervisor) - - for i in range(0, count_vm): - vm_uuid = "VM_{0}".format(i) - vm = VM() - vm.uuid = vm_uuid - mem.set_capacity(vm, 8) - disk.set_capacity(vm, 10) - num_cores.set_capacity(vm, 10) - vms.append(vm) - current_state_cluster.add_vm(vm) - j = 0 - for node_id in current_state_cluster.get_all_hypervisors(): - for i in range(0, random.randint(0, node_count_vm)): - # todo(jed) check if enough capacity - current_state_cluster.get_mapping().map( - current_state_cluster.get_hypervisor_from_id(node_id), - vms[j]) - j += 1 - return current_state_cluster - def generate_scenario_1(self): vms = [] - current_state_cluster = ModelRoot() + current_state_cluster = modelroot.ModelRoot() # number of nodes count_node = 5 # number max of vm per node @@ -99,10 +42,10 @@ class FakerModelCollector(BaseClusterModelCollector): count_vm = (count_node * node_count_vm) # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) + mem = resource.Resource(resource.ResourceType.memory) # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) + num_cores = resource.Resource(resource.ResourceType.cpu_cores) + disk = resource.Resource(resource.ResourceType.disk) current_state_cluster.create_resource(mem) current_state_cluster.create_resource(num_cores) @@ -110,7 +53,7 @@ class FakerModelCollector(BaseClusterModelCollector): for i in range(0, count_node): node_uuid = "Node_{0}".format(i) - node = Hypervisor() + node = hypervisor.Hypervisor() node.uuid = node_uuid node.hostname = "hostname_{0}".format(i) @@ -121,7 +64,7 @@ class FakerModelCollector(BaseClusterModelCollector): for i in range(0, count_vm): vm_uuid = "VM_{0}".format(i) - vm = VM() + vm = modelvm.VM() vm.uuid = vm_uuid mem.set_capacity(vm, 2) disk.set_capacity(vm, 20) @@ -168,130 +111,68 @@ class FakerModelCollector(BaseClusterModelCollector): model.get_hypervisor_from_id(h_id), model.get_vm_from_id(vm_id)) - def generate_scenario_2(self): - vms = [] - - current_state_cluster = ModelRoot() - # number of nodes - count_node = 10 - # number max of vm per node - node_count_vm = 7 - # total number of virtual machine - count_vm = (count_node * node_count_vm) - - # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) - # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) - - current_state_cluster.create_resource(mem) - current_state_cluster.create_resource(num_cores) - current_state_cluster.create_resource(disk) - - for i in range(0, count_node): - node_uuid = "Node_{0}".format(i) - node = Hypervisor() - node.uuid = node_uuid - node.hostname = "hostname_{0}".format(i) - - mem.set_capacity(node, 132) - disk.set_capacity(node, 250) - num_cores.set_capacity(node, 40) - current_state_cluster.add_hypervisor(node) - - for i in range(0, count_vm): - vm_uuid = "VM_{0}".format(i) - vm = VM() - vm.uuid = vm_uuid - mem.set_capacity(vm, 10) - disk.set_capacity(vm, 25) - num_cores.set_capacity(vm, 16) - vms.append(vm) - current_state_cluster.add_vm(vm) - indice = 0 - for j in range(0, 2): - node_uuid = "Node_{0}".format(j) - for i in range(indice, 3): - vm_uuid = "VM_{0}".format(i) - self.map(current_state_cluster, node_uuid, vm_uuid) - - for j in range(2, 5): - node_uuid = "Node_{0}".format(j) - for i in range(indice, 4): - vm_uuid = "VM_{0}".format(i) - self.map(current_state_cluster, node_uuid, vm_uuid) - - for j in range(5, 10): - node_uuid = "Node_{0}".format(j) - for i in range(indice, 4): - vm_uuid = "VM_{0}".format(i) - self.map(current_state_cluster, node_uuid, vm_uuid) - - return current_state_cluster - def generate_scenario_3_with_2_hypervisors(self): vms = [] - current_state_cluster = ModelRoot() + root = modelroot.ModelRoot() # number of nodes count_node = 2 - # number max of vm per node - node_count_vm = 1 - # total number of virtual machine - count_vm = (count_node * node_count_vm) # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) + mem = resource.Resource(resource.ResourceType.memory) # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) + num_cores = resource.Resource(resource.ResourceType.cpu_cores) + disk = resource.Resource(resource.ResourceType.disk) - current_state_cluster.create_resource(mem) - current_state_cluster.create_resource(num_cores) - current_state_cluster.create_resource(disk) + root.create_resource(mem) + root.create_resource(num_cores) + root.create_resource(disk) for i in range(0, count_node): node_uuid = "Node_{0}".format(i) - node = Hypervisor() + node = hypervisor.Hypervisor() node.uuid = node_uuid node.hostname = "hostname_{0}".format(i) mem.set_capacity(node, 132) disk.set_capacity(node, 250) num_cores.set_capacity(node, 40) - current_state_cluster.add_hypervisor(node) + root.add_hypervisor(node) - for i in range(0, count_vm): - vm_uuid = "VM_{0}".format(i) - vm = VM() - vm.uuid = vm_uuid - mem.set_capacity(vm, 2) - disk.set_capacity(vm, 20) - num_cores.set_capacity(vm, 10) - vms.append(vm) - current_state_cluster.add_vm(vm) + vm1 = modelvm.VM() + vm1.uuid = "73b09e16-35b7-4922-804e-e8f5d9b740fc" + mem.set_capacity(vm1, 2) + disk.set_capacity(vm1, 20) + num_cores.set_capacity(vm1, 10) + vms.append(vm1) + root.add_vm(vm1) - current_state_cluster.get_mapping().map( - current_state_cluster.get_hypervisor_from_id("Node_0"), - current_state_cluster.get_vm_from_id("VM_0")) + vm2 = modelvm.VM() + vm2.uuid = "a4cab39b-9828-413a-bf88-f76921bf1517" + mem.set_capacity(vm2, 2) + disk.set_capacity(vm2, 20) + num_cores.set_capacity(vm2, 10) + vms.append(vm2) + root.add_vm(vm2) - current_state_cluster.get_mapping().map( - current_state_cluster.get_hypervisor_from_id("Node_1"), - current_state_cluster.get_vm_from_id("VM_1")) + root.get_mapping().map(root.get_hypervisor_from_id("Node_0"), + root.get_vm_from_id(str(vm1.uuid))) - return current_state_cluster + root.get_mapping().map(root.get_hypervisor_from_id("Node_1"), + root.get_vm_from_id(str(vm2.uuid))) + + return root def generate_scenario_4_with_1_hypervisor_no_vm(self): - current_state_cluster = ModelRoot() + current_state_cluster = modelroot.ModelRoot() # number of nodes count_node = 1 # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) + mem = resource.Resource(resource.ResourceType.memory) # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) + num_cores = resource.Resource(resource.ResourceType.cpu_cores) + disk = resource.Resource(resource.ResourceType.disk) current_state_cluster.create_resource(mem) current_state_cluster.create_resource(num_cores) @@ -299,7 +180,7 @@ class FakerModelCollector(BaseClusterModelCollector): for i in range(0, count_node): node_uuid = "Node_{0}".format(i) - node = Hypervisor() + node = hypervisor.Hypervisor() node.uuid = node_uuid node.hostname = "hostname_{0}".format(i) @@ -312,17 +193,17 @@ class FakerModelCollector(BaseClusterModelCollector): def generate_scenario_5_with_vm_disk_0(self): vms = [] - current_state_cluster = ModelRoot() + current_state_cluster = modelroot.ModelRoot() # number of nodes count_node = 1 # number of vms count_vm = 1 # define ressouce ( CPU, MEM disk, ... ) - mem = Resource(ResourceType.memory) + mem = resource.Resource(resource.ResourceType.memory) # 2199.954 Mhz - num_cores = Resource(ResourceType.cpu_cores) - disk = Resource(ResourceType.disk) + num_cores = resource.Resource(resource.ResourceType.cpu_cores) + disk = resource.Resource(resource.ResourceType.disk) current_state_cluster.create_resource(mem) current_state_cluster.create_resource(num_cores) @@ -330,7 +211,7 @@ class FakerModelCollector(BaseClusterModelCollector): for i in range(0, count_node): node_uuid = "Node_{0}".format(i) - node = Hypervisor() + node = hypervisor.Hypervisor() node.uuid = node_uuid node.hostname = "hostname_{0}".format(i) @@ -341,7 +222,7 @@ class FakerModelCollector(BaseClusterModelCollector): for i in range(0, count_vm): vm_uuid = "VM_{0}".format(i) - vm = VM() + vm = modelvm.VM() vm.uuid = vm_uuid mem.set_capacity(vm, 2) disk.set_capacity(vm, 0) diff --git a/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py b/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py index cf5ec1f12..80b72f01b 100644 --- a/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py +++ b/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py @@ -16,26 +16,26 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from collections import Counter +import collections import mock +from watcher.applier.actions.loading import default from watcher.common import exception -from watcher.decision_engine.model.model_root import ModelRoot -from watcher.decision_engine.strategy.strategies.basic_consolidation import \ - BasicConsolidation +from watcher.decision_engine.model import model_root +from watcher.decision_engine.strategy import strategies from watcher.tests import base -from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state \ - import FakerModelCollector -from watcher.tests.decision_engine.strategy.strategies.faker_metrics_collector\ - import FakerMetricsCollector +from watcher.tests.decision_engine.strategy.strategies \ + import faker_cluster_state +from watcher.tests.decision_engine.strategy.strategies \ + import faker_metrics_collector class TestBasicConsolidation(base.BaseTestCase): # fake metrics - fake_metrics = FakerMetricsCollector() + fake_metrics = faker_metrics_collector.FakerMetricsCollector() # fake cluster - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() def test_cluster_size(self): size_cluster = len( @@ -45,7 +45,7 @@ class TestBasicConsolidation(base.BaseTestCase): def test_basic_consolidation_score_hypervisor(self): cluster = self.fake_cluster.generate_scenario_1() - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) @@ -67,7 +67,7 @@ class TestBasicConsolidation(base.BaseTestCase): def test_basic_consolidation_score_vm(self): cluster = self.fake_cluster.generate_scenario_1() - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) vm_0 = cluster.get_vm_from_id("VM_0") @@ -90,7 +90,7 @@ class TestBasicConsolidation(base.BaseTestCase): def test_basic_consolidation_score_vm_disk(self): cluster = self.fake_cluster.generate_scenario_5_with_vm_disk_0() - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) vm_0 = cluster.get_vm_from_id("VM_0") @@ -99,7 +99,7 @@ class TestBasicConsolidation(base.BaseTestCase): def test_basic_consolidation_weight(self): cluster = self.fake_cluster.generate_scenario_1() - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) vm_0 = cluster.get_vm_from_id("VM_0") @@ -114,24 +114,24 @@ class TestBasicConsolidation(base.BaseTestCase): vm_0_weight_assert) def test_calculate_migration_efficacy(self): - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.calculate_migration_efficacy() def test_exception_model(self): - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() self.assertRaises(exception.ClusterStateNotDefined, sercon.execute, None) def test_exception_cluster_empty(self): - sercon = BasicConsolidation() - model = ModelRoot() + sercon = strategies.BasicConsolidation() + model = model_root.ModelRoot() self.assertRaises(exception.ClusterEmpty, sercon.execute, model) def test_calculate_score_vm_raise_cluster_state_not_found(self): - metrics = FakerMetricsCollector() + metrics = faker_metrics_collector.FakerMetricsCollector() metrics.empty_one_metric("CPU_COMPUTE") - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) @@ -139,8 +139,8 @@ class TestBasicConsolidation(base.BaseTestCase): sercon.calculate_score_vm, "VM_1", None) def test_check_migration(self): - sercon = BasicConsolidation() - fake_cluster = FakerModelCollector() + sercon = strategies.BasicConsolidation() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() all_vms = model.get_all_vms() @@ -151,8 +151,8 @@ class TestBasicConsolidation(base.BaseTestCase): sercon.check_migration(model, hyp0, hyp0, vm0) def test_threshold(self): - sercon = BasicConsolidation() - fake_cluster = FakerModelCollector() + sercon = strategies.BasicConsolidation() + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() all_hyps = model.get_all_hypervisors() @@ -165,19 +165,19 @@ class TestBasicConsolidation(base.BaseTestCase): self.assertEqual(sercon.get_threshold_cores(), threshold_cores + 1) def test_number_of(self): - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.get_number_of_released_nodes() sercon.get_number_of_migrations() def test_basic_consolidation_migration(self): - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) solution = sercon.execute( self.fake_cluster.generate_scenario_3_with_2_hypervisors()) - actions_counter = Counter( + actions_counter = collections.Counter( [action.get('action_type') for action in solution.actions]) expected_num_migrations = 1 @@ -189,26 +189,31 @@ class TestBasicConsolidation(base.BaseTestCase): self.assertEqual(num_migrations, expected_num_migrations) self.assertEqual(num_hypervisor_state_change, expected_power_state) - def test_execute_cluster_empty(self): - current_state_cluster = FakerModelCollector() - sercon = BasicConsolidation("sercon", "Basic offline consolidation") - sercon.ceilometer = mock.MagicMock( - statistic_aggregation=self.fake_metrics.mock_get_statistics) - model = current_state_cluster.generate_random(0, 0) - self.assertRaises(exception.ClusterEmpty, sercon.execute, model) - # calculate_weight def test_execute_no_workload(self): - sercon = BasicConsolidation() + sercon = strategies.BasicConsolidation() sercon.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) - current_state_cluster = FakerModelCollector() + current_state_cluster = faker_cluster_state.FakerModelCollector() model = current_state_cluster. \ generate_scenario_4_with_1_hypervisor_no_vm() - with mock.patch.object(BasicConsolidation, 'calculate_weight') \ + with mock.patch.object(strategies.BasicConsolidation, + 'calculate_weight') \ as mock_score_call: mock_score_call.return_value = 0 solution = sercon.execute(model) self.assertEqual(solution.efficacy, 100) + + def test_check_parameters(self): + sercon = strategies.BasicConsolidation() + sercon.ceilometer = mock.MagicMock( + statistic_aggregation=self.fake_metrics.mock_get_statistics) + solution = sercon.execute( + self.fake_cluster.generate_scenario_3_with_2_hypervisors()) + loader = default.DefaultActionLoader() + for action in solution.actions: + loaded_action = loader.load(action['action_type']) + loaded_action.input_parameters = action['input_parameters'] + loaded_action.validate_parameters() diff --git a/watcher/tests/decision_engine/strategy/strategies/test_dummy_strategy.py b/watcher/tests/decision_engine/strategy/strategies/test_dummy_strategy.py index c0e54b9ed..d1be95412 100644 --- a/watcher/tests/decision_engine/strategy/strategies/test_dummy_strategy.py +++ b/watcher/tests/decision_engine/strategy/strategies/test_dummy_strategy.py @@ -13,16 +13,29 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -from watcher.decision_engine.strategy.strategies.dummy_strategy import \ - DummyStrategy + +from watcher.applier.actions.loading import default +from watcher.decision_engine.strategy import strategies from watcher.tests import base -from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state\ - import FakerModelCollector +from watcher.tests.decision_engine.strategy.strategies import \ + faker_cluster_state class TestDummyStrategy(base.TestCase): def test_dummy_strategy(self): - tactique = DummyStrategy("basic", "Basic offline consolidation") - fake_cluster = FakerModelCollector() + dummy = strategies.DummyStrategy("dummy", "Dummy strategy") + fake_cluster = faker_cluster_state.FakerModelCollector() model = fake_cluster.generate_scenario_3_with_2_hypervisors() - tactique.execute(model) + solution = dummy.execute(model) + self.assertEqual(3, len(solution.actions)) + + def test_check_parameters(self): + dummy = strategies.DummyStrategy("dummy", "Dummy strategy") + fake_cluster = faker_cluster_state.FakerModelCollector() + model = fake_cluster.generate_scenario_3_with_2_hypervisors() + solution = dummy.execute(model) + loader = default.DefaultActionLoader() + for action in solution.actions: + loaded_action = loader.load(action['action_type']) + loaded_action.input_parameters = action['input_parameters'] + loaded_action.validate_parameters() diff --git a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py index 2e93f277b..71e9cbd56 100644 --- a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py +++ b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py @@ -16,35 +16,35 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from collections import Counter +import collections import mock +from watcher.applier.actions.loading import default from watcher.common import exception -from watcher.decision_engine.model.model_root import ModelRoot -from watcher.decision_engine.model.resource import ResourceType -from watcher.decision_engine.strategy.strategies.outlet_temp_control import \ - OutletTempControl +from watcher.decision_engine.model import model_root +from watcher.decision_engine.model import resource +from watcher.decision_engine.strategy import strategies from watcher.tests import base -from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state \ - import FakerModelCollector -from watcher.tests.decision_engine.strategy.strategies.faker_metrics_collector\ - import FakerMetricsCollector +from watcher.tests.decision_engine.strategy.strategies \ + import faker_cluster_state +from watcher.tests.decision_engine.strategy.strategies \ + import faker_metrics_collector class TestOutletTempControl(base.BaseTestCase): # fake metrics - fake_metrics = FakerMetricsCollector() + fake_metrics = faker_metrics_collector.FakerMetricsCollector() # fake cluster - fake_cluster = FakerModelCollector() + fake_cluster = faker_cluster_state.FakerModelCollector() def test_calc_used_res(self): model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() hypervisor = model.get_hypervisor_from_id('Node_0') - cap_cores = model.get_resource_from_id(ResourceType.cpu_cores) - cap_mem = model.get_resource_from_id(ResourceType.memory) - cap_disk = model.get_resource_from_id(ResourceType.disk) + cap_cores = model.get_resource_from_id(resource.ResourceType.cpu_cores) + cap_mem = model.get_resource_from_id(resource.ResourceType.memory) + cap_disk = model.get_resource_from_id(resource.ResourceType.disk) cores_used, mem_used, disk_used = strategy.calc_used_res(model, hypervisor, cap_cores, @@ -55,7 +55,7 @@ class TestOutletTempControl(base.BaseTestCase): def test_group_hosts_by_outlet_temp(self): model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) h1, h2 = strategy.group_hosts_by_outlet_temp(model) @@ -64,17 +64,18 @@ class TestOutletTempControl(base.BaseTestCase): def test_choose_vm_to_migrate(self): model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) h1, h2 = strategy.group_hosts_by_outlet_temp(model) vm_to_mig = strategy.choose_vm_to_migrate(model, h1) self.assertEqual(vm_to_mig[0].uuid, 'Node_1') - self.assertEqual(vm_to_mig[1].uuid, 'VM_1') + self.assertEqual(vm_to_mig[1].uuid, + "a4cab39b-9828-413a-bf88-f76921bf1517") def test_filter_dest_servers(self): model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) h1, h2 = strategy.group_hosts_by_outlet_temp(model) @@ -84,29 +85,28 @@ class TestOutletTempControl(base.BaseTestCase): self.assertEqual(dest_hosts[0]['hv'].uuid, 'Node_0') def test_exception_model(self): - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() self.assertRaises(exception.ClusterStateNotDefined, strategy.execute, None) def test_exception_cluster_empty(self): - strategy = OutletTempControl() - model = ModelRoot() + strategy = strategies.OutletTempControl() + model = model_root.ModelRoot() self.assertRaises(exception.ClusterEmpty, strategy.execute, model) def test_execute_cluster_empty(self): - current_state_cluster = FakerModelCollector() - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) - model = current_state_cluster.generate_random(0, 0) + model = model_root.ModelRoot() self.assertRaises(exception.ClusterEmpty, strategy.execute, model) def test_execute_no_workload(self): - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) - current_state_cluster = FakerModelCollector() + current_state_cluster = faker_cluster_state.FakerModelCollector() model = current_state_cluster. \ generate_scenario_4_with_1_hypervisor_no_vm() @@ -114,13 +114,25 @@ class TestOutletTempControl(base.BaseTestCase): self.assertEqual(solution.actions, []) def test_execute(self): - strategy = OutletTempControl() + strategy = strategies.OutletTempControl() strategy.ceilometer = mock.MagicMock( statistic_aggregation=self.fake_metrics.mock_get_statistics) model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() solution = strategy.execute(model) - actions_counter = Counter( + actions_counter = collections.Counter( [action.get('action_type') for action in solution.actions]) num_migrations = actions_counter.get("migrate", 0) self.assertEqual(num_migrations, 1) + + def test_check_parameters(self): + outlet = strategies.OutletTempControl() + outlet.ceilometer = mock.MagicMock( + statistic_aggregation=self.fake_metrics.mock_get_statistics) + model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() + solution = outlet.execute(model) + loader = default.DefaultActionLoader() + for action in solution.actions: + loaded_action = loader.load(action['action_type']) + loaded_action.input_parameters = action['input_parameters'] + loaded_action.validate_parameters()