diff --git a/doc/source/dev/plugins.rst b/doc/source/dev/plugins.rst index efa43c686..d1aa1e628 100644 --- a/doc/source/dev/plugins.rst +++ b/doc/source/dev/plugins.rst @@ -45,7 +45,7 @@ Here is an example showing how you can write a plugin called ``DummyStrategy``: def execute(self, model): self.solution.add_change_request( - Migrate(vm=my_vm, source_hypervisor=src, dest_hypervisor=dest) + Migrate(vm=my_vm, src_hypervisor=src, dest_hypervisor=dest) ) # Do some more stuff here ... return self.solution diff --git a/watcher/decision_engine/meta_action/__init__.py b/watcher/decision_engine/actions/__init__.py similarity index 100% rename from watcher/decision_engine/meta_action/__init__.py rename to watcher/decision_engine/actions/__init__.py diff --git a/watcher/decision_engine/meta_action/base.py b/watcher/decision_engine/actions/base.py similarity index 94% rename from watcher/decision_engine/meta_action/base.py rename to watcher/decision_engine/actions/base.py index c25782852..c4c3c1153 100644 --- a/watcher/decision_engine/meta_action/base.py +++ b/watcher/decision_engine/actions/base.py @@ -23,7 +23,7 @@ from watcher.decision_engine.strategy.level import StrategyLevel @six.add_metaclass(abc.ABCMeta) -class MetaAction(object): +class BaseAction(object): def __init__(self): self._level = StrategyLevel.conservative self._priority = 0 @@ -43,6 +43,3 @@ class MetaAction(object): @priority.setter def priority(self, p): self._priority = p - - def __str__(self): - return " " diff --git a/watcher/decision_engine/meta_action/hypervisor_state.py b/watcher/decision_engine/actions/hypervisor_state.py similarity index 74% rename from watcher/decision_engine/meta_action/hypervisor_state.py rename to watcher/decision_engine/actions/hypervisor_state.py index 80b2bec3a..da62a637b 100644 --- a/watcher/decision_engine/meta_action/hypervisor_state.py +++ b/watcher/decision_engine/actions/hypervisor_state.py @@ -16,18 +16,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from watcher.decision_engine.meta_action.base import MetaAction +from watcher.decision_engine.actions.base import BaseAction from watcher.decision_engine.model.hypervisor_state import HypervisorState -class ChangeHypervisorState(MetaAction): +class ChangeHypervisorState(BaseAction): def __init__(self, target): - MetaAction.__init__(self) - '''The target host to change the power + '''The target host to change the state - :param target: - :return: + :param target: the target hypervisor uuid ''' + super(ChangeHypervisorState, self).__init__() self._target = target self._state = HypervisorState.ONLINE @@ -48,5 +47,5 @@ class ChangeHypervisorState(MetaAction): self._target = p def __str__(self): - return "{0} {1} ChangeHypervisorState => {2}".format( - MetaAction.__str__(self), self.target, self.state) + return "{} ChangeHypervisorState => {}".format(self.target, + self.state) diff --git a/watcher/decision_engine/actions/migration.py b/watcher/decision_engine/actions/migration.py new file mode 100644 index 000000000..d730635fe --- /dev/null +++ b/watcher/decision_engine/actions/migration.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015 b<>com +# +# Authors: Jean-Emile DARTOIS +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from enum import Enum + +from watcher.decision_engine.actions.base import BaseAction + + +class MigrationType(Enum): + # Total migration time and downtime depend on memory dirtying speed + pre_copy = 0 + # Postcopy transfer a page only once reliability + post_copy = 1 + + +class Migrate(BaseAction): + def __init__(self, vm, src_hypervisor, dest_hypervisor): + """Request to migrate a virtual machine from a host to another + :param vm: the virtual machine uuid to migrate + :param src_hypervisor: uuid + :param dest_hypervisor: uuid + """ + super(Migrate, self).__init__() + self._reserved_disk_iops = 0 + self._remaining_dirty_pages = 0 + self._vm = vm + self._migration_type = MigrationType.pre_copy + self._src_hypervisor = src_hypervisor + self._dest_hypervisor = dest_hypervisor + + @property + def migration_type(self): + return self._migration_type + + @migration_type.setter + def migration_type(self, type): + self._migration_type = type + + @property + def vm(self): + return self._vm + + @property + def src_hypervisor(self): + return self._src_hypervisor + + @property + def dest_hypervisor(self): + return self._dest_hypervisor + + def __str__(self): + return "Migrate {} from {} to {}".format( + self.vm, + self.src_hypervisor, + self.dest_hypervisor) diff --git a/watcher/decision_engine/meta_action/nop.py b/watcher/decision_engine/actions/nop.py similarity index 82% rename from watcher/decision_engine/meta_action/nop.py rename to watcher/decision_engine/actions/nop.py index 8414ac880..8a2881262 100644 --- a/watcher/decision_engine/meta_action/nop.py +++ b/watcher/decision_engine/actions/nop.py @@ -17,9 +17,9 @@ # limitations under the License. # -from watcher.decision_engine.meta_action.base import MetaAction +from watcher.decision_engine.actions.base import BaseAction -class Nop(MetaAction): +class Nop(BaseAction): def __str__(self): - return "{0} Nop".format(MetaAction.__str__(self)) + return "Nop" diff --git a/watcher/decision_engine/meta_action/power_state.py b/watcher/decision_engine/actions/power_state.py similarity index 80% rename from watcher/decision_engine/meta_action/power_state.py rename to watcher/decision_engine/actions/power_state.py index 72f4d8701..29348667e 100644 --- a/watcher/decision_engine/meta_action/power_state.py +++ b/watcher/decision_engine/actions/power_state.py @@ -16,18 +16,17 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from watcher.decision_engine.meta_action.base import MetaAction +from watcher.decision_engine.actions.base import BaseAction from watcher.decision_engine.model.power_state import PowerState -class ChangePowerState(MetaAction): +class ChangePowerState(BaseAction): def __init__(self, target): - MetaAction.__init__(self) """The target host to change the power :param target: - :return: """ + super(ChangePowerState, self).__init__() self._target = target self._power_state = PowerState.g0 @@ -44,10 +43,9 @@ class ChangePowerState(MetaAction): return self._target @target.setter - def target(self, p): - self._target = p + def target(self, t): + self._target = t def __str__(self): - return "{0} ChangePowerState {1} => {2} ".format( - MetaAction.__str__(self), + return "ChangePowerState {} => {} ".format( self.target, self.powerstate) diff --git a/watcher/decision_engine/meta_action/migrate.py b/watcher/decision_engine/meta_action/migrate.py deleted file mode 100644 index 45546705f..000000000 --- a/watcher/decision_engine/meta_action/migrate.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Authors: Jean-Emile DARTOIS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from enum import Enum - -from watcher.decision_engine.meta_action.base import MetaAction - - -class MigrationType(Enum): - # Total migration time and downtime depend on memory dirtying speed - pre_copy = 0 - # Postcopy transfer a page only once reliability - post_copy = 1 - - -class Migrate(MetaAction): - def __init__(self, vm, source_hypervisor, dest_hypervisor): - MetaAction.__init__(self) - """Request Migrate - :param bandwidth the bandwidth reserved for the migration - :param vm: the virtual machine to migrate - :param source_hypervisor: - :param dest_hypervisor: - :return: - """ - self.bandwidth = 0 - self.reservedDiskIOPS = 0 - self.remainingDirtyPages = 0 - self.vm = vm - self.migration_type = MigrationType.pre_copy - self.source_hypervisor = source_hypervisor - self.dest_hypervisor = dest_hypervisor - - def set_migration_type(self, type): - self.migration_type = type - - def set_bandwidth(self, bw): - """Set the bandwidth reserved for the migration - - :param bw: bandwidth - """ - self.bandwidth = bw - - def get_bandwidth(self): - return self.bandwidth - - def get_vm(self): - return self.vm - - def get_source_hypervisor(self): - return self.source_hypervisor - - def get_dest_hypervisor(self): - return self.dest_hypervisor - - def __str__(self): - return "{0} Migrate {1} from {2} to {3}".format( - MetaAction.__str__(self), self.vm, - self.source_hypervisor, - self.dest_hypervisor) diff --git a/watcher/decision_engine/planner/default.py b/watcher/decision_engine/planner/default.py index 0c6300599..760417fdd 100644 --- a/watcher/decision_engine/planner/default.py +++ b/watcher/decision_engine/planner/default.py @@ -25,11 +25,11 @@ from watcher.decision_engine.planner.base import Planner from watcher import objects -from watcher.decision_engine.meta_action.hypervisor_state import \ +from watcher.decision_engine.actions.hypervisor_state import \ ChangeHypervisorState -from watcher.decision_engine.meta_action.migrate import Migrate -from watcher.decision_engine.meta_action.nop import Nop -from watcher.decision_engine.meta_action.power_state import ChangePowerState +from watcher.decision_engine.actions.migration import Migrate +from watcher.decision_engine.actions.nop import Nop +from watcher.decision_engine.actions.power_state import ChangePowerState from watcher.objects.action import Status as AStatus from watcher.objects.action_plan import Status as APStatus @@ -84,7 +84,7 @@ class DefaultPlanner(Planner): LOG.debug('Create an action plan for the audit uuid') action_plan = self._create_action_plan(context, audit_id) - actions = list(solution.meta_actions) + actions = list(solution.actions) to_schedule = [] for action in actions: @@ -92,10 +92,10 @@ class DefaultPlanner(Planner): # TODO(jed) type primitive = self.create_action(action_plan.id, Primitives.LIVE_MIGRATE.value, - action.get_vm().uuid, - action.get_source_hypervisor(). + action.vm.uuid, + action.src_hypervisor. uuid, - action.get_dest_hypervisor(). + action.dest_hypervisor. uuid, description="{0}".format( action) diff --git a/watcher/decision_engine/solution/base.py b/watcher/decision_engine/solution/base.py index 62f8d638a..c58593ae7 100644 --- a/watcher/decision_engine/solution/base.py +++ b/watcher/decision_engine/solution/base.py @@ -57,6 +57,6 @@ class Solution(object): "Should have implemented this") # pragma:no cover @abc.abstractproperty - def meta_actions(self): + def actions(self): raise NotImplementedError( "Should have implemented this") # pragma:no cover diff --git a/watcher/decision_engine/solution/default.py b/watcher/decision_engine/solution/default.py index 134e98089..bde4daf4d 100644 --- a/watcher/decision_engine/solution/default.py +++ b/watcher/decision_engine/solution/default.py @@ -24,20 +24,21 @@ LOG = log.getLogger(__name__) class DefaultSolution(Solution): def __init__(self): - self._meta_actions = [] - - def add_change_request(self, r): - self._meta_actions.append(r) - - def __str__(self): - val = "" - for action in self._meta_actions: - val += str(action) + "\n" - return val - - @property - def meta_actions(self): - """Get the current meta-actions of the solution + """The DefaultSolution class store a set of actions generated by a + strategy in order to achieve the goal. """ - return self._meta_actions + super(DefaultSolution, self).__init__() + self._actions = [] + + def add_change_request(self, r): + self._actions.append(r) + + def __str__(self): + return "\n".join(self._actions) + + @property + def actions(self): + """Get the current actions of the solution + """ + return self._actions diff --git a/watcher/decision_engine/strategy/basic_consolidation.py b/watcher/decision_engine/strategy/basic_consolidation.py index 040dd8cb3..83493a8bf 100644 --- a/watcher/decision_engine/strategy/basic_consolidation.py +++ b/watcher/decision_engine/strategy/basic_consolidation.py @@ -17,21 +17,19 @@ # limitations under the License. # from oslo_log import log - from watcher.common.exception import ClusterEmpty from watcher.common.exception import ClusteStateNotDefined -from watcher.decision_engine.strategy.base import BaseStrategy -from watcher.decision_engine.strategy.level import StrategyLevel - -from watcher.decision_engine.meta_action.hypervisor_state import \ +from watcher.decision_engine.actions.hypervisor_state import \ ChangeHypervisorState -from watcher.decision_engine.meta_action.migrate import Migrate -from watcher.decision_engine.meta_action.migrate import MigrationType -from watcher.decision_engine.meta_action.power_state import ChangePowerState -from watcher.decision_engine.meta_action.power_state import PowerState +from watcher.decision_engine.actions.migration import Migrate +from watcher.decision_engine.actions.migration import MigrationType +from watcher.decision_engine.actions.power_state import ChangePowerState from watcher.decision_engine.model.hypervisor_state import HypervisorState +from watcher.decision_engine.model.power_state import PowerState from watcher.decision_engine.model.resource import ResourceType from watcher.decision_engine.model.vm_state import VMState +from watcher.decision_engine.strategy.base import BaseStrategy +from watcher.decision_engine.strategy.level import StrategyLevel from watcher.metrics_engine.cluster_history.ceilometer import \ CeilometerClusterHistory @@ -39,7 +37,6 @@ LOG = log.getLogger(__name__) class BasicConsolidation(BaseStrategy): - DEFAULT_NAME = "basic" DEFAULT_DESCRIPTION = "Basic offline consolidation" @@ -447,8 +444,8 @@ class BasicConsolidation(BaseStrategy): mig_src_hypervisor, mig_dst_hypervisor) # live migration - live_migrate.set_migration_type( - MigrationType.pre_copy) + live_migrate.migration_type = \ + MigrationType.pre_copy live_migrate.level = StrategyLevel.conservative tmp_vm_migration_schedule.append(live_migrate) diff --git a/watcher/decision_engine/strategy/dummy_strategy.py b/watcher/decision_engine/strategy/dummy_strategy.py index 8198bfb15..0d06542b2 100644 --- a/watcher/decision_engine/strategy/dummy_strategy.py +++ b/watcher/decision_engine/strategy/dummy_strategy.py @@ -18,7 +18,7 @@ # from oslo_log import log -from watcher.decision_engine.meta_action.nop import Nop +from watcher.decision_engine.actions.nop import Nop from watcher.decision_engine.strategy.base import BaseStrategy LOG = log.getLogger(__name__) diff --git a/watcher/tests/decision_engine/meta_actions/__init__.py b/watcher/tests/decision_engine/meta_actions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/watcher/tests/decision_engine/meta_actions/test_migrate.py b/watcher/tests/decision_engine/meta_actions/test_migrate.py deleted file mode 100644 index d86b0b492..000000000 --- a/watcher/tests/decision_engine/meta_actions/test_migrate.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Authors: Jean-Emile DARTOIS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from watcher.decision_engine.meta_action.migrate import Migrate -from watcher.decision_engine.model.hypervisor import Hypervisor -from watcher.decision_engine.model.vm import VM -from watcher.tests import base - - -class TestMigtrate(base.BaseTestCase): - def test_set_get_bandwidth(self): - vm = VM() - hyp_src = Hypervisor() - hyp_dst = Hypervisor() - mig = Migrate(vm, hyp_src, hyp_dst) - mig.set_bandwidth(2) - self.assertEqual(mig.get_bandwidth(), 2) diff --git a/watcher/tests/decision_engine/test_meta_action.py b/watcher/tests/decision_engine/test_action.py similarity index 84% rename from watcher/tests/decision_engine/test_meta_action.py rename to watcher/tests/decision_engine/test_action.py index 24c692746..436bf5968 100644 --- a/watcher/tests/decision_engine/test_meta_action.py +++ b/watcher/tests/decision_engine/test_action.py @@ -14,17 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from watcher.decision_engine.meta_action.base import MetaAction +from watcher.decision_engine.actions.base import BaseAction from watcher.tests import base -class TestMetaAction(base.TestCase): +class TestAction(base.TestCase): def test_get_priority(self): - ma = MetaAction() + ma = BaseAction() ma.priority = 3 self.assertEqual(ma.priority, 3) def test_get_level(self): - ma = MetaAction() + ma = BaseAction() ma.level = 5 self.assertEqual(ma.level, 5) diff --git a/watcher/tests/decision_engine/test_basic_consolidation.py b/watcher/tests/decision_engine/test_basic_consolidation.py index 2e11ba3dc..3e67ecb6e 100644 --- a/watcher/tests/decision_engine/test_basic_consolidation.py +++ b/watcher/tests/decision_engine/test_basic_consolidation.py @@ -23,11 +23,11 @@ from mock import MagicMock from watcher.common import exception -from watcher.decision_engine.meta_action.hypervisor_state import \ +from watcher.decision_engine.actions.hypervisor_state import \ ChangeHypervisorState -from watcher.decision_engine.meta_action.power_state import ChangePowerState +from watcher.decision_engine.actions.power_state import ChangePowerState -from watcher.decision_engine.meta_action.migrate import Migrate +from watcher.decision_engine.actions.migration import Migrate from watcher.decision_engine.model.model_root import ModelRoot from watcher.decision_engine.strategy.basic_consolidation import \ BasicConsolidation @@ -151,24 +151,6 @@ class TestBasicConsolidation(base.BaseTestCase): self.assertRaises(exception.ClusteStateNotDefined, sercon.print_utilization, None) - def check_migration(self, array, indice, vm, src, dest): - """Helper to check migration - - :param array: - :param indice: - :param vm: - :param src: - :param dest: - :return: - """ - self.assertEqual(array[indice].get_vm().uuid, vm) - self.assertEqual(array[indice].get_source_hypervisor().uuid, src) - self.assertEqual(array[indice].get_dest_hypervisor().uuid, dest) - - self.assertEqual(array[indice].get_bandwidth(), 0) - array[indice].set_bandwidth(5) - self.assertEqual(array[indice].get_bandwidth(), 5) - def test_check_migration(self): sercon = BasicConsolidation() fake_cluster = FakerModelCollector() @@ -209,7 +191,7 @@ class TestBasicConsolidation(base.BaseTestCase): self.fake_cluster.generate_scenario_3()) actions_counter = Counter( - [type(action) for action in solution.meta_actions]) + [type(action) for action in solution.actions]) expected_num_migrations = 0 expected_power_state = 0 diff --git a/watcher/tests/decision_engine/test_default_planner.py b/watcher/tests/decision_engine/test_default_planner.py index 41ab7a2b9..a14785cd7 100644 --- a/watcher/tests/decision_engine/test_default_planner.py +++ b/watcher/tests/decision_engine/test_default_planner.py @@ -19,7 +19,7 @@ from mock import MagicMock from watcher.common.exception import MetaActionNotFound from watcher.common import utils from watcher.db import api as db_api -from watcher.decision_engine.meta_action.base import MetaAction +from watcher.decision_engine.actions.base import BaseAction from watcher.decision_engine.planner.default import DefaultPlanner from watcher.decision_engine.solution.default import DefaultSolution from watcher.decision_engine.strategy.basic_consolidation import \ @@ -62,8 +62,8 @@ class TestActionScheduling(base.DbTestCase): scenarios = [ (str(action_cls), {"fake_action": mock.Mock(spec=action_cls)}) - for action_cls in MetaAction.__subclasses__() - ] + for action_cls in BaseAction.__subclasses__() + ] def test_schedule_actions(self): default_planner = DefaultPlanner() @@ -119,7 +119,7 @@ class TestDefaultPlanner(base.DbTestCase): def test_schedule_raise(self): audit = db_utils.create_test_audit(uuid=utils.generate_uuid()) fake_solution = SolutionFaker.build() - fake_solution._meta_actions[0] = "valeur_qcq" + fake_solution.actions[0] = "valeur_qcq" self.assertRaises(MetaActionNotFound, self.default_planner.schedule, self.context, audit.id, fake_solution) diff --git a/watcher/tests/decision_engine/test_default_solution.py b/watcher/tests/decision_engine/test_default_solution.py index ca27e3026..228606aab 100644 --- a/watcher/tests/decision_engine/test_default_solution.py +++ b/watcher/tests/decision_engine/test_default_solution.py @@ -21,4 +21,4 @@ class TestDefaultSolution(base.BaseTestCase): def test_default_solution(self): solution = DefaultSolution() solution.add_change_request("BLA") - self.assertEqual(solution.meta_actions[0], "BLA") + self.assertEqual(solution.actions[0], "BLA")