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 c4b0e7399..957407a69 100644
--- a/watcher/common/nova_helper.py
+++ b/watcher/common/nova_helper.py
@@ -545,9 +545,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 1aa15dc57..4aa2f5ee7 100644
--- a/watcher/decision_engine/model/collector/nova.py
+++ b/watcher/decision_engine/model/collector/nova.py
@@ -248,7 +248,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 cc33909a4..fc9e3f2d2 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.StringField(),
"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 f45fb041b..8ef0e3693 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 9a97b8fff..6e508793e 100644
--- a/watcher/decision_engine/strategy/strategies/base.py
+++ b/watcher/decision_engine/strategy/strategies/base.py
@@ -324,6 +324,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 224126c69..9fef5baed 100644
--- a/watcher/decision_engine/strategy/strategies/basic_consolidation.py
+++ b/watcher/decision_engine/strategy/strategies/basic_consolidation.py
@@ -415,8 +415,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)
@@ -472,9 +473,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 a7bc1eefc..ab0e717fa 100644
--- a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py
+++ b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py
@@ -228,7 +228,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 778b569ee..500e4f549 100644
--- a/watcher/tests/common/test_nova_helper.py
+++ b/watcher/tests/common/test_nova_helper.py
@@ -349,13 +349,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 8eb173e50..715ed01d6 100644
--- a/watcher/tests/decision_engine/cluster/test_nova_cdmc.py
+++ b/watcher/tests/decision_engine/cluster/test_nova_cdmc.py
@@ -36,7 +36,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])