diff -Nru nova-13.1.4/debian/changelog nova-13.1.4/debian/changelog --- nova-13.1.4/debian/changelog 2017-08-02 21:04:06.000000000 +0900 +++ nova-13.1.4/debian/changelog 2017-08-18 13:42:03.000000000 +0900 @@ -1,3 +1,15 @@ +nova (2:13.1.4-0ubuntu2~cloud1) trusty-mitaka; urgency=medium + + * Fix evacuation error when nova-compute is down just + after VM is started. + - d/p/make-sure-to-rebuild-claim-on-recreate.patch + (LP: #1658070, #1686041) + backported from newton 0f2d874, upstream a2b0824 + - d/p/Send-events-to-all-relevant-hosts-if-migrating.patch (LP: #1535918) + backported from a5b920 + + -- Seyeong Kim Tue, 08 Aug 2017 14:03:39 +0900 + nova (2:13.1.4-0ubuntu2~cloud0) trusty-mitaka; urgency=medium * New update for the Ubuntu Cloud Archive. diff -Nru nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch --- nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch 1970-01-01 09:00:00.000000000 +0900 +++ nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch 2017-08-18 13:42:30.000000000 +0900 @@ -0,0 +1,219 @@ +From dae7e52e609055d94dcd69a76660bed4271f2f95 Mon Sep 17 00:00:00 2001 +From: Artom Lifshitz +Date: Wed, 5 Oct 2016 14:37:03 -0400 +Subject: [PATCH 1/1] Send events to all relevant hosts if migrating + +Previously, external events were sent to the instance object's host +field. This patch fixes the external event dispatching to check for +migration. If an instance is being migrated, the source and +destination compute are added to the set of hosts to which the event +is sent. + +Change-Id: If00736ab36df4a5a3be4f02b0a550e4bcae77b1b +Closes-bug: 1535918 +Closes-bug: 1624052 +(back ported from a5b920a197c70d2ae08a1e1335d979857f923b4f) +--- + .../openstack/compute/server_external_events.py | 5 +- + nova/compute/api.py | 37 +++++++++---- + nova/compute/rpcapi.py | 4 +- + .../compute/test_server_external_events.py | 2 +- + nova/tests/unit/compute/test_compute_api.py | 64 ++++++++++++++++++++-- + 5 files changed, 91 insertions(+), 21 deletions(-) + +Origin: https://github.com/openstack/nova/commit/a5b920a197c70d2ae08a1e1335d979857f923b4f +Bug-Ubuntu: https://bugs.launchpad.net/nova-powervm/+bug/1535918 +Index: nova-13.1.4/nova/api/openstack/compute/server_external_events.py +=================================================================== +--- nova-13.1.4.orig/nova/api/openstack/compute/server_external_events.py ++++ nova-13.1.4/nova/api/openstack/compute/server_external_events.py +@@ -65,8 +65,11 @@ class ServerExternalEventsController(wsg + instance = instances.get(event.instance_uuid) + if not instance: + try: ++ # Load migration_context here in a single DB operation ++ # because we need it later on + instance = objects.Instance.get_by_uuid( +- context, event.instance_uuid) ++ context, event.instance_uuid, ++ expected_attrs='migration_context') + instances[event.instance_uuid] = instance + except exception.InstanceNotFound: + LOG.debug('Dropping event %(name)s:%(tag)s for unknown ' +Index: nova-13.1.4/nova/compute/api.py +=================================================================== +--- nova-13.1.4.orig/nova/compute/api.py ++++ nova-13.1.4/nova/compute/api.py +@@ -20,6 +20,7 @@ + networking and storage of VMs, and compute hosts on which they run).""" + + import base64 ++import collections + import copy + import functools + import re +@@ -3574,27 +3575,39 @@ class API(base.Base): + # but doesn't know where they go. We need to collate lists + # by the host the affected instance is on and dispatch them + # according to host +- instances_by_host = {} +- events_by_host = {} +- hosts_by_instance = {} ++ instances_by_host = collections.defaultdict(list) ++ events_by_host = collections.defaultdict(list) ++ hosts_by_instance = collections.defaultdict(list) + for instance in instances: +- instances_on_host = instances_by_host.get(instance.host, []) +- instances_on_host.append(instance) +- instances_by_host[instance.host] = instances_on_host +- hosts_by_instance[instance.uuid] = instance.host ++ for host in self._get_relevant_hosts(context, instance): ++ instances_by_host[host].append(instance) ++ hosts_by_instance[instance.uuid].append(host) + + for event in events: +- host = hosts_by_instance[event.instance_uuid] +- events_on_host = events_by_host.get(host, []) +- events_on_host.append(event) +- events_by_host[host] = events_on_host ++ for host in hosts_by_instance[event.instance_uuid]: ++ events_by_host[host].append(event) + + for host in instances_by_host: + # TODO(salv-orlando): Handle exceptions raised by the rpc api layer + # in order to ensure that a failure in processing events on a host + # will not prevent processing events on other hosts + self.compute_rpcapi.external_instance_event( +- context, instances_by_host[host], events_by_host[host]) ++ context, instances_by_host[host], events_by_host[host], ++ host=host) ++ ++ def _get_relevant_hosts(self, context, instance): ++ hosts = set() ++ hosts.add(instance.host) ++ if instance.migration_context is not None: ++ migration_id = instance.migration_context.migration_id ++ migration = objects.Migration.get_by_id(context, migration_id) ++ hosts.add(migration.dest_compute) ++ hosts.add(migration.source_compute) ++ LOG.debug('Instance %(instance)s is migrating, ' ++ 'copying events to all relevant hosts: ' ++ '%(hosts)s', {'instance': instance.uuid, ++ 'hosts': hosts}) ++ return hosts + + def get_instance_host_status(self, instance): + if instance.host: +Index: nova-13.1.4/nova/compute/rpcapi.py +=================================================================== +--- nova-13.1.4.orig/nova/compute/rpcapi.py ++++ nova-13.1.4/nova/compute/rpcapi.py +@@ -1010,9 +1010,9 @@ class ComputeAPI(object): + volume_id=volume_id, snapshot_id=snapshot_id, + delete_info=delete_info) + +- def external_instance_event(self, ctxt, instances, events): ++ def external_instance_event(self, ctxt, instances, events, host=None): + cctxt = self.client.prepare( +- server=_compute_host(None, instances[0]), ++ server=_compute_host(host, instances[0]), + version='4.0') + cctxt.cast(ctxt, 'external_instance_event', instances=instances, + events=events) +Index: nova-13.1.4/nova/tests/unit/api/openstack/compute/test_server_external_events.py +=================================================================== +--- nova-13.1.4.orig/nova/tests/unit/api/openstack/compute/test_server_external_events.py ++++ nova-13.1.4/nova/tests/unit/api/openstack/compute/test_server_external_events.py +@@ -39,7 +39,7 @@ MISSING_UUID = '00000000-0000-0000-0000- + + + @classmethod +-def fake_get_by_uuid(cls, context, uuid): ++def fake_get_by_uuid(cls, context, uuid, **kwargs): + try: + return fake_instances[uuid] + except KeyError: +Index: nova-13.1.4/nova/tests/unit/compute/test_compute_api.py +=================================================================== +--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute_api.py ++++ nova-13.1.4/nova/tests/unit/compute/test_compute_api.py +@@ -2850,9 +2850,12 @@ class _ComputeAPIUnitTestMixIn(object): + + def test_external_instance_event(self): + instances = [ +- objects.Instance(uuid=uuids.instance_1, host='host1'), +- objects.Instance(uuid=uuids.instance_2, host='host1'), +- objects.Instance(uuid=uuids.instance_3, host='host2'), ++ objects.Instance(uuid=uuids.instance_1, host='host1', ++ migration_context=None), ++ objects.Instance(uuid=uuids.instance_2, host='host1', ++ migration_context=None), ++ objects.Instance(uuid=uuids.instance_3, host='host2', ++ migration_context=None), + ] + events = [ + objects.InstanceExternalEvent( +@@ -2866,10 +2869,61 @@ class _ComputeAPIUnitTestMixIn(object): + self.compute_api.external_instance_event(self.context, + instances, events) + method = self.compute_api.compute_rpcapi.external_instance_event +- method.assert_any_call(self.context, instances[0:2], events[0:2]) +- method.assert_any_call(self.context, instances[2:], events[2:]) ++ method.assert_any_call(self.context, instances[0:2], events[0:2], ++ host='host1') ++ method.assert_any_call(self.context, instances[2:], events[2:], ++ host='host2') + self.assertEqual(2, method.call_count) + ++ def test_external_instance_event_evacuating_instance(self): ++ # Since we're patching the db's migration_get(), use a dict here so ++ # that we can validate the id is making its way correctly to the db api ++ migrations = {} ++ migrations[42] = {'id': 42, 'source_compute': 'host1', ++ 'dest_compute': 'host2', 'source_node': None, ++ 'dest_node': None, 'dest_host': None, ++ 'old_instance_type_id': None, ++ 'new_instance_type_id': None, ++ 'instance_uuid': uuids.instance_2, 'status': None, ++ 'migration_type': 'evacuation', 'memory_total': None, ++ 'memory_processed': None, 'memory_remaining': None, ++ 'disk_total': None, 'disk_processed': None, ++ 'disk_remaining': None, 'deleted': False, ++ 'hidden': False, 'created_at': None, ++ 'updated_at': None, 'deleted_at': None} ++ ++ def migration_get(context, id): ++ return migrations[id] ++ ++ instances = [ ++ objects.Instance(uuid=uuids.instance_1, host='host1', ++ migration_context=None), ++ objects.Instance(uuid=uuids.instance_2, host='host1', ++ migration_context=objects.MigrationContext( ++ migration_id=42)), ++ objects.Instance(uuid=uuids.instance_3, host='host2', ++ migration_context=None) ++ ] ++ events = [ ++ objects.InstanceExternalEvent( ++ instance_uuid=uuids.instance_1), ++ objects.InstanceExternalEvent( ++ instance_uuid=uuids.instance_2), ++ objects.InstanceExternalEvent( ++ instance_uuid=uuids.instance_3), ++ ] ++ ++ with mock.patch('nova.db.sqlalchemy.api.migration_get', migration_get): ++ self.compute_api.compute_rpcapi = mock.MagicMock() ++ self.compute_api.external_instance_event(self.context, ++ instances, events) ++ method = self.compute_api.compute_rpcapi.external_instance_event ++ method.assert_any_call(self.context, instances[0:2], events[0:2], ++ host='host1') ++ method.assert_any_call(self.context, instances[1:], events[1:], ++ host='host2') ++ self.assertEqual(2, method.call_count) ++ + def test_volume_ops_invalid_task_state(self): + instance = self._create_instance_obj() + self.assertEqual(instance.vm_state, vm_states.ACTIVE) diff -Nru nova-13.1.4/debian/patches/lp1535918_xenial.debdiff nova-13.1.4/debian/patches/lp1535918_xenial.debdiff --- nova-13.1.4/debian/patches/lp1535918_xenial.debdiff 1970-01-01 09:00:00.000000000 +0900 +++ nova-13.1.4/debian/patches/lp1535918_xenial.debdiff 2017-08-08 14:02:34.000000000 +0900 @@ -0,0 +1,448 @@ +diff -Nru nova-13.1.4/debian/changelog nova-13.1.4/debian/changelog +--- nova-13.1.4/debian/changelog 2017-07-26 14:46:18.000000000 +0900 ++++ nova-13.1.4/debian/changelog 2017-08-04 16:40:22.000000000 +0900 +@@ -1,3 +1,12 @@ ++nova (2:13.1.4-0ubuntu2+sf145756v20170804.3) xenial; urgency=medium ++ ++ * d/p/make-sure-to-rebuild-claim-on-recreate.patch (LP: #1658070) ++ - backported from newton 0f2d874 ++ * d/p/Send-events-to-all-relevant-hosts-if-migrating.patch (LP: #1535918) ++ - backported from a5b920 ++ ++ -- Seyeong Kim Fri, 04 Aug 2017 04:46:40 +0900 ++ + nova (2:13.1.4-0ubuntu2) xenial; urgency=medium + + * d/p/libvirt-fix-incorrect-host-cpus-giving-to-emulator-t.patch: +diff -Nru nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch +--- nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch 1970-01-01 09:00:00.000000000 +0900 ++++ nova-13.1.4/debian/patches/Send-events-to-all-relevant-hosts-if-migrating.patch 2017-08-04 16:38:54.000000000 +0900 +@@ -0,0 +1,217 @@ ++From dae7e52e609055d94dcd69a76660bed4271f2f95 Mon Sep 17 00:00:00 2001 ++From: Artom Lifshitz ++Date: Wed, 5 Oct 2016 14:37:03 -0400 ++Subject: [PATCH 1/1] Send events to all relevant hosts if migrating ++ ++Previously, external events were sent to the instance object's host ++field. This patch fixes the external event dispatching to check for ++migration. If an instance is being migrated, the source and ++destination compute are added to the set of hosts to which the event ++is sent. ++ ++Change-Id: If00736ab36df4a5a3be4f02b0a550e4bcae77b1b ++Closes-bug: 1535918 ++Closes-bug: 1624052 ++(back ported from a5b920a197c70d2ae08a1e1335d979857f923b4f) ++--- ++ .../openstack/compute/server_external_events.py | 5 +- ++ nova/compute/api.py | 37 +++++++++---- ++ nova/compute/rpcapi.py | 4 +- ++ .../compute/test_server_external_events.py | 2 +- ++ nova/tests/unit/compute/test_compute_api.py | 64 ++++++++++++++++++++-- ++ 5 files changed, 91 insertions(+), 21 deletions(-) ++ ++Index: nova-13.1.4/nova/api/openstack/compute/server_external_events.py ++=================================================================== ++--- nova-13.1.4.orig/nova/api/openstack/compute/server_external_events.py +++++ nova-13.1.4/nova/api/openstack/compute/server_external_events.py ++@@ -65,8 +65,11 @@ class ServerExternalEventsController(wsg ++ instance = instances.get(event.instance_uuid) ++ if not instance: ++ try: +++ # Load migration_context here in a single DB operation +++ # because we need it later on ++ instance = objects.Instance.get_by_uuid( ++- context, event.instance_uuid) +++ context, event.instance_uuid, +++ expected_attrs='migration_context') ++ instances[event.instance_uuid] = instance ++ except exception.InstanceNotFound: ++ LOG.debug('Dropping event %(name)s:%(tag)s for unknown ' ++Index: nova-13.1.4/nova/compute/api.py ++=================================================================== ++--- nova-13.1.4.orig/nova/compute/api.py +++++ nova-13.1.4/nova/compute/api.py ++@@ -20,6 +20,7 @@ ++ networking and storage of VMs, and compute hosts on which they run).""" ++ ++ import base64 +++import collections ++ import copy ++ import functools ++ import re ++@@ -3574,27 +3575,39 @@ class API(base.Base): ++ # but doesn't know where they go. We need to collate lists ++ # by the host the affected instance is on and dispatch them ++ # according to host ++- instances_by_host = {} ++- events_by_host = {} ++- hosts_by_instance = {} +++ instances_by_host = collections.defaultdict(list) +++ events_by_host = collections.defaultdict(list) +++ hosts_by_instance = collections.defaultdict(list) ++ for instance in instances: ++- instances_on_host = instances_by_host.get(instance.host, []) ++- instances_on_host.append(instance) ++- instances_by_host[instance.host] = instances_on_host ++- hosts_by_instance[instance.uuid] = instance.host +++ for host in self._get_relevant_hosts(context, instance): +++ instances_by_host[host].append(instance) +++ hosts_by_instance[instance.uuid].append(host) ++ ++ for event in events: ++- host = hosts_by_instance[event.instance_uuid] ++- events_on_host = events_by_host.get(host, []) ++- events_on_host.append(event) ++- events_by_host[host] = events_on_host +++ for host in hosts_by_instance[event.instance_uuid]: +++ events_by_host[host].append(event) ++ ++ for host in instances_by_host: ++ # TODO(salv-orlando): Handle exceptions raised by the rpc api layer ++ # in order to ensure that a failure in processing events on a host ++ # will not prevent processing events on other hosts ++ self.compute_rpcapi.external_instance_event( ++- context, instances_by_host[host], events_by_host[host]) +++ context, instances_by_host[host], events_by_host[host], +++ host=host) +++ +++ def _get_relevant_hosts(self, context, instance): +++ hosts = set() +++ hosts.add(instance.host) +++ if instance.migration_context is not None: +++ migration_id = instance.migration_context.migration_id +++ migration = objects.Migration.get_by_id(context, migration_id) +++ hosts.add(migration.dest_compute) +++ hosts.add(migration.source_compute) +++ LOG.debug('Instance %(instance)s is migrating, ' +++ 'copying events to all relevant hosts: ' +++ '%(hosts)s', {'instance': instance.uuid, +++ 'hosts': hosts}) +++ return hosts ++ ++ def get_instance_host_status(self, instance): ++ if instance.host: ++Index: nova-13.1.4/nova/compute/rpcapi.py ++=================================================================== ++--- nova-13.1.4.orig/nova/compute/rpcapi.py +++++ nova-13.1.4/nova/compute/rpcapi.py ++@@ -1010,9 +1010,9 @@ class ComputeAPI(object): ++ volume_id=volume_id, snapshot_id=snapshot_id, ++ delete_info=delete_info) ++ ++- def external_instance_event(self, ctxt, instances, events): +++ def external_instance_event(self, ctxt, instances, events, host=None): ++ cctxt = self.client.prepare( ++- server=_compute_host(None, instances[0]), +++ server=_compute_host(host, instances[0]), ++ version='4.0') ++ cctxt.cast(ctxt, 'external_instance_event', instances=instances, ++ events=events) ++Index: nova-13.1.4/nova/tests/unit/api/openstack/compute/test_server_external_events.py ++=================================================================== ++--- nova-13.1.4.orig/nova/tests/unit/api/openstack/compute/test_server_external_events.py +++++ nova-13.1.4/nova/tests/unit/api/openstack/compute/test_server_external_events.py ++@@ -39,7 +39,7 @@ MISSING_UUID = '00000000-0000-0000-0000- ++ ++ ++ @classmethod ++-def fake_get_by_uuid(cls, context, uuid): +++def fake_get_by_uuid(cls, context, uuid, **kwargs): ++ try: ++ return fake_instances[uuid] ++ except KeyError: ++Index: nova-13.1.4/nova/tests/unit/compute/test_compute_api.py ++=================================================================== ++--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute_api.py +++++ nova-13.1.4/nova/tests/unit/compute/test_compute_api.py ++@@ -2850,9 +2850,12 @@ class _ComputeAPIUnitTestMixIn(object): ++ ++ def test_external_instance_event(self): ++ instances = [ ++- objects.Instance(uuid=uuids.instance_1, host='host1'), ++- objects.Instance(uuid=uuids.instance_2, host='host1'), ++- objects.Instance(uuid=uuids.instance_3, host='host2'), +++ objects.Instance(uuid=uuids.instance_1, host='host1', +++ migration_context=None), +++ objects.Instance(uuid=uuids.instance_2, host='host1', +++ migration_context=None), +++ objects.Instance(uuid=uuids.instance_3, host='host2', +++ migration_context=None), ++ ] ++ events = [ ++ objects.InstanceExternalEvent( ++@@ -2866,10 +2869,61 @@ class _ComputeAPIUnitTestMixIn(object): ++ self.compute_api.external_instance_event(self.context, ++ instances, events) ++ method = self.compute_api.compute_rpcapi.external_instance_event ++- method.assert_any_call(self.context, instances[0:2], events[0:2]) ++- method.assert_any_call(self.context, instances[2:], events[2:]) +++ method.assert_any_call(self.context, instances[0:2], events[0:2], +++ host='host1') +++ method.assert_any_call(self.context, instances[2:], events[2:], +++ host='host2') ++ self.assertEqual(2, method.call_count) ++ +++ def test_external_instance_event_evacuating_instance(self): +++ # Since we're patching the db's migration_get(), use a dict here so +++ # that we can validate the id is making its way correctly to the db api +++ migrations = {} +++ migrations[42] = {'id': 42, 'source_compute': 'host1', +++ 'dest_compute': 'host2', 'source_node': None, +++ 'dest_node': None, 'dest_host': None, +++ 'old_instance_type_id': None, +++ 'new_instance_type_id': None, +++ 'instance_uuid': uuids.instance_2, 'status': None, +++ 'migration_type': 'evacuation', 'memory_total': None, +++ 'memory_processed': None, 'memory_remaining': None, +++ 'disk_total': None, 'disk_processed': None, +++ 'disk_remaining': None, 'deleted': False, +++ 'hidden': False, 'created_at': None, +++ 'updated_at': None, 'deleted_at': None} +++ +++ def migration_get(context, id): +++ return migrations[id] +++ +++ instances = [ +++ objects.Instance(uuid=uuids.instance_1, host='host1', +++ migration_context=None), +++ objects.Instance(uuid=uuids.instance_2, host='host1', +++ migration_context=objects.MigrationContext( +++ migration_id=42)), +++ objects.Instance(uuid=uuids.instance_3, host='host2', +++ migration_context=None) +++ ] +++ events = [ +++ objects.InstanceExternalEvent( +++ instance_uuid=uuids.instance_1), +++ objects.InstanceExternalEvent( +++ instance_uuid=uuids.instance_2), +++ objects.InstanceExternalEvent( +++ instance_uuid=uuids.instance_3), +++ ] +++ +++ with mock.patch('nova.db.sqlalchemy.api.migration_get', migration_get): +++ self.compute_api.compute_rpcapi = mock.MagicMock() +++ self.compute_api.external_instance_event(self.context, +++ instances, events) +++ method = self.compute_api.compute_rpcapi.external_instance_event +++ method.assert_any_call(self.context, instances[0:2], events[0:2], +++ host='host1') +++ method.assert_any_call(self.context, instances[1:], events[1:], +++ host='host2') +++ self.assertEqual(2, method.call_count) +++ ++ def test_volume_ops_invalid_task_state(self): ++ instance = self._create_instance_obj() ++ self.assertEqual(instance.vm_state, vm_states.ACTIVE) +diff -Nru nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch +--- nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch 1970-01-01 09:00:00.000000000 +0900 ++++ nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch 2017-08-04 13:01:04.000000000 +0900 +@@ -0,0 +1,198 @@ ++From 3c14ea0510cd753bda74379b4a5b83872c3e934a Mon Sep 17 00:00:00 2001 ++From: Guang Yee ++Date: Thu, 18 May 2017 16:38:16 -0700 ++Subject: [PATCH 1/1] make sure to rebuild claim on recreate ++ ++On recreate where the instance is being evacuated to a different node, ++we should be rebuilding the claim so the migration context is available ++when rebuilding the instance. ++ ++Conflicts: ++ nova/compute/manager.py ++ nova/tests/unit/compute/test_compute_mgr.py ++ ++NOTE(mriedem): There are a few issues here: ++ ++1. I5aaa869f2e6155964827e659d18e2bcaad9d866b changed the LOG.info ++ method to not pass a context in Ocata. ++2. I57233259065d887b38a79850a05177fcbbdfb8c3 changed some tests in ++ test_compute_manager in Ocata, but is irrelevant here. ++3. The bigger change isn't a merge conflict but in Ocata the compute ++ manager code was all refactored so that the _get_resource_tracker ++ method no longer needed a nodename passed to it. In Newton, however, ++ if we're force evacuating (scenario 3) then we don't have a scheduled_node ++ passed to the rebuild_instance method and in this case we need to ++ lookup the nodename for the host we're currently on. To resolve this, ++ some existing code that handles this case is moved up where it is ++ needed to get the resource tracker so we can get the rebuild_claim method. ++ We let any ComputeHostNotFound exception raise up in this case rather than ++ log it because without the compute node we can't make the rebuild claim and ++ we need to fail. Tests are adjusted accordingly for this. ++ ++Change-Id: I53bdcf8edf640e97b4632ef7a093f14a6e3845e4 ++Closes-Bug: 1658070 ++(cherry picked from commit a2b0824aca5cb4a2ae579f625327c51ed0414d35) ++(cherry picked from commit ea90c60b07534a46541c55432389f2d50b5b7d0a) ++(back ported from commit 0f2d87416eff1e96c0fbf0f4b08bf6b6b22246d5) ++--- ++ nova/compute/manager.py | 33 ++++++++++++++++-------- ++ nova/tests/unit/compute/test_compute.py | 19 +++++--------- ++ nova/tests/unit/compute/test_compute_mgr.py | 40 +++++++++++++++++++++++++++-- ++ 3 files changed, 68 insertions(+), 24 deletions(-) ++ ++Index: nova-13.1.4/nova/compute/manager.py ++=================================================================== ++--- nova-13.1.4.orig/nova/compute/manager.py +++++ nova-13.1.4/nova/compute/manager.py ++@@ -2777,7 +2777,28 @@ class ComputeManager(manager.Manager): ++ ++ LOG.info(_LI("Rebuilding instance"), context=context, ++ instance=instance) ++- if scheduled_node is not None: +++ +++ # NOTE(gyee): there are three possible scenarios. +++ # +++ # 1. instance is being rebuilt on the same node. In this case, +++ # recreate should be False and scheduled_node should be None. +++ # 2. instance is being rebuilt on a node chosen by the +++ # scheduler (i.e. evacuate). In this case, scheduled_node should +++ # be specified and recreate should be True. +++ # 3. instance is being rebuilt on a node chosen by the user. (i.e. +++ # force evacuate). In this case, scheduled_node is not specified +++ # and recreate is set to True. +++ # +++ # For scenarios #2 and #3, we must do rebuild claim as server is +++ # being evacuated to a different node. +++ if recreate or scheduled_node is not None: +++ if scheduled_node is None: +++ # NOTE(mriedem): On a recreate (evacuate), we need to update +++ # the instance's host and node properties to reflect it's +++ # destination node for the recreate, and to make a rebuild +++ # claim. +++ compute_node = self._get_compute_info(context, self.host) +++ scheduled_node = compute_node.hypervisor_hostname ++ rt = self._get_resource_tracker(scheduled_node) ++ rebuild_claim = rt.rebuild_claim ++ else: ++@@ -2787,16 +2808,8 @@ class ComputeManager(manager.Manager): ++ if image_ref: ++ image_meta = self.image_api.get(context, image_ref) ++ ++- # NOTE(mriedem): On a recreate (evacuate), we need to update ++- # the instance's host and node properties to reflect it's ++- # destination node for the recreate. ++ if not scheduled_node: ++- try: ++- compute_node = self._get_compute_info(context, self.host) ++- scheduled_node = compute_node.hypervisor_hostname ++- except exception.ComputeHostNotFound: ++- LOG.exception(_LE('Failed to get compute_info for %s'), ++- self.host) +++ scheduled_node = instance.node ++ ++ with self._error_out_instance_on_exception(context, instance): ++ try: ++Index: nova-13.1.4/nova/tests/unit/compute/test_compute.py ++=================================================================== ++--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute.py +++++ nova-13.1.4/nova/tests/unit/compute/test_compute.py ++@@ -11580,17 +11580,6 @@ class EvacuateHostTestCase(BaseTestCase) ++ def fake_get_compute_info(context, host): ++ raise exception.ComputeHostNotFound(host=host) ++ ++- self.stubs.Set(self.compute, '_get_compute_info', ++- fake_get_compute_info) ++- self.mox.ReplayAll() ++- ++- self._rebuild() ++- ++- # Should be on destination host ++- instance = db.instance_get(self.context, self.inst.id) ++- self.assertEqual(instance['host'], self.compute.host) ++- self.assertIsNone(instance['node']) ++- ++ def test_rebuild_on_host_node_passed(self): ++ patch_get_info = mock.patch.object(self.compute, '_get_compute_info') ++ patch_on_disk = mock.patch.object( ++@@ -11776,15 +11765,21 @@ class EvacuateHostTestCase(BaseTestCase) ++ ++ self._rebuild(on_shared_storage=None) ++ ++- def test_rebuild_migration_passed_in(self): +++ @mock.patch('nova.compute.manager.ComputeManager._get_compute_info') +++ @mock.patch('nova.compute.manager.ComputeManager._get_resource_tracker') +++ def test_rebuild_migration_passed_in(self, get_rt, get_compute): ++ migration = mock.Mock(spec=objects.Migration) ++ ++ patch_spawn = mock.patch.object(self.compute.driver, 'spawn') ++ patch_on_disk = mock.patch.object( ++ self.compute.driver, 'instance_on_disk', return_value=True) +++ get_compute.return_value = objects.ComputeNode( +++ hypervisor_hostname=NODENAME) ++ with patch_spawn, patch_on_disk: ++ self._rebuild(migration=migration) ++ +++ get_rt.assert_called_once_with(NODENAME) +++ self.assertTrue(get_rt.return_value.rebuild_claim.called) ++ self.assertEqual('done', migration.status) ++ migration.save.assert_called_once_with() ++ ++Index: nova-13.1.4/nova/tests/unit/compute/test_compute_mgr.py ++=================================================================== ++--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute_mgr.py +++++ nova-13.1.4/nova/tests/unit/compute/test_compute_mgr.py ++@@ -2761,16 +2761,52 @@ class ComputeManagerUnitTestCase(test.No ++ 'rebuild.error', fault=ex) ++ ++ def test_rebuild_deleting(self): ++- instance = objects.Instance(uuid='fake-uuid') +++ instance = fake_instance.fake_instance_obj(self.context) ++ ex = exception.UnexpectedDeletingTaskStateError( ++ instance_uuid=instance.uuid, expected='expected', actual='actual') ++ self._test_rebuild_ex(instance, ex) ++ ++ def test_rebuild_notfound(self): ++- instance = objects.Instance(uuid='fake-uuid') +++ instance = fake_instance.fake_instance_obj(self.context) ++ ex = exception.InstanceNotFound(instance_id=instance.uuid) ++ self._test_rebuild_ex(instance, ex) ++ +++ def test_rebuild_node_not_updated_if_not_recreate(self): +++ node = uuidutils.generate_uuid() # ironic node uuid +++ instance = fake_instance.fake_instance_obj(self.context, node=node) +++ instance.migration_context = None +++ with test.nested( +++ mock.patch.object(self.compute, '_get_compute_info'), +++ mock.patch.object(self.compute, '_do_rebuild_instance_with_claim'), +++ mock.patch.object(objects.Instance, 'save'), +++ mock.patch.object(self.compute, '_set_migration_status'), +++ ) as (mock_get, mock_rebuild, mock_save, mock_set): +++ self.compute.rebuild_instance(self.context, instance, None, None, +++ None, None, None, None, False) +++ self.assertFalse(mock_get.called) +++ self.assertEqual(node, instance.node) +++ mock_set.assert_called_once_with(None, 'done') +++ +++ def test_rebuild_node_updated_if_recreate(self): +++ dead_node = uuidutils.generate_uuid() +++ instance = fake_instance.fake_instance_obj(self.context, +++ node=dead_node) +++ instance.migration_context = None +++ with test.nested( +++ mock.patch.object(self.compute, '_get_resource_tracker'), +++ mock.patch.object(self.compute, '_get_compute_info'), +++ mock.patch.object(self.compute, '_do_rebuild_instance_with_claim'), +++ mock.patch.object(objects.Instance, 'save'), +++ mock.patch.object(self.compute, '_set_migration_status'), +++ ) as (mock_rt, mock_get, mock_rebuild, mock_save, mock_set): +++ mock_get.return_value.hypervisor_hostname = 'new-node' +++ self.compute.rebuild_instance(self.context, instance, None, None, +++ None, None, None, None, True) +++ mock_get.assert_called_once_with(mock.ANY, self.compute.host) +++ self.assertEqual('new-node', instance.node) +++ mock_set.assert_called_once_with(None, 'done') +++ mock_rt.assert_called_once_with('new-node') +++ ++ def test_rebuild_default_impl(self): ++ def _detach(context, bdms): ++ # NOTE(rpodolyaka): check that instance has been powered off by +diff -Nru nova-13.1.4/debian/patches/series nova-13.1.4/debian/patches/series +--- nova-13.1.4/debian/patches/series 2017-07-26 14:46:18.000000000 +0900 ++++ nova-13.1.4/debian/patches/series 2017-08-04 16:38:50.000000000 +0900 +@@ -11,3 +11,5 @@ + uefi-delete-instances.patch + fix-exception-due-to-bdm-race-in-get_available_resou.patch + libvirt-fix-incorrect-host-cpus-giving-to-emulator-t.patch ++make-sure-to-rebuild-claim-on-recreate.patch ++Send-events-to-all-relevant-hosts-if-migrating.patch diff -Nru nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch --- nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch 1970-01-01 09:00:00.000000000 +0900 +++ nova-13.1.4/debian/patches/make-sure-to-rebuild-claim-on-recreate.patch 2017-08-18 13:43:05.000000000 +0900 @@ -0,0 +1,200 @@ +From 3c14ea0510cd753bda74379b4a5b83872c3e934a Mon Sep 17 00:00:00 2001 +From: Guang Yee +Date: Thu, 18 May 2017 16:38:16 -0700 +Subject: [PATCH 1/1] make sure to rebuild claim on recreate + +On recreate where the instance is being evacuated to a different node, +we should be rebuilding the claim so the migration context is available +when rebuilding the instance. + +Conflicts: + nova/compute/manager.py + nova/tests/unit/compute/test_compute_mgr.py + +NOTE(mriedem): There are a few issues here: + +1. I5aaa869f2e6155964827e659d18e2bcaad9d866b changed the LOG.info + method to not pass a context in Ocata. +2. I57233259065d887b38a79850a05177fcbbdfb8c3 changed some tests in + test_compute_manager in Ocata, but is irrelevant here. +3. The bigger change isn't a merge conflict but in Ocata the compute + manager code was all refactored so that the _get_resource_tracker + method no longer needed a nodename passed to it. In Newton, however, + if we're force evacuating (scenario 3) then we don't have a scheduled_node + passed to the rebuild_instance method and in this case we need to + lookup the nodename for the host we're currently on. To resolve this, + some existing code that handles this case is moved up where it is + needed to get the resource tracker so we can get the rebuild_claim method. + We let any ComputeHostNotFound exception raise up in this case rather than + log it because without the compute node we can't make the rebuild claim and + we need to fail. Tests are adjusted accordingly for this. + +Change-Id: I53bdcf8edf640e97b4632ef7a093f14a6e3845e4 +Closes-Bug: 1658070 +(cherry picked from commit a2b0824aca5cb4a2ae579f625327c51ed0414d35) +(cherry picked from commit ea90c60b07534a46541c55432389f2d50b5b7d0a) +(back ported from commit 0f2d87416eff1e96c0fbf0f4b08bf6b6b22246d5) +--- + nova/compute/manager.py | 33 ++++++++++++++++-------- + nova/tests/unit/compute/test_compute.py | 19 +++++--------- + nova/tests/unit/compute/test_compute_mgr.py | 40 +++++++++++++++++++++++++++-- + 3 files changed, 68 insertions(+), 24 deletions(-) + +Origin: https://github.com/openstack/nova/commit/a2b0824aca5cb4a2ae579f625327c51ed0414d35 +Bug-Ubuntu: https://bugs.launchpad.net/nova/+bug/1686041 +Index: nova-13.1.4/nova/compute/manager.py +=================================================================== +--- nova-13.1.4.orig/nova/compute/manager.py ++++ nova-13.1.4/nova/compute/manager.py +@@ -2777,7 +2777,28 @@ class ComputeManager(manager.Manager): + + LOG.info(_LI("Rebuilding instance"), context=context, + instance=instance) +- if scheduled_node is not None: ++ ++ # NOTE(gyee): there are three possible scenarios. ++ # ++ # 1. instance is being rebuilt on the same node. In this case, ++ # recreate should be False and scheduled_node should be None. ++ # 2. instance is being rebuilt on a node chosen by the ++ # scheduler (i.e. evacuate). In this case, scheduled_node should ++ # be specified and recreate should be True. ++ # 3. instance is being rebuilt on a node chosen by the user. (i.e. ++ # force evacuate). In this case, scheduled_node is not specified ++ # and recreate is set to True. ++ # ++ # For scenarios #2 and #3, we must do rebuild claim as server is ++ # being evacuated to a different node. ++ if recreate or scheduled_node is not None: ++ if scheduled_node is None: ++ # NOTE(mriedem): On a recreate (evacuate), we need to update ++ # the instance's host and node properties to reflect it's ++ # destination node for the recreate, and to make a rebuild ++ # claim. ++ compute_node = self._get_compute_info(context, self.host) ++ scheduled_node = compute_node.hypervisor_hostname + rt = self._get_resource_tracker(scheduled_node) + rebuild_claim = rt.rebuild_claim + else: +@@ -2787,16 +2808,8 @@ class ComputeManager(manager.Manager): + if image_ref: + image_meta = self.image_api.get(context, image_ref) + +- # NOTE(mriedem): On a recreate (evacuate), we need to update +- # the instance's host and node properties to reflect it's +- # destination node for the recreate. + if not scheduled_node: +- try: +- compute_node = self._get_compute_info(context, self.host) +- scheduled_node = compute_node.hypervisor_hostname +- except exception.ComputeHostNotFound: +- LOG.exception(_LE('Failed to get compute_info for %s'), +- self.host) ++ scheduled_node = instance.node + + with self._error_out_instance_on_exception(context, instance): + try: +Index: nova-13.1.4/nova/tests/unit/compute/test_compute.py +=================================================================== +--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute.py ++++ nova-13.1.4/nova/tests/unit/compute/test_compute.py +@@ -11580,17 +11580,6 @@ class EvacuateHostTestCase(BaseTestCase) + def fake_get_compute_info(context, host): + raise exception.ComputeHostNotFound(host=host) + +- self.stubs.Set(self.compute, '_get_compute_info', +- fake_get_compute_info) +- self.mox.ReplayAll() +- +- self._rebuild() +- +- # Should be on destination host +- instance = db.instance_get(self.context, self.inst.id) +- self.assertEqual(instance['host'], self.compute.host) +- self.assertIsNone(instance['node']) +- + def test_rebuild_on_host_node_passed(self): + patch_get_info = mock.patch.object(self.compute, '_get_compute_info') + patch_on_disk = mock.patch.object( +@@ -11776,15 +11765,21 @@ class EvacuateHostTestCase(BaseTestCase) + + self._rebuild(on_shared_storage=None) + +- def test_rebuild_migration_passed_in(self): ++ @mock.patch('nova.compute.manager.ComputeManager._get_compute_info') ++ @mock.patch('nova.compute.manager.ComputeManager._get_resource_tracker') ++ def test_rebuild_migration_passed_in(self, get_rt, get_compute): + migration = mock.Mock(spec=objects.Migration) + + patch_spawn = mock.patch.object(self.compute.driver, 'spawn') + patch_on_disk = mock.patch.object( + self.compute.driver, 'instance_on_disk', return_value=True) ++ get_compute.return_value = objects.ComputeNode( ++ hypervisor_hostname=NODENAME) + with patch_spawn, patch_on_disk: + self._rebuild(migration=migration) + ++ get_rt.assert_called_once_with(NODENAME) ++ self.assertTrue(get_rt.return_value.rebuild_claim.called) + self.assertEqual('done', migration.status) + migration.save.assert_called_once_with() + +Index: nova-13.1.4/nova/tests/unit/compute/test_compute_mgr.py +=================================================================== +--- nova-13.1.4.orig/nova/tests/unit/compute/test_compute_mgr.py ++++ nova-13.1.4/nova/tests/unit/compute/test_compute_mgr.py +@@ -2761,16 +2761,52 @@ class ComputeManagerUnitTestCase(test.No + 'rebuild.error', fault=ex) + + def test_rebuild_deleting(self): +- instance = objects.Instance(uuid='fake-uuid') ++ instance = fake_instance.fake_instance_obj(self.context) + ex = exception.UnexpectedDeletingTaskStateError( + instance_uuid=instance.uuid, expected='expected', actual='actual') + self._test_rebuild_ex(instance, ex) + + def test_rebuild_notfound(self): +- instance = objects.Instance(uuid='fake-uuid') ++ instance = fake_instance.fake_instance_obj(self.context) + ex = exception.InstanceNotFound(instance_id=instance.uuid) + self._test_rebuild_ex(instance, ex) + ++ def test_rebuild_node_not_updated_if_not_recreate(self): ++ node = uuidutils.generate_uuid() # ironic node uuid ++ instance = fake_instance.fake_instance_obj(self.context, node=node) ++ instance.migration_context = None ++ with test.nested( ++ mock.patch.object(self.compute, '_get_compute_info'), ++ mock.patch.object(self.compute, '_do_rebuild_instance_with_claim'), ++ mock.patch.object(objects.Instance, 'save'), ++ mock.patch.object(self.compute, '_set_migration_status'), ++ ) as (mock_get, mock_rebuild, mock_save, mock_set): ++ self.compute.rebuild_instance(self.context, instance, None, None, ++ None, None, None, None, False) ++ self.assertFalse(mock_get.called) ++ self.assertEqual(node, instance.node) ++ mock_set.assert_called_once_with(None, 'done') ++ ++ def test_rebuild_node_updated_if_recreate(self): ++ dead_node = uuidutils.generate_uuid() ++ instance = fake_instance.fake_instance_obj(self.context, ++ node=dead_node) ++ instance.migration_context = None ++ with test.nested( ++ mock.patch.object(self.compute, '_get_resource_tracker'), ++ mock.patch.object(self.compute, '_get_compute_info'), ++ mock.patch.object(self.compute, '_do_rebuild_instance_with_claim'), ++ mock.patch.object(objects.Instance, 'save'), ++ mock.patch.object(self.compute, '_set_migration_status'), ++ ) as (mock_rt, mock_get, mock_rebuild, mock_save, mock_set): ++ mock_get.return_value.hypervisor_hostname = 'new-node' ++ self.compute.rebuild_instance(self.context, instance, None, None, ++ None, None, None, None, True) ++ mock_get.assert_called_once_with(mock.ANY, self.compute.host) ++ self.assertEqual('new-node', instance.node) ++ mock_set.assert_called_once_with(None, 'done') ++ mock_rt.assert_called_once_with('new-node') ++ + def test_rebuild_default_impl(self): + def _detach(context, bdms): + # NOTE(rpodolyaka): check that instance has been powered off by diff -Nru nova-13.1.4/debian/patches/series nova-13.1.4/debian/patches/series --- nova-13.1.4/debian/patches/series 2017-07-26 14:46:18.000000000 +0900 +++ nova-13.1.4/debian/patches/series 2017-08-08 14:03:35.000000000 +0900 @@ -11,3 +11,5 @@ uefi-delete-instances.patch fix-exception-due-to-bdm-race-in-get_available_resou.patch libvirt-fix-incorrect-host-cpus-giving-to-emulator-t.patch +make-sure-to-rebuild-claim-on-recreate.patch +Send-events-to-all-relevant-hosts-if-migrating.patch