Metadata-API fails to retrieve avz for instances created before Pike

In Pike (through change: I8d426f2635232ffc4b510548a905794ca88d7f99)
we started setting instance.avilability_zone during schedule time by
calculating the avz of the host into which the instance was scheduled
into. After this change was introduced, the metadata request for the avz
on the instance (through change: I73c3b10e52ab4cfda9dacc0c0ba92d1fcb60bcc9)
started using instance.get(availability_zone) instead of doing the upcall.
However this would return None for instances older than Pike whose
availability_zone was not mentioned during boot time as it would be set to
CONF.default_schedule_zone whose default value is None.

This patch adds an online_migration tool to populate missing
instance.availability_zone values.

Change-Id: I2a1d81bfeb1ea006c16d8f403e045e9acedcbe57
Closes-Bug: #1768876
This commit is contained in:
Surya Seetharaman 2018-05-11 17:12:34 +02:00 committed by Matt Riedemann
parent c1e0d2bf20
commit 6b4c38c041
4 changed files with 77 additions and 0 deletions

View File

@ -57,6 +57,7 @@ from nova import objects
from nova.objects import block_device as block_device_obj
from nova.objects import build_request as build_request_obj
from nova.objects import host_mapping as host_mapping_obj
from nova.objects import instance as instance_obj
from nova.objects import instance_group as instance_group_obj
from nova.objects import keypair as keypair_obj
from nova.objects import quotas as quotas_obj
@ -392,6 +393,11 @@ class DbCommands(object):
sa_db.migration_migrate_to_uuid,
# Added in Queens
block_device_obj.BlockDeviceMapping.populate_uuids,
# Added in Rocky
# NOTE(tssurya): This online migration is going to be backported to
# Queens and Pike since instance.avz of instances before Pike
# need to be populated if it was not specified during boot time.
instance_obj.populate_missing_availability_zones,
)
def __init__(self):

View File

@ -24,6 +24,7 @@ from sqlalchemy import or_
from sqlalchemy.sql import func
from sqlalchemy.sql import null
from nova import availability_zones as avail_zone
from nova.cells import opts as cells_opts
from nova.cells import rpcapi as cells_rpcapi
from nova.cells import utils as cells_utils
@ -1234,6 +1235,20 @@ def _make_instance_list(context, inst_list, db_inst_list, expected_attrs):
return inst_list
@db_api.pick_context_manager_writer
def populate_missing_availability_zones(context, count):
instances = (context.session.query(models.Instance).
filter_by(availability_zone=None).limit(count).all())
count_all = len(instances)
count_hit = 0
for instance in instances:
az = avail_zone.get_instance_availability_zone(context, instance)
instance.availability_zone = az
instance.save(context.session)
count_hit += 1
return count_all, count_hit
@base.NovaObjectRegistry.register
class InstanceList(base.ObjectListBase, base.NovaObject):
# Version 2.0: Initial Version

View File

@ -14,6 +14,7 @@ from oslo_utils import uuidutils
from nova.compute import vm_states
from nova import context
from nova import db
from nova import objects
from nova import test
@ -73,3 +74,48 @@ class InstanceObjectTestCase(test.TestCase):
instance = objects.Instance.get_by_uuid(
self.context, instance.uuid, expected_attrs=['flavor'])
self.assertIsNone(instance.flavor.description)
def test_populate_missing_availability_zones(self):
# create two instances once with avz set and other not set.
inst1 = self._create_instance(host="fake-host1")
uuid1 = inst1.uuid
inst2 = self._create_instance(availability_zone="fake",
host="fake-host2")
self.assertIsNone(inst1.availability_zone)
self.assertEqual("fake", inst2.availability_zone)
count_all, count_hit = (objects.instance.
populate_missing_availability_zones(self.context, 10))
# we get only the instance whose avz was None.
self.assertEqual(1, count_all)
self.assertEqual(1, count_hit)
inst1 = objects.Instance.get_by_uuid(self.context, uuid1)
# since instance.host was None, avz is set to
# CONF.default_availability_zone i.e 'nova' which is the default zone
# for compute services.
self.assertEqual('nova', inst1.availability_zone)
# create an instance with avz as None on a host that has avz.
host = 'fake-host'
agg_meta = {'name': 'az_agg', 'uuid': uuidutils.generate_uuid(),
'metadata': {'availability_zone': 'nova-test'}}
agg = objects.Aggregate(self.context, **agg_meta)
agg.create()
agg = objects.Aggregate.get_by_id(self.context, agg.id)
values = {
'binary': 'nova-compute',
'host': host,
'topic': 'compute',
'disabled': False,
}
service = db.service_create(self.context, values)
agg.add_host(service['host'])
inst3 = self._create_instance(host=host)
uuid3 = inst3.uuid
self.assertIsNone(inst3.availability_zone)
count_all, count_hit = (objects.instance.
populate_missing_availability_zones(self.context, 10))
# we get only the instance whose avz was None i.e inst3.
self.assertEqual(1, count_all)
self.assertEqual(1, count_hit)
inst3 = objects.Instance.get_by_uuid(self.context, uuid3)
self.assertEqual('nova-test', inst3.availability_zone)

View File

@ -0,0 +1,10 @@
---
upgrade:
- |
A new online data migration has been added to populate missing
instance.availability_zone values for instances older than Pike whose
availability_zone was not specified during boot time. This can be run
during the normal ``nova-manage db online_data_migrations`` routine.
This fixes `Bug 1768876`_
.. _Bug 1768876: https://bugs.launchpad.net/nova/+bug/1768876