From 74bc31e562044a7b5eacf8e65d7b5feaf83bec56 Mon Sep 17 00:00:00 2001 From: suzhengwei Date: Mon, 15 May 2017 15:40:18 +0800 Subject: [PATCH] extend-node-status add 'disabled_reason' filed into 'ComputeNode' resource, to distinguish which nodes are disabled by Watcher and which are not by Watcher. Implements:blueprint extend-node-status Change-Id: I7175f14870834a4582e45309529d7e8d9fbb2e6f --- .../applier/actions/change_nova_service_state.py | 15 ++++++++++++++- watcher/common/nova_helper.py | 7 ++++--- watcher/decision_engine/model/collector/nova.py | 3 ++- watcher/decision_engine/model/element/node.py | 2 +- .../decision_engine/model/notification/nova.py | 4 ++++ .../decision_engine/strategy/strategies/base.py | 2 ++ .../strategy/strategies/basic_consolidation.py | 9 ++++----- .../strategies/vm_workload_consolidation.py | 3 ++- .../actions/test_change_nova_service_state.py | 8 ++++++-- watcher/tests/common/test_nova_helper.py | 4 ++-- .../decision_engine/cluster/test_nova_cdmc.py | 8 +++++++- ...ario_9_with_3_active_plus_1_disabled_nodes.xml | 2 +- .../data/scenario3_service-update-disabled.json | 2 +- .../strategies/test_vm_workload_consolidation.py | 12 ++++++++---- 14 files changed, 58 insertions(+), 23 deletions(-) diff --git a/watcher/applier/actions/change_nova_service_state.py b/watcher/applier/actions/change_nova_service_state.py index a2d9792a1..166b449bf 100644 --- a/watcher/applier/actions/change_nova_service_state.py +++ b/watcher/applier/actions/change_nova_service_state.py @@ -36,15 +36,20 @@ class ChangeNovaServiceState(base.BaseAction): schema = Schema({ 'resource_id': str, 'state': str, + 'disabled_reason': str, }) The `resource_id` references a nova-compute service name (list of available nova-compute services is returned by this command: ``nova service-list --binary nova-compute``). The `state` value should either be `ONLINE` or `OFFLINE`. + The `disabled_reason` references the reason why Watcher disables this + nova-compute service. The value should be with `watcher_` prefix, such as + `watcher_disabled`, `watcher_maintaining`. """ STATE = 'state' + REASON = 'disabled_reason' @property def schema(self): @@ -61,6 +66,10 @@ class ChangeNovaServiceState(base.BaseAction): element.ServiceState.OFFLINE.value, element.ServiceState.ENABLED.value, element.ServiceState.DISABLED.value] + }, + 'disabled_reason': { + 'type': 'string', + "minlength": 1 } }, 'required': ['resource_id', 'state'], @@ -75,6 +84,10 @@ class ChangeNovaServiceState(base.BaseAction): def state(self): return self.input_parameters.get(self.STATE) + @property + def reason(self): + return self.input_parameters.get(self.REASON) + def execute(self): target_state = None if self.state == element.ServiceState.DISABLED.value: @@ -100,7 +113,7 @@ class ChangeNovaServiceState(base.BaseAction): if state is True: return nova.enable_service_nova_compute(self.host) else: - return nova.disable_service_nova_compute(self.host) + return nova.disable_service_nova_compute(self.host, self.reason) def pre_condition(self): pass diff --git a/watcher/common/nova_helper.py b/watcher/common/nova_helper.py index 72dae4188..201acb2cc 100644 --- a/watcher/common/nova_helper.py +++ b/watcher/common/nova_helper.py @@ -547,9 +547,10 @@ class NovaHelper(object): else: return False - def disable_service_nova_compute(self, hostname): - if self.nova.services.disable(host=hostname, - binary='nova-compute'). \ + def disable_service_nova_compute(self, hostname, reason=None): + if self.nova.services.disable_log_reason(host=hostname, + binary='nova-compute', + reason=reason). \ status == 'disabled': return True else: diff --git a/watcher/decision_engine/model/collector/nova.py b/watcher/decision_engine/model/collector/nova.py index a5fe3bd80..e4122a6bb 100644 --- a/watcher/decision_engine/model/collector/nova.py +++ b/watcher/decision_engine/model/collector/nova.py @@ -139,7 +139,8 @@ class ModelBuilder(object): "disk_capacity": node.local_gb, "vcpus": node.vcpus, "state": node.state, - "status": node.status} + "status": node.status, + "disabled_reason": compute_service.disabled_reason} compute_node = element.ComputeNode(**node_attributes) # compute_node = self._build_node("physical", "compute", "hypervisor", diff --git a/watcher/decision_engine/model/element/node.py b/watcher/decision_engine/model/element/node.py index 3807a6ff1..9619be792 100644 --- a/watcher/decision_engine/model/element/node.py +++ b/watcher/decision_engine/model/element/node.py @@ -36,8 +36,8 @@ class ComputeNode(compute_resource.ComputeResource): "id": wfields.NonNegativeIntegerField(), "hostname": wfields.StringField(), "status": wfields.StringField(default=ServiceState.ENABLED.value), + "disabled_reason": wfields.StringField(nullable=True), "state": wfields.StringField(default=ServiceState.ONLINE.value), - "memory": wfields.NonNegativeIntegerField(), "disk": wfields.IntegerField(), "disk_capacity": wfields.NonNegativeIntegerField(), diff --git a/watcher/decision_engine/model/notification/nova.py b/watcher/decision_engine/model/notification/nova.py index 42df5cdd2..f51bcb0db 100644 --- a/watcher/decision_engine/model/notification/nova.py +++ b/watcher/decision_engine/model/notification/nova.py @@ -122,11 +122,15 @@ class NovaNotification(base.NotificationEndpoint): node_status = ( element.ServiceState.DISABLED.value if node_data['disabled'] else element.ServiceState.ENABLED.value) + disabled_reason = ( + node_data['disabled_reason'] + if node_data['disabled'] else None) node.update({ 'hostname': node_data['host'], 'state': node_state, 'status': node_status, + 'disabled_reason': disabled_reason, }) def create_compute_node(self, node_hostname): diff --git a/watcher/decision_engine/strategy/strategies/base.py b/watcher/decision_engine/strategy/strategies/base.py index 406baa75b..6634dd5d3 100644 --- a/watcher/decision_engine/strategy/strategies/base.py +++ b/watcher/decision_engine/strategy/strategies/base.py @@ -331,6 +331,8 @@ class UnclassifiedStrategy(BaseStrategy): @six.add_metaclass(abc.ABCMeta) class ServerConsolidationBaseStrategy(BaseStrategy): + REASON_FOR_DISABLE = 'watcher_disabled' + @classmethod def get_goal_name(cls): return "server_consolidation" diff --git a/watcher/decision_engine/strategy/strategies/basic_consolidation.py b/watcher/decision_engine/strategy/strategies/basic_consolidation.py index 5618d0f6e..31d4ad863 100644 --- a/watcher/decision_engine/strategy/strategies/basic_consolidation.py +++ b/watcher/decision_engine/strategy/strategies/basic_consolidation.py @@ -407,8 +407,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy): return self.calculate_weight(instance, total_cores_used, 0, 0) - def add_change_service_state(self, resource_id, state): - parameters = {'state': state} + def add_action_disable_node(self, resource_id): + parameters = {'state': element.ServiceState.DISABLED.value, + 'disabled_reason': self.REASON_FOR_DISABLE} self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE, resource_id=resource_id, input_parameters=parameters) @@ -464,9 +465,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy): mig_destination_node.uuid) if len(self.compute_model.get_node_instances(mig_source_node)) == 0: - self.add_change_service_state(mig_source_node. - uuid, - element.ServiceState.DISABLED.value) + self.add_action_disable_node(mig_source_node.uuid) self.number_of_released_nodes += 1 def calculate_num_migrations(self, sorted_instances, node_to_release, diff --git a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py index 2c906579f..d4246c651 100644 --- a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py +++ b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py @@ -220,7 +220,8 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy): :param node: node object :return: None """ - params = {'state': element.ServiceState.DISABLED.value} + params = {'state': element.ServiceState.DISABLED.value, + 'disabled_reason': self.REASON_FOR_DISABLE} self.solution.add_action( action_type=self.CHANGE_NOVA_SERVICE_STATE, resource_id=node.uuid, diff --git a/watcher/tests/applier/actions/test_change_nova_service_state.py b/watcher/tests/applier/actions/test_change_nova_service_state.py index e2f016c56..905b2a775 100644 --- a/watcher/tests/applier/actions/test_change_nova_service_state.py +++ b/watcher/tests/applier/actions/test_change_nova_service_state.py @@ -110,18 +110,22 @@ class TestChangeNovaServiceState(base.TestCase): def test_execute_change_service_state_with_disable_target(self): self.action.input_parameters["state"] = ( element.ServiceState.DISABLED.value) + self.action.input_parameters["disabled_reason"] = ( + "watcher_disabled") self.action.execute() self.m_helper_cls.assert_called_once_with(osc=self.m_osc) self.m_helper.disable_service_nova_compute.assert_called_once_with( - "compute-1") + "compute-1", "watcher_disabled") def test_revert_change_service_state_with_enable_target(self): + self.action.input_parameters["disabled_reason"] = ( + "watcher_disabled") self.action.revert() self.m_helper_cls.assert_called_once_with(osc=self.m_osc) self.m_helper.disable_service_nova_compute.assert_called_once_with( - "compute-1") + "compute-1", "watcher_disabled") def test_revert_change_service_state_with_disable_target(self): self.action.input_parameters["state"] = ( diff --git a/watcher/tests/common/test_nova_helper.py b/watcher/tests/common/test_nova_helper.py index a554b502c..bf9869224 100644 --- a/watcher/tests/common/test_nova_helper.py +++ b/watcher/tests/common/test_nova_helper.py @@ -328,13 +328,13 @@ class TestNovaHelper(base.TestCase): mock_neutron, mock_nova): nova_util = nova_helper.NovaHelper() nova_services = nova_util.nova.services - nova_services.disable.return_value = mock.MagicMock( + nova_services.disable_log_reason.return_value = mock.MagicMock( status='enabled') result = nova_util.disable_service_nova_compute('nanjing') self.assertFalse(result) - nova_services.disable.return_value = mock.MagicMock( + nova_services.disable_log_reason.return_value = mock.MagicMock( status='disabled') result = nova_util.disable_service_nova_compute('nanjing') diff --git a/watcher/tests/decision_engine/cluster/test_nova_cdmc.py b/watcher/tests/decision_engine/cluster/test_nova_cdmc.py index a6857667e..6a3b46158 100644 --- a/watcher/tests/decision_engine/cluster/test_nova_cdmc.py +++ b/watcher/tests/decision_engine/cluster/test_nova_cdmc.py @@ -37,7 +37,13 @@ class TestNovaClusterDataModelCollector(base.TestCase): m_nova_helper = mock.Mock(name="nova_helper") m_nova_helper_cls.return_value = m_nova_helper m_nova_helper.get_service.return_value = mock.Mock( - host="test_hostname") + id=1355, + host='test_hostname', + binary='nova-compute', + status='enabled', + state='up', + disabled_reason='', + ) fake_compute_node = mock.Mock( id=1337, diff --git a/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml index d1d3f94eb..488f1c7f0 100644 --- a/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml +++ b/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml @@ -10,7 +10,7 @@ - + diff --git a/watcher/tests/decision_engine/model/notification/data/scenario3_service-update-disabled.json b/watcher/tests/decision_engine/model/notification/data/scenario3_service-update-disabled.json index 410f12d0e..07cc00e34 100644 --- a/watcher/tests/decision_engine/model/notification/data/scenario3_service-update-disabled.json +++ b/watcher/tests/decision_engine/model/notification/data/scenario3_service-update-disabled.json @@ -10,7 +10,7 @@ "last_seen_up": "2012-10-29T13:42:05Z", "binary": "nova-compute", "topic": "compute", - "disabled_reason": null, + "disabled_reason": "watcher_disabled", "report_count": 1, "forced_down": true, "version": 15 diff --git a/watcher/tests/decision_engine/strategy/strategies/test_vm_workload_consolidation.py b/watcher/tests/decision_engine/strategy/strategies/test_vm_workload_consolidation.py index 0f8382410..e09fdd86a 100644 --- a/watcher/tests/decision_engine/strategy/strategies/test_vm_workload_consolidation.py +++ b/watcher/tests/decision_engine/strategy/strategies/test_vm_workload_consolidation.py @@ -197,8 +197,10 @@ class TestVMWorkloadConsolidation(base.TestCase): n = model.get_node_by_uuid('Node_0') self.strategy.add_action_disable_node(n) expected = [{'action_type': 'change_nova_service_state', - 'input_parameters': {'state': 'disabled', - 'resource_id': 'Node_0'}}] + 'input_parameters': { + 'state': 'disabled', + 'disabled_reason': 'watcher_disabled', + 'resource_id': 'Node_0'}}] self.assertEqual(expected, self.strategy.solution.actions) def test_disable_unused_nodes(self): @@ -217,8 +219,10 @@ class TestVMWorkloadConsolidation(base.TestCase): self.strategy.disable_unused_nodes() expected = {'action_type': 'change_nova_service_state', - 'input_parameters': {'state': 'disabled', - 'resource_id': 'Node_0'}} + 'input_parameters': { + 'state': 'disabled', + 'disabled_reason': 'watcher_disabled', + 'resource_id': 'Node_0'}} self.assertEqual(2, len(self.strategy.solution.actions)) self.assertEqual(expected, self.strategy.solution.actions[1])