objects: Update keypairs when saving an instance

The keypair of a server is updated when rebuilding the server with a
keypair. This function has been added since API microversion 2.54.
However the 'keypairs' of the instance object is not saved when saving
the instance object currently.

Make the instance object update the 'keypairs' field when saving the
instance object.

Conflicts:
	nova/tests/unit/fake_instance.py

NOTE(stephenfin): Conflicts are due to change
I44ad826f0edb39d770bb3201c675dff78154cbf3 ("partial support for live
migration with specific resources"), which initialized the
'migration_context' attribute of the instance created as part of the
'fake_instance_obj' function.

Changes:
	nova/tests/functional/regressions/test_bug_1843708.py

NOTE(stephenfin): The 'IntegratedTestBase' and 'InstanceHelperMixin'
base classes for functional tests are not fully integrated yet in
stable/train, which requires reinventing the wheel somewhat.

Change-Id: I8a2726b39d0444de8c35480024078a97430f5d0c
Closes-Bug: #1843708
Co-authored-by: Stephen Finucane <stephenfin@redhat.com>
(cherry picked from commit 086796021b)
(cherry picked from commit aed86ee5d6)
This commit is contained in:
Takashi NATSUME 2019-09-19 15:57:44 +09:00 committed by Stephen Finucane
parent 7e91134c23
commit b971dc82cb
4 changed files with 118 additions and 8 deletions

View File

@ -734,8 +734,9 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
pass pass
def _save_keypairs(self, context): def _save_keypairs(self, context):
# NOTE(danms): Read-only so no need to save this. if 'keypairs' in self.obj_what_changed():
pass self._save_extra_generic('keypairs')
self.obj_reset_changes(['keypairs'], recursive=True)
def _save_extra_generic(self, field): def _save_extra_generic(self, field):
if field in self.obj_what_changed(): if field in self.obj_what_changed():

View File

@ -0,0 +1,93 @@
# Copyright 2019 NTT Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import context
from nova import objects
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional import fixtures as func_fixtures
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_notifier
from nova.tests.unit.image import fake as fake_image
class RebuildWithKeypairTestCase(
test.TestCase, integrated_helpers.InstanceHelperMixin,
):
"""Regression test for bug 1843708.
This tests a rebuild scenario with new key pairs.
"""
def setUp(self):
super(RebuildWithKeypairTestCase, self).setUp()
# Start standard fixtures.
self.useFixture(func_fixtures.PlacementFixture())
self.useFixture(nova_fixtures.NeutronFixture(self))
fake_image.stub_out_image_service(self)
self.addCleanup(fake_image.FakeImageService_reset)
fake_notifier.stub_notifier(self)
self.addCleanup(fake_notifier.reset)
self.api = self.useFixture(nova_fixtures.OSAPIFixture(
api_version='v2.1')).admin_api
self.api.microversion = 'latest'
# Start nova services.
self.start_service('conductor')
self.start_service('scheduler')
self.start_service('compute')
def test_rebuild_with_keypair(self):
keypair_req = {
'keypair': {
'name': 'test-key1',
'type': 'ssh',
},
}
keypair1 = self.api.post_keypair(keypair_req)
keypair_req['keypair']['name'] = 'test-key2'
keypair2 = self.api.post_keypair(keypair_req)
server = self._build_minimal_create_server_request(
self.api, 'test-rebuild-with-keypair',
image_uuid=fake_image.get_valid_image_id(),
networks='none')
server.update({'key_name': 'test-key1'})
# Create a server with keypair 'test-key1'
server = self.api.post_server({'server': server})
self._wait_for_state_change(self.api, server, 'ACTIVE')
# Check keypairs
ctxt = context.get_admin_context()
instance = objects.Instance.get_by_uuid(
ctxt, server['id'], expected_attrs=['keypairs'])
self.assertEqual(
keypair1['public_key'], instance.keypairs[0].public_key)
# Rebuild a server with keypair 'test-key2'
body = {
'rebuild': {
'imageRef': fake_image.get_valid_image_id(),
'key_name': 'test-key2',
},
}
self.api.api_post('servers/%s/action' % server['id'], body)
fake_notifier.wait_for_versioned_notifications('instance.rebuild.end')
self._wait_for_state_change(self.api, server, 'ACTIVE')
# Check keypairs changed
instance = objects.Instance.get_by_uuid(
ctxt, server['id'], expected_attrs=['keypairs'])
self.assertEqual(
keypair2['public_key'], instance.keypairs[0].public_key)

View File

@ -119,7 +119,6 @@ def fake_instance_obj(context, obj_instance_class=None, **updates):
is_public=True, is_public=True,
extra_specs={}, extra_specs={},
projects=[]) projects=[])
flavor.obj_reset_changes()
inst = obj_instance_class._from_db_object(context, inst = obj_instance_class._from_db_object(context,
obj_instance_class(), fake_db_instance(**updates), obj_instance_class(), fake_db_instance(**updates),
expected_attrs=expected_attrs) expected_attrs=expected_attrs)
@ -140,7 +139,7 @@ def fake_instance_obj(context, obj_instance_class=None, **updates):
inst.old_flavor = None inst.old_flavor = None
inst.new_flavor = None inst.new_flavor = None
inst.resources = None inst.resources = None
inst.obj_reset_changes() inst.obj_reset_changes(recursive=True)
return inst return inst

View File

@ -683,15 +683,32 @@ class _TestInstanceObject(object):
inst.numa_topology = None inst.numa_topology = None
inst.migration_context = None inst.migration_context = None
inst.vcpu_model = test_vcpu_model.fake_vcpumodel inst.vcpu_model = test_vcpu_model.fake_vcpumodel
inst.save() inst.keypairs = objects.KeyPairList(
objects=[objects.KeyPair(name='foo')])
json_vcpu_model = jsonutils.dumps( json_vcpu_model = jsonutils.dumps(
test_vcpu_model.fake_vcpumodel.obj_to_primitive()) test_vcpu_model.fake_vcpumodel.obj_to_primitive())
expected_vals = {'numa_topology': None, json_keypairs = jsonutils.dumps(inst.keypairs.obj_to_primitive())
'migration_context': None,
'vcpu_model': json_vcpu_model} # Check changed fields in the instance object
self.assertIn('keypairs', inst.obj_what_changed())
self.assertEqual({'objects'}, inst.keypairs.obj_what_changed())
inst.save()
expected_vals = {
'numa_topology': None,
'migration_context': None,
'vcpu_model': json_vcpu_model,
'keypairs': json_keypairs,
}
mock_update.assert_called_once_with(self.context, inst.uuid, mock_update.assert_called_once_with(self.context, inst.uuid,
expected_vals) expected_vals)
# Verify that the record of changed fields has been cleared
self.assertNotIn('keypairs', inst.obj_what_changed())
self.assertEqual(set(), inst.keypairs.obj_what_changed())
@mock.patch.object(db, 'instance_get_by_uuid') @mock.patch.object(db, 'instance_get_by_uuid')
def test_get_deleted(self, mock_get): def test_get_deleted(self, mock_get):
fake_inst = dict(self.fake_instance, id=123, deleted=123) fake_inst = dict(self.fake_instance, id=123, deleted=123)