Add reserved_host to failed_host's aggregate
Reserved hosts can be shared between multiple host_aggregates. So before evacuating the instances from failed_host to reserved_host, the target resered_host should be added to the same aggregate in which the failed_host is. This patch adds the reserved_host to failed_host's aggregate. Adding reserved_host to aggregate is optional and can be configured by operators with the help of new configuration parameter 'add_reserved_host_to_aggregate' which is added under the 'host_failure' section. This config option defaults to 'False'. Change-Id: I7478e0f24ecd6fd6385dd67e7f0cad5ca3460526
This commit is contained in:
parent
3f254e32a9
commit
d45f754cbb
@ -206,3 +206,20 @@ class API(object):
|
|||||||
msg = (_LI('Call start server command for instance %(uuid)s'))
|
msg = (_LI('Call start server command for instance %(uuid)s'))
|
||||||
LOG.info(msg, {'uuid': uuid})
|
LOG.info(msg, {'uuid': uuid})
|
||||||
return nova.servers.start(uuid)
|
return nova.servers.start(uuid)
|
||||||
|
|
||||||
|
@translate_nova_exception
|
||||||
|
def get_aggregate_list(self, context):
|
||||||
|
"""Get all aggregate list."""
|
||||||
|
nova = novaclient(context)
|
||||||
|
LOG.info(_LI('Call aggregate-list command to get list of all '
|
||||||
|
'aggregates.'))
|
||||||
|
return nova.aggregates.list()
|
||||||
|
|
||||||
|
@translate_nova_exception
|
||||||
|
def add_host_to_aggregate(self, context, host, aggregate):
|
||||||
|
"""Add host to given aggregate."""
|
||||||
|
nova = novaclient(context)
|
||||||
|
msg = _LI("Call add_host command to add host '%(host_name)s' to "
|
||||||
|
"aggregate '%(aggregate_name)s'.")
|
||||||
|
LOG.info(msg, {'host_name': host, 'aggregate_name': aggregate})
|
||||||
|
return nova.aggregates.add_host(aggregate, host)
|
||||||
|
@ -38,6 +38,14 @@ from a failed source compute node. First preference will be given to those
|
|||||||
instances which contain 'HA_Enabled=True' metadata key, and then it will
|
instances which contain 'HA_Enabled=True' metadata key, and then it will
|
||||||
evacuate the remaining ones. When set to False, it will evacuate only those
|
evacuate the remaining ones. When set to False, it will evacuate only those
|
||||||
instances which contain 'HA_Enabled=True' metadata key."""),
|
instances which contain 'HA_Enabled=True' metadata key."""),
|
||||||
|
|
||||||
|
cfg.BoolOpt("add_reserved_host_to_aggregate",
|
||||||
|
default=False,
|
||||||
|
help="""
|
||||||
|
Operators can decide whether reserved_host should be added to aggregate group
|
||||||
|
of failed compute host. When set to True, reserved host will be added to the
|
||||||
|
aggregate group of failed compute host. When set to False, the reserved_host
|
||||||
|
will not be added to the aggregate group of failed compute host."""),
|
||||||
]
|
]
|
||||||
|
|
||||||
instance_failure_options = [
|
instance_failure_options = [
|
||||||
|
@ -90,14 +90,30 @@ class EvacuateInstancesTask(base.MasakariTask):
|
|||||||
default_provides = set(["instance_list"])
|
default_provides = set(["instance_list"])
|
||||||
|
|
||||||
def __init__(self, novaclient):
|
def __init__(self, novaclient):
|
||||||
requires = ["instance_list"]
|
requires = ["host_name", "instance_list"]
|
||||||
super(EvacuateInstancesTask, self).__init__(addons=[ACTION],
|
super(EvacuateInstancesTask, self).__init__(addons=[ACTION],
|
||||||
requires=requires)
|
requires=requires)
|
||||||
self.novaclient = novaclient
|
self.novaclient = novaclient
|
||||||
|
|
||||||
def execute(self, context, instance_list, reserved_host=None):
|
def execute(self, context, host_name, instance_list, reserved_host=None):
|
||||||
def _do_evacuate(context, instance_list, reserved_host=None):
|
def _do_evacuate(context, host_name, instance_list,
|
||||||
|
reserved_host=None):
|
||||||
if reserved_host:
|
if reserved_host:
|
||||||
|
if CONF.host_failure.add_reserved_host_to_aggregate:
|
||||||
|
# Assign reserved_host to an aggregate to which the failed
|
||||||
|
# compute host belongs to.
|
||||||
|
aggregates = self.novaclient.get_aggregate_list(context)
|
||||||
|
for aggregate in aggregates:
|
||||||
|
if host_name in aggregate.hosts:
|
||||||
|
self.novaclient.add_host_to_aggregate(
|
||||||
|
context, reserved_host.name, aggregate.name)
|
||||||
|
# A failed compute host can be associated with
|
||||||
|
# multiple aggregates but operators will not
|
||||||
|
# associate it with multiple aggregates in real
|
||||||
|
# deployment so adding reserved_host to the very
|
||||||
|
# first aggregate from the list.
|
||||||
|
break
|
||||||
|
|
||||||
self.novaclient.enable_disable_service(
|
self.novaclient.enable_disable_service(
|
||||||
context, reserved_host.name, enable=True)
|
context, reserved_host.name, enable=True)
|
||||||
|
|
||||||
@ -131,17 +147,18 @@ class EvacuateInstancesTask(base.MasakariTask):
|
|||||||
lock_name = reserved_host.name if reserved_host else None
|
lock_name = reserved_host.name if reserved_host else None
|
||||||
|
|
||||||
@utils.synchronized(lock_name)
|
@utils.synchronized(lock_name)
|
||||||
def do_evacuate_with_reserved_host(context, instance_list,
|
def do_evacuate_with_reserved_host(context, host_name, instance_list,
|
||||||
reserved_host):
|
reserved_host):
|
||||||
_do_evacuate(context, instance_list, reserved_host=reserved_host)
|
_do_evacuate(context, host_name, instance_list,
|
||||||
|
reserved_host=reserved_host)
|
||||||
|
|
||||||
if lock_name:
|
if lock_name:
|
||||||
do_evacuate_with_reserved_host(context, instance_list,
|
do_evacuate_with_reserved_host(context, host_name, instance_list,
|
||||||
reserved_host)
|
reserved_host)
|
||||||
else:
|
else:
|
||||||
# No need to acquire lock on reserved_host when recovery_method is
|
# No need to acquire lock on reserved_host when recovery_method is
|
||||||
# 'auto' as the selection of compute host will be decided by nova.
|
# 'auto' as the selection of compute host will be decided by nova.
|
||||||
_do_evacuate(context, instance_list)
|
_do_evacuate(context, host_name, instance_list)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"instance_list": instance_list,
|
"instance_list": instance_list,
|
||||||
|
@ -238,3 +238,23 @@ class NovaApiTestCase(test.TestCase):
|
|||||||
|
|
||||||
mock_novaclient.assert_called_once_with(self.ctx)
|
mock_novaclient.assert_called_once_with(self.ctx)
|
||||||
mock_servers.start.assert_called_once_with(uuidsentinel.fake_server)
|
mock_servers.start.assert_called_once_with(uuidsentinel.fake_server)
|
||||||
|
|
||||||
|
@mock.patch('masakari.compute.nova.novaclient')
|
||||||
|
def test_get_aggregate_list(self, mock_novaclient):
|
||||||
|
mock_aggregates = mock.MagicMock()
|
||||||
|
mock_novaclient.return_value = mock.MagicMock(
|
||||||
|
aggregates=mock_aggregates)
|
||||||
|
self.api.get_aggregate_list(self.ctx)
|
||||||
|
|
||||||
|
mock_novaclient.assert_called_once_with(self.ctx)
|
||||||
|
self.assertTrue(mock_aggregates.list.called)
|
||||||
|
|
||||||
|
@mock.patch('masakari.compute.nova.novaclient')
|
||||||
|
def test_add_host_to_aggregate(self, mock_novaclient):
|
||||||
|
mock_aggregates = mock.MagicMock()
|
||||||
|
mock_novaclient.return_value = mock.MagicMock(
|
||||||
|
aggregates=mock_aggregates)
|
||||||
|
self.api.add_host_to_aggregate(self.ctx, 'fake_host', 'fake_aggregate')
|
||||||
|
mock_novaclient.assert_called_once_with(self.ctx)
|
||||||
|
mock_aggregates.add_host.assert_called_once_with(
|
||||||
|
'fake_aggregate', 'fake_host')
|
||||||
|
@ -89,7 +89,7 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
self.novaclient,
|
self.novaclient,
|
||||||
"enable_disable_service") as mock_enable_disable_service:
|
"enable_disable_service") as mock_enable_disable_service:
|
||||||
instance_list = task.execute(self.ctxt,
|
instance_list = task.execute(self.ctxt, self.instance_host,
|
||||||
instance_list['instance_list'],
|
instance_list['instance_list'],
|
||||||
reserved_host=reserved_host)
|
reserved_host=reserved_host)
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
self.ctxt, reserved_host.name, enable=True)
|
self.ctxt, reserved_host.name, enable=True)
|
||||||
else:
|
else:
|
||||||
instance_list = task.execute(
|
instance_list = task.execute(
|
||||||
self.ctxt, instance_list['instance_list'])
|
self.ctxt, self.instance_host, instance_list['instance_list'])
|
||||||
|
|
||||||
return instance_list
|
return instance_list
|
||||||
|
|
||||||
@ -137,6 +137,8 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
_mock_novaclient.return_value = self.fake_client
|
_mock_novaclient.return_value = self.fake_client
|
||||||
self.override_config("evacuate_all_instances",
|
self.override_config("evacuate_all_instances",
|
||||||
True, "host_failure")
|
True, "host_failure")
|
||||||
|
self.override_config("add_reserved_host_to_aggregate",
|
||||||
|
True, "host_failure")
|
||||||
|
|
||||||
# create test data
|
# create test data
|
||||||
self.fake_client.servers.create(id="1", host=self.instance_host,
|
self.fake_client.servers.create(id="1", host=self.instance_host,
|
||||||
@ -144,6 +146,8 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
self.fake_client.servers.create(id="2", host=self.instance_host)
|
self.fake_client.servers.create(id="2", host=self.instance_host)
|
||||||
reserved_host = fakes.create_fake_host(name="fake-reserved-host",
|
reserved_host = fakes.create_fake_host(name="fake-reserved-host",
|
||||||
reserved=True)
|
reserved=True)
|
||||||
|
self.fake_client.aggregates.create(id="1", name='fake_agg',
|
||||||
|
hosts=[self.instance_host])
|
||||||
|
|
||||||
# execute DisableComputeServiceTask
|
# execute DisableComputeServiceTask
|
||||||
self._test_disable_compute_service()
|
self._test_disable_compute_service()
|
||||||
@ -156,6 +160,8 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
instance_list = self._evacuate_instances(
|
instance_list = self._evacuate_instances(
|
||||||
instance_list, reserved_host=reserved_host)
|
instance_list, reserved_host=reserved_host)
|
||||||
self.assertEqual(1, mock_save.call_count)
|
self.assertEqual(1, mock_save.call_count)
|
||||||
|
self.assertIn(reserved_host.name,
|
||||||
|
self.fake_client.aggregates.get('fake_agg').hosts)
|
||||||
|
|
||||||
# execute ConfirmEvacuationTask
|
# execute ConfirmEvacuationTask
|
||||||
self._test_confirm_evacuate_task(instance_list)
|
self._test_confirm_evacuate_task(instance_list)
|
||||||
@ -182,7 +188,7 @@ class HostFailureTestCase(test.TestCase):
|
|||||||
# method is called.
|
# method is called.
|
||||||
with mock.patch.object(fakes.FakeNovaClient.ServerManager,
|
with mock.patch.object(fakes.FakeNovaClient.ServerManager,
|
||||||
"evacuate") as mock_evacuate:
|
"evacuate") as mock_evacuate:
|
||||||
task.execute(self.ctxt,
|
task.execute(self.ctxt, self.instance_host,
|
||||||
instance_list['instance_list'])
|
instance_list['instance_list'])
|
||||||
self.assertEqual(2, mock_evacuate.call_count)
|
self.assertEqual(2, mock_evacuate.call_count)
|
||||||
|
|
||||||
|
@ -80,6 +80,36 @@ class FakeNovaClient(object):
|
|||||||
server = self.get(id)
|
server = self.get(id)
|
||||||
setattr(server, 'OS-EXT-STS:vm_state', 'active')
|
setattr(server, 'OS-EXT-STS:vm_state', 'active')
|
||||||
|
|
||||||
|
class Aggregate(object):
|
||||||
|
def __init__(self, id=None, uuid=None, name=None, hosts=None):
|
||||||
|
self.id = id
|
||||||
|
self.uuid = uuid or uuidutils.generate_uuid()
|
||||||
|
self.name = name
|
||||||
|
self.hosts = hosts
|
||||||
|
|
||||||
|
class AggregatesManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.aggregates = []
|
||||||
|
|
||||||
|
def create(self, id, uuid=None, name=None, hosts=None):
|
||||||
|
aggregate = FakeNovaClient.Aggregate(id=id, uuid=uuid, name=name,
|
||||||
|
hosts=hosts)
|
||||||
|
self.aggregates.append(aggregate)
|
||||||
|
return aggregate
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
return self.aggregates
|
||||||
|
|
||||||
|
def add_host(self, aggregate_name, host_name):
|
||||||
|
aggregate = self.get(aggregate_name)
|
||||||
|
if host_name not in aggregate.hosts:
|
||||||
|
aggregate.hosts.append(host_name)
|
||||||
|
|
||||||
|
def get(self, name):
|
||||||
|
for aggregate in self.aggregates:
|
||||||
|
if aggregate.name == name:
|
||||||
|
return aggregate
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
def __init__(self, id=None, host=None, binary=None, status='enabled'):
|
def __init__(self, id=None, host=None, binary=None, status='enabled'):
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -111,6 +141,7 @@ class FakeNovaClient(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.servers = FakeNovaClient.ServerManager()
|
self.servers = FakeNovaClient.ServerManager()
|
||||||
self.services = FakeNovaClient.Services()
|
self.services = FakeNovaClient.Services()
|
||||||
|
self.aggregates = FakeNovaClient.AggregatesManager()
|
||||||
|
|
||||||
|
|
||||||
def create_fake_notification(type="VM", id=1, payload=None,
|
def create_fake_notification(type="VM", id=1, payload=None,
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Operators can now decide based on the new config option
|
||||||
|
'add_reserved_host_to_aggregate' whether to add or not a reserved_host
|
||||||
|
to all host aggregates which failed compute host belongs to.
|
||||||
|
|
||||||
|
To use this feature, following config option need to be set under
|
||||||
|
``host_failure`` section in 'masakari.conf' file::
|
||||||
|
|
||||||
|
[host_failure]
|
||||||
|
add_reserved_host_to_aggregate = True
|
Loading…
Reference in New Issue
Block a user