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 nova/tests/unit/objects/test_instance.py NOTE(stephenfin): Conflicts in 'fake_instance.py' are due to change If7f48933db10fcca3b9a05e1e978dfc51f6dabd0 ("Claim resources in resource tracker"), which is related to the vPMEM work and shouldn't be backported, while the conflicts in 'test_instance.py' are due to change Ic89352a9900515484bffe961475feb1cefc6b2a9 ("Remove 'instance_update_at_top', 'instance_destroy_at_top'") which removed some cells v1 tests but shouldn't be removed here where cells v1 is technically still a thing. Change-Id: I8a2726b39d0444de8c35480024078a97430f5d0c Closes-Bug: #1843708 Co-authored-by: Stephen Finucane <stephenfin@redhat.com> (cherry picked from commit086796021b
) (cherry picked from commitaed86ee5d6
) (cherry picked from commitb971dc82cb
)
This commit is contained in:
parent
e91540f965
commit
0bc5a4ecb5
|
@ -714,8 +714,9 @@ class Instance(base.NovaPersistentObject, base.NovaObject,
|
|||
pass
|
||||
|
||||
def _save_keypairs(self, context):
|
||||
# NOTE(danms): Read-only so no need to save this.
|
||||
pass
|
||||
if 'keypairs' in self.obj_what_changed():
|
||||
self._save_extra_generic('keypairs')
|
||||
self.obj_reset_changes(['keypairs'], recursive=True)
|
||||
|
||||
def _save_extra_generic(self, field):
|
||||
if field in self.obj_what_changed():
|
||||
|
|
|
@ -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)
|
|
@ -118,7 +118,6 @@ def fake_instance_obj(context, obj_instance_class=None, **updates):
|
|||
is_public=True,
|
||||
extra_specs={},
|
||||
projects=[])
|
||||
flavor.obj_reset_changes()
|
||||
inst = obj_instance_class._from_db_object(context,
|
||||
obj_instance_class(), fake_db_instance(**updates),
|
||||
expected_attrs=expected_attrs)
|
||||
|
@ -137,7 +136,7 @@ def fake_instance_obj(context, obj_instance_class=None, **updates):
|
|||
inst.instance_type_id = flavor.id
|
||||
inst.old_flavor = None
|
||||
inst.new_flavor = None
|
||||
inst.obj_reset_changes()
|
||||
inst.obj_reset_changes(recursive=True)
|
||||
return inst
|
||||
|
||||
|
||||
|
|
|
@ -709,14 +709,30 @@ class _TestInstanceObject(object):
|
|||
inst.numa_topology = None
|
||||
inst.migration_context = None
|
||||
inst.vcpu_model = test_vcpu_model.fake_vcpumodel
|
||||
inst.save()
|
||||
inst.keypairs = objects.KeyPairList(
|
||||
objects=[objects.KeyPair(name='foo')])
|
||||
|
||||
json_vcpu_model = jsonutils.dumps(
|
||||
test_vcpu_model.fake_vcpumodel.obj_to_primitive())
|
||||
expected_vals = {'numa_topology': None,
|
||||
'migration_context': None,
|
||||
'vcpu_model': json_vcpu_model}
|
||||
json_keypairs = jsonutils.dumps(inst.keypairs.obj_to_primitive())
|
||||
|
||||
# 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,
|
||||
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(notifications, 'send_update')
|
||||
@mock.patch.object(cells_rpcapi.CellsAPI, 'instance_update_from_api')
|
||||
|
|
Loading…
Reference in New Issue