replace windows line endings with unix line endings
The python 3 version of the linter does not allow Windows-style line endings (\r\n) so replace them with UNIX-style endings (\n). Change-Id: Ifb97491323d3df92bb1520e373552aeb5e1919a4 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
@@ -1,331 +1,331 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2017 chinac.com
|
# Copyright (c) 2017 chinac.com
|
||||||
#
|
#
|
||||||
# Authors: suzhengwei<suzhengwei@chinac.com>
|
# Authors: suzhengwei<suzhengwei@chinac.com>
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.common import exception as wexc
|
from watcher.common import exception as wexc
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HostMaintenance(base.HostMaintenanceBaseStrategy):
|
class HostMaintenance(base.HostMaintenanceBaseStrategy):
|
||||||
"""[PoC]Host Maintenance
|
"""[PoC]Host Maintenance
|
||||||
|
|
||||||
*Description*
|
*Description*
|
||||||
|
|
||||||
It is a migration strategy for one compute node maintenance,
|
It is a migration strategy for one compute node maintenance,
|
||||||
without having the user's application been interruptted.
|
without having the user's application been interruptted.
|
||||||
If given one backup node, the strategy will firstly
|
If given one backup node, the strategy will firstly
|
||||||
migrate all instances from the maintenance node to
|
migrate all instances from the maintenance node to
|
||||||
the backup node. If the backup node is not provided,
|
the backup node. If the backup node is not provided,
|
||||||
it will migrate all instances, relying on nova-scheduler.
|
it will migrate all instances, relying on nova-scheduler.
|
||||||
|
|
||||||
*Requirements*
|
*Requirements*
|
||||||
|
|
||||||
* You must have at least 2 physical compute nodes to run this strategy.
|
* You must have at least 2 physical compute nodes to run this strategy.
|
||||||
|
|
||||||
*Limitations*
|
*Limitations*
|
||||||
|
|
||||||
- This is a proof of concept that is not meant to be used in production
|
- This is a proof of concept that is not meant to be used in production
|
||||||
- It migrates all instances from one host to other hosts. It's better to
|
- It migrates all instances from one host to other hosts. It's better to
|
||||||
execute such strategy when load is not heavy, and use this algorithm
|
execute such strategy when load is not heavy, and use this algorithm
|
||||||
with `ONESHOT` audit.
|
with `ONESHOT` audit.
|
||||||
- It assume that cold and live migrations are possible
|
- It assume that cold and live migrations are possible
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INSTANCE_MIGRATION = "migrate"
|
INSTANCE_MIGRATION = "migrate"
|
||||||
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
||||||
REASON_FOR_DISABLE = 'watcher_disabled'
|
REASON_FOR_DISABLE = 'watcher_disabled'
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
super(HostMaintenance, self).__init__(config, osc)
|
super(HostMaintenance, self).__init__(config, osc)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
return "host_maintenance"
|
return "host_maintenance"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_display_name(cls):
|
def get_display_name(cls):
|
||||||
return _("Host Maintenance Strategy")
|
return _("Host Maintenance Strategy")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_translatable_display_name(cls):
|
def get_translatable_display_name(cls):
|
||||||
return "Host Maintenance Strategy"
|
return "Host Maintenance Strategy"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_schema(cls):
|
def get_schema(cls):
|
||||||
return {
|
return {
|
||||||
"properties": {
|
"properties": {
|
||||||
"maintenance_node": {
|
"maintenance_node": {
|
||||||
"description": "The name of the compute node which "
|
"description": "The name of the compute node which "
|
||||||
"need maintenance",
|
"need maintenance",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"backup_node": {
|
"backup_node": {
|
||||||
"description": "The name of the compute node which "
|
"description": "The name of the compute node which "
|
||||||
"will backup the maintenance node.",
|
"will backup the maintenance node.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["maintenance_node"],
|
"required": ["maintenance_node"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_disabled_compute_nodes_with_reason(self, reason=None):
|
def get_disabled_compute_nodes_with_reason(self, reason=None):
|
||||||
return {uuid: cn for uuid, cn in
|
return {uuid: cn for uuid, cn in
|
||||||
self.compute_model.get_all_compute_nodes().items()
|
self.compute_model.get_all_compute_nodes().items()
|
||||||
if cn.state == element.ServiceState.ONLINE.value and
|
if cn.state == element.ServiceState.ONLINE.value and
|
||||||
cn.status == element.ServiceState.DISABLED.value and
|
cn.status == element.ServiceState.DISABLED.value and
|
||||||
cn.disabled_reason == reason}
|
cn.disabled_reason == reason}
|
||||||
|
|
||||||
def get_disabled_compute_nodes(self):
|
def get_disabled_compute_nodes(self):
|
||||||
return self.get_disabled_compute_nodes_with_reason(
|
return self.get_disabled_compute_nodes_with_reason(
|
||||||
self.REASON_FOR_DISABLE)
|
self.REASON_FOR_DISABLE)
|
||||||
|
|
||||||
def get_instance_state_str(self, instance):
|
def get_instance_state_str(self, instance):
|
||||||
"""Get instance state in string format"""
|
"""Get instance state in string format"""
|
||||||
if isinstance(instance.state, six.string_types):
|
if isinstance(instance.state, six.string_types):
|
||||||
return instance.state
|
return instance.state
|
||||||
elif isinstance(instance.state, element.InstanceState):
|
elif isinstance(instance.state, element.InstanceState):
|
||||||
return instance.state.value
|
return instance.state.value
|
||||||
else:
|
else:
|
||||||
LOG.error('Unexpected instance state type, '
|
LOG.error('Unexpected instance state type, '
|
||||||
'state=%(state)s, state_type=%(st)s.',
|
'state=%(state)s, state_type=%(st)s.',
|
||||||
dict(state=instance.state,
|
dict(state=instance.state,
|
||||||
st=type(instance.state)))
|
st=type(instance.state)))
|
||||||
raise wexc.WatcherException
|
raise wexc.WatcherException
|
||||||
|
|
||||||
def get_node_status_str(self, node):
|
def get_node_status_str(self, node):
|
||||||
"""Get node status in string format"""
|
"""Get node status in string format"""
|
||||||
if isinstance(node.status, six.string_types):
|
if isinstance(node.status, six.string_types):
|
||||||
return node.status
|
return node.status
|
||||||
elif isinstance(node.status, element.ServiceState):
|
elif isinstance(node.status, element.ServiceState):
|
||||||
return node.status.value
|
return node.status.value
|
||||||
else:
|
else:
|
||||||
LOG.error('Unexpected node status type, '
|
LOG.error('Unexpected node status type, '
|
||||||
'status=%(status)s, status_type=%(st)s.',
|
'status=%(status)s, status_type=%(st)s.',
|
||||||
dict(status=node.status,
|
dict(status=node.status,
|
||||||
st=type(node.status)))
|
st=type(node.status)))
|
||||||
raise wexc.WatcherException
|
raise wexc.WatcherException
|
||||||
|
|
||||||
def get_node_capacity(self, node):
|
def get_node_capacity(self, node):
|
||||||
"""Collect cpu, ram and disk capacity of a node.
|
"""Collect cpu, ram and disk capacity of a node.
|
||||||
|
|
||||||
:param node: node object
|
:param node: node object
|
||||||
:return: dict(cpu(cores), ram(MB), disk(B))
|
:return: dict(cpu(cores), ram(MB), disk(B))
|
||||||
"""
|
"""
|
||||||
return dict(cpu=node.vcpus,
|
return dict(cpu=node.vcpus,
|
||||||
ram=node.memory,
|
ram=node.memory,
|
||||||
disk=node.disk_capacity)
|
disk=node.disk_capacity)
|
||||||
|
|
||||||
def get_node_used(self, node):
|
def get_node_used(self, node):
|
||||||
"""Collect cpu, ram and disk used of a node.
|
"""Collect cpu, ram and disk used of a node.
|
||||||
|
|
||||||
:param node: node object
|
:param node: node object
|
||||||
:return: dict(cpu(cores), ram(MB), disk(B))
|
:return: dict(cpu(cores), ram(MB), disk(B))
|
||||||
"""
|
"""
|
||||||
vcpus_used = 0
|
vcpus_used = 0
|
||||||
memory_used = 0
|
memory_used = 0
|
||||||
disk_used = 0
|
disk_used = 0
|
||||||
for instance in self.compute_model.get_node_instances(node):
|
for instance in self.compute_model.get_node_instances(node):
|
||||||
vcpus_used += instance.vcpus
|
vcpus_used += instance.vcpus
|
||||||
memory_used += instance.memory
|
memory_used += instance.memory
|
||||||
disk_used += instance.disk
|
disk_used += instance.disk
|
||||||
|
|
||||||
return dict(cpu=vcpus_used,
|
return dict(cpu=vcpus_used,
|
||||||
ram=memory_used,
|
ram=memory_used,
|
||||||
disk=disk_used)
|
disk=disk_used)
|
||||||
|
|
||||||
def get_node_free(self, node):
|
def get_node_free(self, node):
|
||||||
"""Collect cpu, ram and disk free of a node.
|
"""Collect cpu, ram and disk free of a node.
|
||||||
|
|
||||||
:param node: node object
|
:param node: node object
|
||||||
:return: dict(cpu(cores), ram(MB), disk(B))
|
:return: dict(cpu(cores), ram(MB), disk(B))
|
||||||
"""
|
"""
|
||||||
node_capacity = self.get_node_capacity(node)
|
node_capacity = self.get_node_capacity(node)
|
||||||
node_used = self.get_node_used(node)
|
node_used = self.get_node_used(node)
|
||||||
return dict(cpu=node_capacity['cpu']-node_used['cpu'],
|
return dict(cpu=node_capacity['cpu']-node_used['cpu'],
|
||||||
ram=node_capacity['ram']-node_used['ram'],
|
ram=node_capacity['ram']-node_used['ram'],
|
||||||
disk=node_capacity['disk']-node_used['disk'],
|
disk=node_capacity['disk']-node_used['disk'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def host_fits(self, source_node, destination_node):
|
def host_fits(self, source_node, destination_node):
|
||||||
"""check host fits
|
"""check host fits
|
||||||
|
|
||||||
return True if VMs could intensively migrate
|
return True if VMs could intensively migrate
|
||||||
from source_node to destination_node.
|
from source_node to destination_node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source_node_used = self.get_node_used(source_node)
|
source_node_used = self.get_node_used(source_node)
|
||||||
destination_node_free = self.get_node_free(destination_node)
|
destination_node_free = self.get_node_free(destination_node)
|
||||||
metrics = ['cpu', 'ram']
|
metrics = ['cpu', 'ram']
|
||||||
for m in metrics:
|
for m in metrics:
|
||||||
if source_node_used[m] > destination_node_free[m]:
|
if source_node_used[m] > destination_node_free[m]:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def add_action_enable_compute_node(self, node):
|
def add_action_enable_compute_node(self, node):
|
||||||
"""Add an action for node enabler into the solution."""
|
"""Add an action for node enabler into the solution."""
|
||||||
params = {'state': element.ServiceState.ENABLED.value}
|
params = {'state': element.ServiceState.ENABLED.value}
|
||||||
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,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
|
|
||||||
def add_action_maintain_compute_node(self, node):
|
def add_action_maintain_compute_node(self, node):
|
||||||
"""Add an action for node maintenance into the solution."""
|
"""Add an action for node maintenance into the solution."""
|
||||||
params = {'state': element.ServiceState.DISABLED.value,
|
params = {'state': element.ServiceState.DISABLED.value,
|
||||||
'disabled_reason': self.REASON_FOR_MAINTAINING}
|
'disabled_reason': self.REASON_FOR_MAINTAINING}
|
||||||
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,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
|
|
||||||
def enable_compute_node_if_disabled(self, node):
|
def enable_compute_node_if_disabled(self, node):
|
||||||
node_status_str = self.get_node_status_str(node)
|
node_status_str = self.get_node_status_str(node)
|
||||||
if node_status_str != element.ServiceState.ENABLED.value:
|
if node_status_str != element.ServiceState.ENABLED.value:
|
||||||
self.add_action_enable_compute_node(node)
|
self.add_action_enable_compute_node(node)
|
||||||
|
|
||||||
def instance_migration(self, instance, src_node, des_node=None):
|
def instance_migration(self, instance, src_node, des_node=None):
|
||||||
"""Add an action for instance migration into the solution.
|
"""Add an action for instance migration into the solution.
|
||||||
|
|
||||||
:param instance: instance object
|
:param instance: instance object
|
||||||
:param src_node: node object
|
:param src_node: node object
|
||||||
:param des_node: node object. if None, the instance will be
|
:param des_node: node object. if None, the instance will be
|
||||||
migrated relying on nova-scheduler
|
migrated relying on nova-scheduler
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
instance_state_str = self.get_instance_state_str(instance)
|
instance_state_str = self.get_instance_state_str(instance)
|
||||||
if instance_state_str == element.InstanceState.ACTIVE.value:
|
if instance_state_str == element.InstanceState.ACTIVE.value:
|
||||||
migration_type = 'live'
|
migration_type = 'live'
|
||||||
else:
|
else:
|
||||||
migration_type = 'cold'
|
migration_type = 'cold'
|
||||||
|
|
||||||
params = {'migration_type': migration_type,
|
params = {'migration_type': migration_type,
|
||||||
'source_node': src_node.uuid}
|
'source_node': src_node.uuid}
|
||||||
if des_node:
|
if des_node:
|
||||||
params['destination_node'] = des_node.uuid
|
params['destination_node'] = des_node.uuid
|
||||||
self.solution.add_action(action_type=self.INSTANCE_MIGRATION,
|
self.solution.add_action(action_type=self.INSTANCE_MIGRATION,
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
|
|
||||||
def host_migration(self, source_node, destination_node):
|
def host_migration(self, source_node, destination_node):
|
||||||
"""host migration
|
"""host migration
|
||||||
|
|
||||||
Migrate all instances from source_node to destination_node.
|
Migrate all instances from source_node to destination_node.
|
||||||
Active instances use "live-migrate",
|
Active instances use "live-migrate",
|
||||||
and other instances use "cold-migrate"
|
and other instances use "cold-migrate"
|
||||||
"""
|
"""
|
||||||
instances = self.compute_model.get_node_instances(source_node)
|
instances = self.compute_model.get_node_instances(source_node)
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
self.instance_migration(instance, source_node, destination_node)
|
self.instance_migration(instance, source_node, destination_node)
|
||||||
|
|
||||||
def safe_maintain(self, maintenance_node, backup_node=None):
|
def safe_maintain(self, maintenance_node, backup_node=None):
|
||||||
"""safe maintain one compute node
|
"""safe maintain one compute node
|
||||||
|
|
||||||
Migrate all instances of the maintenance_node intensively to the
|
Migrate all instances of the maintenance_node intensively to the
|
||||||
backup host. If users didn't give the backup host, it will select
|
backup host. If users didn't give the backup host, it will select
|
||||||
one unused node to backup the maintaining node.
|
one unused node to backup the maintaining node.
|
||||||
|
|
||||||
It calculate the resource both of the backup node and maintaining
|
It calculate the resource both of the backup node and maintaining
|
||||||
node to evaluate the migrations from maintaining node to backup node.
|
node to evaluate the migrations from maintaining node to backup node.
|
||||||
If all instances of the maintaining node can migrated to
|
If all instances of the maintaining node can migrated to
|
||||||
the backup node, it will set the maintaining node in
|
the backup node, it will set the maintaining node in
|
||||||
'watcher_maintaining' status., and add the migrations to solution.
|
'watcher_maintaining' status., and add the migrations to solution.
|
||||||
"""
|
"""
|
||||||
# If user gives a backup node with required capacity, then migrate
|
# If user gives a backup node with required capacity, then migrate
|
||||||
# all instances from the maintaining node to the backup node.
|
# all instances from the maintaining node to the backup node.
|
||||||
if backup_node:
|
if backup_node:
|
||||||
if self.host_fits(maintenance_node, backup_node):
|
if self.host_fits(maintenance_node, backup_node):
|
||||||
self.enable_compute_node_if_disabled(backup_node)
|
self.enable_compute_node_if_disabled(backup_node)
|
||||||
self.add_action_maintain_compute_node(maintenance_node)
|
self.add_action_maintain_compute_node(maintenance_node)
|
||||||
self.host_migration(maintenance_node, backup_node)
|
self.host_migration(maintenance_node, backup_node)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If uses didn't give the backup host, select one unused node
|
# If uses didn't give the backup host, select one unused node
|
||||||
# with required capacity, then migrate all instances
|
# with required capacity, then migrate all instances
|
||||||
# from maintaining node to it.
|
# from maintaining node to it.
|
||||||
nodes = sorted(
|
nodes = sorted(
|
||||||
self.get_disabled_compute_nodes().values(),
|
self.get_disabled_compute_nodes().values(),
|
||||||
key=lambda x: self.get_node_capacity(x)['cpu'])
|
key=lambda x: self.get_node_capacity(x)['cpu'])
|
||||||
if maintenance_node in nodes:
|
if maintenance_node in nodes:
|
||||||
nodes.remove(maintenance_node)
|
nodes.remove(maintenance_node)
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if self.host_fits(maintenance_node, node):
|
if self.host_fits(maintenance_node, node):
|
||||||
self.enable_compute_node_if_disabled(node)
|
self.enable_compute_node_if_disabled(node)
|
||||||
self.add_action_maintain_compute_node(maintenance_node)
|
self.add_action_maintain_compute_node(maintenance_node)
|
||||||
self.host_migration(maintenance_node, node)
|
self.host_migration(maintenance_node, node)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def try_maintain(self, maintenance_node):
|
def try_maintain(self, maintenance_node):
|
||||||
"""try to maintain one compute node
|
"""try to maintain one compute node
|
||||||
|
|
||||||
It firstly set the maintenance_node in 'watcher_maintaining' status.
|
It firstly set the maintenance_node in 'watcher_maintaining' status.
|
||||||
Then try to migrate all instances of the maintenance node, rely
|
Then try to migrate all instances of the maintenance node, rely
|
||||||
on nova-scheduler.
|
on nova-scheduler.
|
||||||
"""
|
"""
|
||||||
self.add_action_maintain_compute_node(maintenance_node)
|
self.add_action_maintain_compute_node(maintenance_node)
|
||||||
instances = self.compute_model.get_node_instances(maintenance_node)
|
instances = self.compute_model.get_node_instances(maintenance_node)
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
self.instance_migration(instance, maintenance_node)
|
self.instance_migration(instance, maintenance_node)
|
||||||
|
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
LOG.debug(self.compute_model.to_string())
|
LOG.debug(self.compute_model.to_string())
|
||||||
|
|
||||||
if not self.compute_model:
|
if not self.compute_model:
|
||||||
raise wexc.ClusterStateNotDefined()
|
raise wexc.ClusterStateNotDefined()
|
||||||
|
|
||||||
if self.compute_model.stale:
|
if self.compute_model.stale:
|
||||||
raise wexc.ClusterStateStale()
|
raise wexc.ClusterStateStale()
|
||||||
|
|
||||||
def do_execute(self):
|
def do_execute(self):
|
||||||
LOG.info(_('Executing Host Maintenance Migration Strategy'))
|
LOG.info(_('Executing Host Maintenance Migration Strategy'))
|
||||||
|
|
||||||
maintenance_node = self.input_parameters.get('maintenance_node')
|
maintenance_node = self.input_parameters.get('maintenance_node')
|
||||||
backup_node = self.input_parameters.get('backup_node')
|
backup_node = self.input_parameters.get('backup_node')
|
||||||
|
|
||||||
# if no VMs in the maintenance_node, just maintain the compute node
|
# if no VMs in the maintenance_node, just maintain the compute node
|
||||||
src_node = self.compute_model.get_node_by_uuid(maintenance_node)
|
src_node = self.compute_model.get_node_by_uuid(maintenance_node)
|
||||||
if len(self.compute_model.get_node_instances(src_node)) == 0:
|
if len(self.compute_model.get_node_instances(src_node)) == 0:
|
||||||
if (src_node.disabled_reason !=
|
if (src_node.disabled_reason !=
|
||||||
self.REASON_FOR_MAINTAINING):
|
self.REASON_FOR_MAINTAINING):
|
||||||
self.add_action_maintain_compute_node(src_node)
|
self.add_action_maintain_compute_node(src_node)
|
||||||
return
|
return
|
||||||
|
|
||||||
if backup_node:
|
if backup_node:
|
||||||
des_node = self.compute_model.get_node_by_uuid(backup_node)
|
des_node = self.compute_model.get_node_by_uuid(backup_node)
|
||||||
else:
|
else:
|
||||||
des_node = None
|
des_node = None
|
||||||
|
|
||||||
if not self.safe_maintain(src_node, des_node):
|
if not self.safe_maintain(src_node, des_node):
|
||||||
self.try_maintain(src_node)
|
self.try_maintain(src_node)
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
"""Post-execution phase
|
"""Post-execution phase
|
||||||
|
|
||||||
This can be used to compute the global efficacy
|
This can be used to compute the global efficacy
|
||||||
"""
|
"""
|
||||||
LOG.debug(self.solution.actions)
|
LOG.debug(self.solution.actions)
|
||||||
LOG.debug(self.compute_model.to_string())
|
LOG.debug(self.compute_model.to_string())
|
||||||
|
|||||||
@@ -1,206 +1,206 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2017 chinac.com
|
# Copyright (c) 2017 chinac.com
|
||||||
#
|
#
|
||||||
# Authors: suzhengwei<suzhengwei@chinac.com>
|
# Authors: suzhengwei<suzhengwei@chinac.com>
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.decision_engine.model import model_root
|
from watcher.decision_engine.model import model_root
|
||||||
from watcher.decision_engine.strategy import strategies
|
from watcher.decision_engine.strategy import strategies
|
||||||
from watcher.tests import base
|
from watcher.tests import base
|
||||||
from watcher.tests.decision_engine.model import faker_cluster_state
|
from watcher.tests.decision_engine.model import faker_cluster_state
|
||||||
|
|
||||||
|
|
||||||
class TestHostMaintenance(base.TestCase):
|
class TestHostMaintenance(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestHostMaintenance, self).setUp()
|
super(TestHostMaintenance, self).setUp()
|
||||||
|
|
||||||
# fake cluster
|
# fake cluster
|
||||||
self.fake_cluster = faker_cluster_state.FakerModelCollector()
|
self.fake_cluster = faker_cluster_state.FakerModelCollector()
|
||||||
|
|
||||||
p_model = mock.patch.object(
|
p_model = mock.patch.object(
|
||||||
strategies.HostMaintenance, "compute_model",
|
strategies.HostMaintenance, "compute_model",
|
||||||
new_callable=mock.PropertyMock)
|
new_callable=mock.PropertyMock)
|
||||||
self.m_model = p_model.start()
|
self.m_model = p_model.start()
|
||||||
self.addCleanup(p_model.stop)
|
self.addCleanup(p_model.stop)
|
||||||
|
|
||||||
p_audit_scope = mock.patch.object(
|
p_audit_scope = mock.patch.object(
|
||||||
strategies.HostMaintenance, "audit_scope",
|
strategies.HostMaintenance, "audit_scope",
|
||||||
new_callable=mock.PropertyMock
|
new_callable=mock.PropertyMock
|
||||||
)
|
)
|
||||||
self.m_audit_scope = p_audit_scope.start()
|
self.m_audit_scope = p_audit_scope.start()
|
||||||
self.addCleanup(p_audit_scope.stop)
|
self.addCleanup(p_audit_scope.stop)
|
||||||
|
|
||||||
self.m_audit_scope.return_value = mock.Mock()
|
self.m_audit_scope.return_value = mock.Mock()
|
||||||
|
|
||||||
self.m_model.return_value = model_root.ModelRoot()
|
self.m_model.return_value = model_root.ModelRoot()
|
||||||
self.strategy = strategies.HostMaintenance(config=mock.Mock())
|
self.strategy = strategies.HostMaintenance(config=mock.Mock())
|
||||||
|
|
||||||
def test_exception_stale_cdm(self):
|
def test_exception_stale_cdm(self):
|
||||||
self.fake_cluster.set_cluster_data_model_as_stale()
|
self.fake_cluster.set_cluster_data_model_as_stale()
|
||||||
self.m_model.return_value = self.fake_cluster.cluster_data_model
|
self.m_model.return_value = self.fake_cluster.cluster_data_model
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.ClusterStateNotDefined,
|
exception.ClusterStateNotDefined,
|
||||||
self.strategy.execute)
|
self.strategy.execute)
|
||||||
|
|
||||||
def test_get_node_capacity(self):
|
def test_get_node_capacity(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid("Node_0")
|
node_0 = model.get_node_by_uuid("Node_0")
|
||||||
node_capacity = dict(cpu=40, ram=132, disk=250)
|
node_capacity = dict(cpu=40, ram=132, disk=250)
|
||||||
self.assertEqual(node_capacity,
|
self.assertEqual(node_capacity,
|
||||||
self.strategy.get_node_capacity(node_0))
|
self.strategy.get_node_capacity(node_0))
|
||||||
|
|
||||||
def test_get_node_used(self):
|
def test_get_node_used(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid("Node_0")
|
node_0 = model.get_node_by_uuid("Node_0")
|
||||||
node_used = dict(cpu=20, ram=4, disk=40)
|
node_used = dict(cpu=20, ram=4, disk=40)
|
||||||
self.assertEqual(node_used,
|
self.assertEqual(node_used,
|
||||||
self.strategy.get_node_used(node_0))
|
self.strategy.get_node_used(node_0))
|
||||||
|
|
||||||
def test_get_node_free(self):
|
def test_get_node_free(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid("Node_0")
|
node_0 = model.get_node_by_uuid("Node_0")
|
||||||
node_free = dict(cpu=20, ram=128, disk=210)
|
node_free = dict(cpu=20, ram=128, disk=210)
|
||||||
self.assertEqual(node_free,
|
self.assertEqual(node_free,
|
||||||
self.strategy.get_node_free(node_0))
|
self.strategy.get_node_free(node_0))
|
||||||
|
|
||||||
def test_host_fits(self):
|
def test_host_fits(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid("Node_0")
|
node_0 = model.get_node_by_uuid("Node_0")
|
||||||
node_1 = model.get_node_by_uuid("Node_1")
|
node_1 = model.get_node_by_uuid("Node_1")
|
||||||
self.assertTrue(self.strategy.host_fits(node_0, node_1))
|
self.assertTrue(self.strategy.host_fits(node_0, node_1))
|
||||||
|
|
||||||
def test_add_action_enable_compute_node(self):
|
def test_add_action_enable_compute_node(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
self.strategy.add_action_enable_compute_node(node_0)
|
self.strategy.add_action_enable_compute_node(node_0)
|
||||||
expected = [{'action_type': 'change_nova_service_state',
|
expected = [{'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {
|
'input_parameters': {
|
||||||
'state': 'enabled',
|
'state': 'enabled',
|
||||||
'resource_id': 'Node_0'}}]
|
'resource_id': 'Node_0'}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_add_action_maintain_compute_node(self):
|
def test_add_action_maintain_compute_node(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
self.strategy.add_action_maintain_compute_node(node_0)
|
self.strategy.add_action_maintain_compute_node(node_0)
|
||||||
expected = [{'action_type': 'change_nova_service_state',
|
expected = [{'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {
|
'input_parameters': {
|
||||||
'state': 'disabled',
|
'state': 'disabled',
|
||||||
'disabled_reason': 'watcher_maintaining',
|
'disabled_reason': 'watcher_maintaining',
|
||||||
'resource_id': 'Node_0'}}]
|
'resource_id': 'Node_0'}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_instance_migration(self):
|
def test_instance_migration(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
node_1 = model.get_node_by_uuid('Node_1')
|
node_1 = model.get_node_by_uuid('Node_1')
|
||||||
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
||||||
self.strategy.instance_migration(instance_0, node_0, node_1)
|
self.strategy.instance_migration(instance_0, node_0, node_1)
|
||||||
self.assertEqual(1, len(self.strategy.solution.actions))
|
self.assertEqual(1, len(self.strategy.solution.actions))
|
||||||
expected = [{'action_type': 'migrate',
|
expected = [{'action_type': 'migrate',
|
||||||
'input_parameters': {'destination_node': node_1.uuid,
|
'input_parameters': {'destination_node': node_1.uuid,
|
||||||
'source_node': node_0.uuid,
|
'source_node': node_0.uuid,
|
||||||
'migration_type': 'live',
|
'migration_type': 'live',
|
||||||
'resource_id': instance_0.uuid}}]
|
'resource_id': instance_0.uuid}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_instance_migration_without_dest_node(self):
|
def test_instance_migration_without_dest_node(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
||||||
self.strategy.instance_migration(instance_0, node_0)
|
self.strategy.instance_migration(instance_0, node_0)
|
||||||
self.assertEqual(1, len(self.strategy.solution.actions))
|
self.assertEqual(1, len(self.strategy.solution.actions))
|
||||||
expected = [{'action_type': 'migrate',
|
expected = [{'action_type': 'migrate',
|
||||||
'input_parameters': {'source_node': node_0.uuid,
|
'input_parameters': {'source_node': node_0.uuid,
|
||||||
'migration_type': 'live',
|
'migration_type': 'live',
|
||||||
'resource_id': instance_0.uuid}}]
|
'resource_id': instance_0.uuid}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_host_migration(self):
|
def test_host_migration(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
node_1 = model.get_node_by_uuid('Node_1')
|
node_1 = model.get_node_by_uuid('Node_1')
|
||||||
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
instance_0 = model.get_instance_by_uuid("INSTANCE_0")
|
||||||
instance_1 = model.get_instance_by_uuid("INSTANCE_1")
|
instance_1 = model.get_instance_by_uuid("INSTANCE_1")
|
||||||
self.strategy.host_migration(node_0, node_1)
|
self.strategy.host_migration(node_0, node_1)
|
||||||
self.assertEqual(2, len(self.strategy.solution.actions))
|
self.assertEqual(2, len(self.strategy.solution.actions))
|
||||||
expected = [{'action_type': 'migrate',
|
expected = [{'action_type': 'migrate',
|
||||||
'input_parameters': {'destination_node': node_1.uuid,
|
'input_parameters': {'destination_node': node_1.uuid,
|
||||||
'source_node': node_0.uuid,
|
'source_node': node_0.uuid,
|
||||||
'migration_type': 'live',
|
'migration_type': 'live',
|
||||||
'resource_id': instance_0.uuid}},
|
'resource_id': instance_0.uuid}},
|
||||||
{'action_type': 'migrate',
|
{'action_type': 'migrate',
|
||||||
'input_parameters': {'destination_node': node_1.uuid,
|
'input_parameters': {'destination_node': node_1.uuid,
|
||||||
'source_node': node_0.uuid,
|
'source_node': node_0.uuid,
|
||||||
'migration_type': 'live',
|
'migration_type': 'live',
|
||||||
'resource_id': instance_1.uuid}}]
|
'resource_id': instance_1.uuid}}]
|
||||||
self.assertIn(expected[0], self.strategy.solution.actions)
|
self.assertIn(expected[0], self.strategy.solution.actions)
|
||||||
self.assertIn(expected[1], self.strategy.solution.actions)
|
self.assertIn(expected[1], self.strategy.solution.actions)
|
||||||
|
|
||||||
def test_safe_maintain(self):
|
def test_safe_maintain(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_0 = model.get_node_by_uuid('Node_0')
|
node_0 = model.get_node_by_uuid('Node_0')
|
||||||
node_1 = model.get_node_by_uuid('Node_1')
|
node_1 = model.get_node_by_uuid('Node_1')
|
||||||
self.assertFalse(self.strategy.safe_maintain(node_0))
|
self.assertFalse(self.strategy.safe_maintain(node_0))
|
||||||
self.assertFalse(self.strategy.safe_maintain(node_1))
|
self.assertFalse(self.strategy.safe_maintain(node_1))
|
||||||
|
|
||||||
def test_try_maintain(self):
|
def test_try_maintain(self):
|
||||||
model = self.fake_cluster.generate_scenario_1()
|
model = self.fake_cluster.generate_scenario_1()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_1 = model.get_node_by_uuid('Node_1')
|
node_1 = model.get_node_by_uuid('Node_1')
|
||||||
self.strategy.try_maintain(node_1)
|
self.strategy.try_maintain(node_1)
|
||||||
self.assertEqual(2, len(self.strategy.solution.actions))
|
self.assertEqual(2, len(self.strategy.solution.actions))
|
||||||
|
|
||||||
def test_strategy(self):
|
def test_strategy(self):
|
||||||
model = self.fake_cluster. \
|
model = self.fake_cluster. \
|
||||||
generate_scenario_9_with_3_active_plus_1_disabled_nodes()
|
generate_scenario_9_with_3_active_plus_1_disabled_nodes()
|
||||||
self.m_model.return_value = model
|
self.m_model.return_value = model
|
||||||
node_2 = model.get_node_by_uuid('Node_2')
|
node_2 = model.get_node_by_uuid('Node_2')
|
||||||
node_3 = model.get_node_by_uuid('Node_3')
|
node_3 = model.get_node_by_uuid('Node_3')
|
||||||
instance_4 = model.get_instance_by_uuid("INSTANCE_4")
|
instance_4 = model.get_instance_by_uuid("INSTANCE_4")
|
||||||
if not self.strategy.safe_maintain(node_2, node_3):
|
if not self.strategy.safe_maintain(node_2, node_3):
|
||||||
self.strategy.try_maintain(node_2)
|
self.strategy.try_maintain(node_2)
|
||||||
expected = [{'action_type': 'change_nova_service_state',
|
expected = [{'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {
|
'input_parameters': {
|
||||||
'resource_id': 'Node_3',
|
'resource_id': 'Node_3',
|
||||||
'state': 'enabled'}},
|
'state': 'enabled'}},
|
||||||
{'action_type': 'change_nova_service_state',
|
{'action_type': 'change_nova_service_state',
|
||||||
'input_parameters': {
|
'input_parameters': {
|
||||||
'resource_id': 'Node_2',
|
'resource_id': 'Node_2',
|
||||||
'state': 'disabled',
|
'state': 'disabled',
|
||||||
'disabled_reason': 'watcher_maintaining'}},
|
'disabled_reason': 'watcher_maintaining'}},
|
||||||
{'action_type': 'migrate',
|
{'action_type': 'migrate',
|
||||||
'input_parameters': {
|
'input_parameters': {
|
||||||
'destination_node': node_3.uuid,
|
'destination_node': node_3.uuid,
|
||||||
'source_node': node_2.uuid,
|
'source_node': node_2.uuid,
|
||||||
'migration_type': 'live',
|
'migration_type': 'live',
|
||||||
'resource_id': instance_4.uuid}}]
|
'resource_id': instance_4.uuid}}]
|
||||||
self.assertEqual(expected, self.strategy.solution.actions)
|
self.assertEqual(expected, self.strategy.solution.actions)
|
||||||
|
|||||||
Reference in New Issue
Block a user