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
This commit is contained in:
@@ -36,15 +36,20 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
schema = Schema({
|
schema = Schema({
|
||||||
'resource_id': str,
|
'resource_id': str,
|
||||||
'state': str,
|
'state': str,
|
||||||
|
'disabled_reason': str,
|
||||||
})
|
})
|
||||||
|
|
||||||
The `resource_id` references a nova-compute service name (list of available
|
The `resource_id` references a nova-compute service name (list of available
|
||||||
nova-compute services is returned by this command: ``nova service-list
|
nova-compute services is returned by this command: ``nova service-list
|
||||||
--binary nova-compute``).
|
--binary nova-compute``).
|
||||||
The `state` value should either be `ONLINE` or `OFFLINE`.
|
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'
|
STATE = 'state'
|
||||||
|
REASON = 'disabled_reason'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
@@ -61,6 +66,10 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
element.ServiceState.OFFLINE.value,
|
element.ServiceState.OFFLINE.value,
|
||||||
element.ServiceState.ENABLED.value,
|
element.ServiceState.ENABLED.value,
|
||||||
element.ServiceState.DISABLED.value]
|
element.ServiceState.DISABLED.value]
|
||||||
|
},
|
||||||
|
'disabled_reason': {
|
||||||
|
'type': 'string',
|
||||||
|
"minlength": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'required': ['resource_id', 'state'],
|
'required': ['resource_id', 'state'],
|
||||||
@@ -75,6 +84,10 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
def state(self):
|
def state(self):
|
||||||
return self.input_parameters.get(self.STATE)
|
return self.input_parameters.get(self.STATE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reason(self):
|
||||||
|
return self.input_parameters.get(self.REASON)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
target_state = None
|
target_state = None
|
||||||
if self.state == element.ServiceState.DISABLED.value:
|
if self.state == element.ServiceState.DISABLED.value:
|
||||||
@@ -100,7 +113,7 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
if state is True:
|
if state is True:
|
||||||
return nova.enable_service_nova_compute(self.host)
|
return nova.enable_service_nova_compute(self.host)
|
||||||
else:
|
else:
|
||||||
return nova.disable_service_nova_compute(self.host)
|
return nova.disable_service_nova_compute(self.host, self.reason)
|
||||||
|
|
||||||
def pre_condition(self):
|
def pre_condition(self):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -547,9 +547,10 @@ class NovaHelper(object):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def disable_service_nova_compute(self, hostname):
|
def disable_service_nova_compute(self, hostname, reason=None):
|
||||||
if self.nova.services.disable(host=hostname,
|
if self.nova.services.disable_log_reason(host=hostname,
|
||||||
binary='nova-compute'). \
|
binary='nova-compute',
|
||||||
|
reason=reason). \
|
||||||
status == 'disabled':
|
status == 'disabled':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class ModelBuilder(object):
|
|||||||
"disk_capacity": node.local_gb,
|
"disk_capacity": node.local_gb,
|
||||||
"vcpus": node.vcpus,
|
"vcpus": node.vcpus,
|
||||||
"state": node.state,
|
"state": node.state,
|
||||||
"status": node.status}
|
"status": node.status,
|
||||||
|
"disabled_reason": compute_service.disabled_reason}
|
||||||
|
|
||||||
compute_node = element.ComputeNode(**node_attributes)
|
compute_node = element.ComputeNode(**node_attributes)
|
||||||
# compute_node = self._build_node("physical", "compute", "hypervisor",
|
# compute_node = self._build_node("physical", "compute", "hypervisor",
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ class ComputeNode(compute_resource.ComputeResource):
|
|||||||
"id": wfields.NonNegativeIntegerField(),
|
"id": wfields.NonNegativeIntegerField(),
|
||||||
"hostname": wfields.StringField(),
|
"hostname": wfields.StringField(),
|
||||||
"status": wfields.StringField(default=ServiceState.ENABLED.value),
|
"status": wfields.StringField(default=ServiceState.ENABLED.value),
|
||||||
|
"disabled_reason": wfields.StringField(nullable=True),
|
||||||
"state": wfields.StringField(default=ServiceState.ONLINE.value),
|
"state": wfields.StringField(default=ServiceState.ONLINE.value),
|
||||||
|
|
||||||
"memory": wfields.NonNegativeIntegerField(),
|
"memory": wfields.NonNegativeIntegerField(),
|
||||||
"disk": wfields.IntegerField(),
|
"disk": wfields.IntegerField(),
|
||||||
"disk_capacity": wfields.NonNegativeIntegerField(),
|
"disk_capacity": wfields.NonNegativeIntegerField(),
|
||||||
|
|||||||
@@ -122,11 +122,15 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
node_status = (
|
node_status = (
|
||||||
element.ServiceState.DISABLED.value
|
element.ServiceState.DISABLED.value
|
||||||
if node_data['disabled'] else element.ServiceState.ENABLED.value)
|
if node_data['disabled'] else element.ServiceState.ENABLED.value)
|
||||||
|
disabled_reason = (
|
||||||
|
node_data['disabled_reason']
|
||||||
|
if node_data['disabled'] else None)
|
||||||
|
|
||||||
node.update({
|
node.update({
|
||||||
'hostname': node_data['host'],
|
'hostname': node_data['host'],
|
||||||
'state': node_state,
|
'state': node_state,
|
||||||
'status': node_status,
|
'status': node_status,
|
||||||
|
'disabled_reason': disabled_reason,
|
||||||
})
|
})
|
||||||
|
|
||||||
def create_compute_node(self, node_hostname):
|
def create_compute_node(self, node_hostname):
|
||||||
|
|||||||
@@ -331,6 +331,8 @@ class UnclassifiedStrategy(BaseStrategy):
|
|||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class ServerConsolidationBaseStrategy(BaseStrategy):
|
class ServerConsolidationBaseStrategy(BaseStrategy):
|
||||||
|
|
||||||
|
REASON_FOR_DISABLE = 'watcher_disabled'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_goal_name(cls):
|
def get_goal_name(cls):
|
||||||
return "server_consolidation"
|
return "server_consolidation"
|
||||||
|
|||||||
@@ -407,8 +407,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
return self.calculate_weight(instance, total_cores_used, 0, 0)
|
return self.calculate_weight(instance, total_cores_used, 0, 0)
|
||||||
|
|
||||||
def add_change_service_state(self, resource_id, state):
|
def add_action_disable_node(self, resource_id):
|
||||||
parameters = {'state': state}
|
parameters = {'state': element.ServiceState.DISABLED.value,
|
||||||
|
'disabled_reason': self.REASON_FOR_DISABLE}
|
||||||
self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
input_parameters=parameters)
|
input_parameters=parameters)
|
||||||
@@ -464,9 +465,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
mig_destination_node.uuid)
|
mig_destination_node.uuid)
|
||||||
|
|
||||||
if len(self.compute_model.get_node_instances(mig_source_node)) == 0:
|
if len(self.compute_model.get_node_instances(mig_source_node)) == 0:
|
||||||
self.add_change_service_state(mig_source_node.
|
self.add_action_disable_node(mig_source_node.uuid)
|
||||||
uuid,
|
|
||||||
element.ServiceState.DISABLED.value)
|
|
||||||
self.number_of_released_nodes += 1
|
self.number_of_released_nodes += 1
|
||||||
|
|
||||||
def calculate_num_migrations(self, sorted_instances, node_to_release,
|
def calculate_num_migrations(self, sorted_instances, node_to_release,
|
||||||
|
|||||||
@@ -220,7 +220,8 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
:param node: node object
|
:param node: node object
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
params = {'state': element.ServiceState.DISABLED.value}
|
params = {'state': element.ServiceState.DISABLED.value,
|
||||||
|
'disabled_reason': self.REASON_FOR_DISABLE}
|
||||||
self.solution.add_action(
|
self.solution.add_action(
|
||||||
action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
||||||
resource_id=node.uuid,
|
resource_id=node.uuid,
|
||||||
|
|||||||
@@ -110,18 +110,22 @@ class TestChangeNovaServiceState(base.TestCase):
|
|||||||
def test_execute_change_service_state_with_disable_target(self):
|
def test_execute_change_service_state_with_disable_target(self):
|
||||||
self.action.input_parameters["state"] = (
|
self.action.input_parameters["state"] = (
|
||||||
element.ServiceState.DISABLED.value)
|
element.ServiceState.DISABLED.value)
|
||||||
|
self.action.input_parameters["disabled_reason"] = (
|
||||||
|
"watcher_disabled")
|
||||||
self.action.execute()
|
self.action.execute()
|
||||||
|
|
||||||
self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
|
self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
|
||||||
self.m_helper.disable_service_nova_compute.assert_called_once_with(
|
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):
|
def test_revert_change_service_state_with_enable_target(self):
|
||||||
|
self.action.input_parameters["disabled_reason"] = (
|
||||||
|
"watcher_disabled")
|
||||||
self.action.revert()
|
self.action.revert()
|
||||||
|
|
||||||
self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
|
self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
|
||||||
self.m_helper.disable_service_nova_compute.assert_called_once_with(
|
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):
|
def test_revert_change_service_state_with_disable_target(self):
|
||||||
self.action.input_parameters["state"] = (
|
self.action.input_parameters["state"] = (
|
||||||
|
|||||||
@@ -328,13 +328,13 @@ class TestNovaHelper(base.TestCase):
|
|||||||
mock_neutron, mock_nova):
|
mock_neutron, mock_nova):
|
||||||
nova_util = nova_helper.NovaHelper()
|
nova_util = nova_helper.NovaHelper()
|
||||||
nova_services = nova_util.nova.services
|
nova_services = nova_util.nova.services
|
||||||
nova_services.disable.return_value = mock.MagicMock(
|
nova_services.disable_log_reason.return_value = mock.MagicMock(
|
||||||
status='enabled')
|
status='enabled')
|
||||||
|
|
||||||
result = nova_util.disable_service_nova_compute('nanjing')
|
result = nova_util.disable_service_nova_compute('nanjing')
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
|
||||||
nova_services.disable.return_value = mock.MagicMock(
|
nova_services.disable_log_reason.return_value = mock.MagicMock(
|
||||||
status='disabled')
|
status='disabled')
|
||||||
|
|
||||||
result = nova_util.disable_service_nova_compute('nanjing')
|
result = nova_util.disable_service_nova_compute('nanjing')
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ class TestNovaClusterDataModelCollector(base.TestCase):
|
|||||||
m_nova_helper = mock.Mock(name="nova_helper")
|
m_nova_helper = mock.Mock(name="nova_helper")
|
||||||
m_nova_helper_cls.return_value = m_nova_helper
|
m_nova_helper_cls.return_value = m_nova_helper
|
||||||
m_nova_helper.get_service.return_value = mock.Mock(
|
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(
|
fake_compute_node = mock.Mock(
|
||||||
id=1337,
|
id=1337,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<ComputeNode hostname="hostname_2" uuid="Node_2" id="2" state="up" human_id="" status="enabled" vcpus="16" disk="250" disk_capacity="250" memory="64">
|
<ComputeNode hostname="hostname_2" uuid="Node_2" id="2" state="up" human_id="" status="enabled" vcpus="16" disk="250" disk_capacity="250" memory="64">
|
||||||
<Instance human_id="" state="active" uuid="INSTANCE_4" vcpus="2" disk="20" disk_capacity="20" memory="2" metadata='{"optimize": true,"top": "floor", "nested": {"x": "y"}}'/>
|
<Instance human_id="" state="active" uuid="INSTANCE_4" vcpus="2" disk="20" disk_capacity="20" memory="2" metadata='{"optimize": true,"top": "floor", "nested": {"x": "y"}}'/>
|
||||||
</ComputeNode>
|
</ComputeNode>
|
||||||
<ComputeNode hostname="hostname_3" uuid="Node_3" id="3" state="up" human_id="" status="disabled" vcpus="16" disk="250" disk_capacity="250" memory="64">
|
<ComputeNode hostname="hostname_3" uuid="Node_3" id="3" state="up" human_id="" status="disabled" disabled_reason='watcher_disabled' vcpus="16" disk="250" disk_capacity="250" memory="64">
|
||||||
<Instance human_id="" state="active" uuid="INSTANCE_5" vcpus="2" disk="20" disk_capacity="20" memory="2" metadata='{"optimize": true,"top": "floor", "nested": {"x": "y"}}'/>
|
<Instance human_id="" state="active" uuid="INSTANCE_5" vcpus="2" disk="20" disk_capacity="20" memory="2" metadata='{"optimize": true,"top": "floor", "nested": {"x": "y"}}'/>
|
||||||
</ComputeNode>
|
</ComputeNode>
|
||||||
</ModelRoot>
|
</ModelRoot>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"last_seen_up": "2012-10-29T13:42:05Z",
|
"last_seen_up": "2012-10-29T13:42:05Z",
|
||||||
"binary": "nova-compute",
|
"binary": "nova-compute",
|
||||||
"topic": "compute",
|
"topic": "compute",
|
||||||
"disabled_reason": null,
|
"disabled_reason": "watcher_disabled",
|
||||||
"report_count": 1,
|
"report_count": 1,
|
||||||
"forced_down": true,
|
"forced_down": true,
|
||||||
"version": 15
|
"version": 15
|
||||||
|
|||||||
@@ -197,8 +197,10 @@ class TestVMWorkloadConsolidation(base.TestCase):
|
|||||||
n = model.get_node_by_uuid('Node_0')
|
n = model.get_node_by_uuid('Node_0')
|
||||||
self.strategy.add_action_disable_node(n)
|
self.strategy.add_action_disable_node(n)
|
||||||
expected = [{'action_type': 'change_nova_service_state',
|
expected = [{'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {'state': 'disabled',
|
'input_parameters': {
|
||||||
'resource_id': 'Node_0'}}]
|
'state': 'disabled',
|
||||||
|
'disabled_reason': 'watcher_disabled',
|
||||||
|
'resource_id': 'Node_0'}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_disable_unused_nodes(self):
|
def test_disable_unused_nodes(self):
|
||||||
@@ -217,8 +219,10 @@ class TestVMWorkloadConsolidation(base.TestCase):
|
|||||||
|
|
||||||
self.strategy.disable_unused_nodes()
|
self.strategy.disable_unused_nodes()
|
||||||
expected = {'action_type': 'change_nova_service_state',
|
expected = {'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {'state': 'disabled',
|
'input_parameters': {
|
||||||
'resource_id': 'Node_0'}}
|
'state': 'disabled',
|
||||||
|
'disabled_reason': 'watcher_disabled',
|
||||||
|
'resource_id': 'Node_0'}}
|
||||||
self.assertEqual(2, len(self.strategy.solution.actions))
|
self.assertEqual(2, len(self.strategy.solution.actions))
|
||||||
self.assertEqual(expected, self.strategy.solution.actions[1])
|
self.assertEqual(expected, self.strategy.solution.actions[1])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user