diff --git a/bareon_ironic/modules/bareon_base.py b/bareon_ironic/modules/bareon_base.py index 26e5bb4..a3cb24e 100644 --- a/bareon_ironic/modules/bareon_base.py +++ b/bareon_ironic/modules/bareon_base.py @@ -31,6 +31,8 @@ from oslo_utils import fileutils from oslo_log import log from oslo_service import loopingcall +from ironic_lib import utils as ironic_utils + from ironic.common import boot_devices from ironic.common import dhcp_factory from ironic.common import exception @@ -267,7 +269,7 @@ class BareonDeploy(base.DeployInterface): pxe_info = self._get_tftp_image_info(task.node) for label in pxe_info: path = pxe_info[label][1] - utils.unlink_without_raise(path) + ironic_utils.unlink_without_raise(path) AgentTFTPImageCache().clean_up() pxe_utils.clean_up_pxe_config(task) diff --git a/bareon_ironic/modules/resources/resources.py b/bareon_ironic/modules/resources/resources.py index cf2abda..14f54e2 100644 --- a/bareon_ironic/modules/resources/resources.py +++ b/bareon_ironic/modules/resources/resources.py @@ -23,6 +23,8 @@ from oslo_utils import fileutils from oslo_log import log from six.moves.urllib import parse +from ironic_lib import utils as ironic_utils + from ironic.common import exception from ironic.common import utils from ironic.common.i18n import _ @@ -135,7 +137,7 @@ def url_download_raw(context, node, url): resource_path = url_download(context, node, url) with open(resource_path) as f: raw = f.read() - utils.unlink_without_raise(resource_path) + ironic_utils.unlink_without_raise(resource_path) return raw @@ -232,7 +234,7 @@ class Resource(bareon_utils.RawToPropertyMixin): """ if not self.is_fetched(): return - utils.unlink_without_raise(self.local_path) + ironic_utils.unlink_without_raise(self.local_path) self._raw.pop('local_path', None) @staticmethod @@ -497,7 +499,7 @@ class PullMountResource(Resource): return bareon_utils.umount_without_raise(self.mount_point, '-fl') self._raw.pop('mount_point', None) - utils.unlink_without_raise(self.local_path) + ironic_utils.unlink_without_raise(self.local_path) self._raw.pop('local_path', None) diff --git a/patches/patch-ironic-stable-liberty b/patches/patch-ironic-stable-mitaka similarity index 83% rename from patches/patch-ironic-stable-liberty rename to patches/patch-ironic-stable-mitaka index ee2e6b7..e83125e 100644 --- a/patches/patch-ironic-stable-liberty +++ b/patches/patch-ironic-stable-mitaka @@ -1,8 +1,8 @@ diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py -index d95298f..1b99c68 100644 +index 30d52e8..0c66cb0 100644 --- a/ironic/api/controllers/v1/node.py +++ b/ironic/api/controllers/v1/node.py -@@ -452,8 +452,13 @@ class NodeStatesController(rest.RestController): +@@ -472,8 +472,13 @@ class NodeStatesController(rest.RestController): raise exception.NodeInMaintenance(op=_('provisioning'), node=rpc_node.uuid) @@ -16,7 +16,7 @@ index d95298f..1b99c68 100644 if not m.is_actionable_event(ir_states.VERBS.get(target, target)): # Normally, we let the task manager recognize and deal with # NodeLocked exceptions. However, that isn't done until the RPC -@@ -470,6 +475,16 @@ class NodeStatesController(rest.RestController): +@@ -490,6 +495,16 @@ class NodeStatesController(rest.RestController): action=target, node=rpc_node.uuid, state=rpc_node.provision_state) @@ -34,7 +34,7 @@ index d95298f..1b99c68 100644 msg = (_('Adding a config drive is only supported when setting ' 'provision state to %s') % ir_states.ACTIVE) diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py -index 538ca45..3715d41 100644 +index 5a7f474..3f39f3f 100644 --- a/ironic/api/controllers/v1/utils.py +++ b/ironic/api/controllers/v1/utils.py @@ -23,6 +23,7 @@ from webob.static import FileIter @@ -45,7 +45,7 @@ index 538ca45..3715d41 100644 from ironic.common import exception from ironic.common.i18n import _ from ironic.common import states -@@ -109,7 +110,16 @@ def is_valid_node_name(name): +@@ -122,7 +123,16 @@ def is_valid_node_name(name): :param: name: the node name to check. :returns: True if the name is valid, False otherwise. """ @@ -74,11 +74,11 @@ index ccd2222..b8186c9 100644 - values.pop('tenant', None) return cls(**values) diff --git a/ironic/common/images.py b/ironic/common/images.py -index 5b00e65..28e6bd7 100644 +index 682a1a7..6685248 100644 --- a/ironic/common/images.py +++ b/ironic/common/images.py -@@ -328,16 +328,17 @@ def convert_image(source, dest, out_format, run_as_root=False): - utils.execute(*cmd, run_as_root=run_as_root) +@@ -314,16 +314,17 @@ def create_isolinux_image_for_uefi(output_file, deploy_iso, kernel, ramdisk, + raise exception.ImageCreationFailed(image_type='iso', error=e) -def fetch(context, image_href, path, force_raw=False): @@ -102,10 +102,10 @@ index 5b00e65..28e6bd7 100644 with fileutils.remove_path_on_error(path): with open(path, "wb") as image_file: diff --git a/ironic/common/states.py b/ironic/common/states.py -index e61c807..2523a7f 100644 +index 09c4aff..0e87cfd 100644 --- a/ironic/common/states.py +++ b/ironic/common/states.py -@@ -245,6 +245,9 @@ machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers) +@@ -246,6 +246,9 @@ machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers) # A deployment may fail machine.add_transition(DEPLOYING, DEPLOYFAIL, 'fail') @@ -116,7 +116,7 @@ index e61c807..2523a7f 100644 # ironic/conductor/manager.py:do_node_deploy() machine.add_transition(DEPLOYFAIL, DEPLOYING, 'rebuild') diff --git a/ironic/common/swift.py b/ironic/common/swift.py -index 8fa2d65..14d6b55 100644 +index b5c66c6..067d491 100644 --- a/ironic/common/swift.py +++ b/ironic/common/swift.py @@ -24,6 +24,7 @@ from swiftclient import utils as swift_utils @@ -141,41 +141,43 @@ index 8fa2d65..14d6b55 100644 CONF.import_opt('admin_user', 'keystonemiddleware.auth_token', group='keystone_authtoken') CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token', -@@ -62,7 +70,9 @@ class SwiftAPI(object): - tenant_name=CONF.keystone_authtoken.admin_tenant_name, - key=CONF.keystone_authtoken.admin_password, - auth_url=CONF.keystone_authtoken.auth_uri, -- auth_version=CONF.keystone_authtoken.auth_version): -+ auth_version=CONF.keystone_authtoken.auth_version, +@@ -63,7 +71,9 @@ class SwiftAPI(object): + key=None, + auth_url=None, + auth_version=None, +- region_name=None): ++ region_name=None, + preauthtoken=None, + preauthtenant=None): """Constructor for creating a SwiftAPI object. :param user: the name of the user for Swift account -@@ -70,16 +80,41 @@ class SwiftAPI(object): - :param key: the 'password' or key to authenticate with +@@ -72,6 +82,11 @@ class SwiftAPI(object): :param auth_url: the url for authentication :param auth_version: the version of api to use for authentication + :param region_name: the region used for getting endpoints of swift + :param preauthtoken: authentication token (if you have already + authenticated) note authurl/user/key/tenant_name + are not required when specifying preauthtoken + :param preauthtenant a tenant that will be accessed using the + preauthtoken """ -- auth_url = keystone.get_keystone_url(auth_url, auth_version) -- params = {'retries': CONF.swift.swift_max_retries, -- 'insecure': CONF.keystone_authtoken.insecure, + user = user or CONF.keystone_authtoken.admin_user + tenant_name = tenant_name or CONF.keystone_authtoken.admin_tenant_name +@@ -79,14 +94,34 @@ class SwiftAPI(object): + auth_url = auth_url or CONF.keystone_authtoken.auth_uri + auth_version = auth_version or CONF.keystone_authtoken.auth_version + auth_url = keystone.get_keystone_url(auth_url, auth_version) ++ + params = {'retries': CONF.swift.swift_max_retries, + 'insecure': CONF.keystone_authtoken.insecure, - 'cacert': CONF.keystone_authtoken.cafile, - 'user': user, - 'tenant_name': tenant_name, - 'key': key, - 'authurl': auth_url, - 'auth_version': auth_version} -+ params = { -+ 'retries': CONF.swift.swift_max_retries, -+ 'insecure': CONF.keystone_authtoken.insecure, -+ 'cacert': CONF.keystone_authtoken.cafile -+ } ++ 'cacert': CONF.keystone_authtoken.cafile} + + if preauthtoken: + # Determining swift url for the user's tenant account. @@ -192,7 +194,6 @@ index 8fa2d65..14d6b55 100644 + 'preauthurl': url + }) + else: -+ auth_url = keystone.get_keystone_url(auth_url, auth_version) + params.update({ + 'user': user, + 'tenant_name': tenant_name, @@ -200,10 +201,11 @@ index 8fa2d65..14d6b55 100644 + 'authurl': auth_url, + 'auth_version': auth_version + }) - - self.connection = swift_client.Connection(**params) - -@@ -131,8 +166,8 @@ class SwiftAPI(object): ++ + region_name = region_name or CONF.keystone_authtoken.region_name + if region_name: + params['os_options'] = {'region_name': region_name} +@@ -141,8 +176,8 @@ class SwiftAPI(object): operation = _("head account") raise exception.SwiftOperationError(operation=operation, error=e) @@ -214,7 +216,7 @@ index 8fa2d65..14d6b55 100644 parse_result = parse.urlparse(storage_url) swift_object_path = '/'.join((parse_result.path, container, object)) temp_url_key = account_info['x-account-meta-temp-url-key'] -@@ -189,3 +224,23 @@ class SwiftAPI(object): +@@ -205,3 +240,23 @@ class SwiftAPI(object): except swift_exceptions.ClientException as e: operation = _("post object") raise exception.SwiftOperationError(operation=operation, error=e) @@ -239,10 +241,10 @@ index 8fa2d65..14d6b55 100644 + operation = _("get object") + raise exception.SwiftOperationError(operation=operation, error=e) diff --git a/ironic/common/utils.py b/ironic/common/utils.py -index f863087..ed4398f 100644 +index 9652a1b..7d17dcc 100644 --- a/ironic/common/utils.py +++ b/ironic/common/utils.py -@@ -42,6 +42,7 @@ from ironic.common import exception +@@ -41,6 +41,7 @@ from ironic.common import exception from ironic.common.i18n import _ from ironic.common.i18n import _LE from ironic.common.i18n import _LW @@ -250,8 +252,8 @@ index f863087..ed4398f 100644 utils_opts = [ cfg.StrOpt('rootwrap_config', -@@ -560,6 +561,11 @@ def is_http_url(url): - return url.startswith('http://') or url.startswith('https://') +@@ -560,6 +561,11 @@ def umount(loc, *args): + execute(*args, run_as_root=True, check_exit_code=[0]) +def get_tenant_id(tenant_name): @@ -263,10 +265,10 @@ index f863087..ed4398f 100644 """Check a directory is usable. diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py -index b4bee31..e5bb190 100644 +index 7e5679f..f419877 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py -@@ -768,6 +768,10 @@ class ConductorManager(periodic_task.PeriodicTasks): +@@ -588,6 +588,11 @@ class ConductorManager(base_manager.BaseConductorManager): """ LOG.debug("RPC do_node_tear_down called for node %s." % node_id) @@ -274,14 +276,15 @@ index b4bee31..e5bb190 100644 + if (task.node.provision_state == states.DEPLOYING and + task.driver.deploy.can_terminate_deployment): + task.driver.deploy.terminate_deployment(task) ++ with task_manager.acquire(context, node_id, shared=False, purpose='node tear down') as task: try: diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py -index 098b7a0..6ebe05d 100644 +index 02c3a75..15a2ce2 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py -@@ -345,6 +345,13 @@ class DeployInterface(BaseInterface): +@@ -376,6 +376,13 @@ class DeployInterface(BaseInterface): """ pass @@ -296,7 +299,7 @@ index 098b7a0..6ebe05d 100644 @six.add_metaclass(abc.ABCMeta) class BootInterface(object): diff --git a/ironic/drivers/modules/image_cache.py b/ironic/drivers/modules/image_cache.py -index 8bb1e23..8e1a921 100644 +index 1835425..7c45ef3 100644 --- a/ironic/drivers/modules/image_cache.py +++ b/ironic/drivers/modules/image_cache.py @@ -27,6 +27,7 @@ from oslo_concurrency import lockutils @@ -317,7 +320,7 @@ index 8bb1e23..8e1a921 100644 """Constructor. :param master_dir: cache directory to work on -@@ -70,6 +72,7 @@ class ImageCache(object): +@@ -71,6 +73,7 @@ class ImageCache(object): self.master_dir = master_dir self._cache_size = cache_size self._cache_ttl = cache_ttl @@ -325,7 +328,7 @@ index 8bb1e23..8e1a921 100644 if master_dir is not None: fileutils.ensure_tree(master_dir) -@@ -94,23 +97,28 @@ class ImageCache(object): +@@ -95,23 +98,28 @@ class ImageCache(object): # NOTE(ghe): We don't share images between instances/hosts if not CONF.parallel_image_downloads: with lockutils.lock(img_download_lock_name, 'ironic-'): @@ -362,7 +365,7 @@ index 8bb1e23..8e1a921 100644 master_path = os.path.join(self.master_dir, master_file_name) if CONF.parallel_image_downloads: -@@ -121,8 +129,8 @@ class ImageCache(object): +@@ -122,8 +130,8 @@ class ImageCache(object): # NOTE(vdrok): After rebuild requested image can change, so we # should ensure that dest_path and master_path (if exists) are # pointing to the same file and their content is up to date @@ -373,7 +376,7 @@ index 8bb1e23..8e1a921 100644 dest_up_to_date = _delete_dest_path_if_stale(master_path, dest_path) -@@ -168,7 +176,8 @@ class ImageCache(object): +@@ -169,7 +177,8 @@ class ImageCache(object): tmp_path = os.path.join(tmp_dir, href.split('/')[-1]) try: @@ -383,7 +386,7 @@ index 8bb1e23..8e1a921 100644 # NOTE(dtantsur): no need for global lock here - master_path # will have link count >1 at any moment, so won't be cleaned up os.link(tmp_path, master_path) -@@ -308,10 +317,11 @@ def _free_disk_space_for(path): +@@ -310,10 +319,11 @@ def _free_disk_space_for(path): return stat.f_frsize * stat.f_bavail @@ -397,7 +400,7 @@ index 8bb1e23..8e1a921 100644 # Notes(yjiang5): If glance can provide the virtual size information, # then we can firstly clean cache and then invoke images.fetch(). if force_raw: -@@ -384,7 +394,7 @@ def cleanup(priority): +@@ -386,7 +396,7 @@ def cleanup(priority): return _add_property_to_class_func @@ -406,7 +409,7 @@ index 8bb1e23..8e1a921 100644 """Delete image from cache if it is not up to date with href contents. :param master_path: path to an image in master cache -@@ -397,7 +407,8 @@ def _delete_master_path_if_stale(master_path, href, ctx): +@@ -399,7 +409,8 @@ def _delete_master_path_if_stale(master_path, href, ctx): # Glance image contents cannot be updated without changing image's UUID return os.path.exists(master_path) if os.path.exists(master_path): @@ -416,11 +419,28 @@ index 8bb1e23..8e1a921 100644 img_mtime = img_service.show(href).get('updated_at') if not img_mtime: # This means that href is not a glance image and doesn't have an -diff --git a/ironic/tests/common/test_swift.py b/ironic/tests/common/test_swift.py -index 43e3ef0..b2632c4 100644 ---- a/ironic/tests/common/test_swift.py -+++ b/ironic/tests/common/test_swift.py -@@ -120,6 +120,7 @@ class SwiftTestCase(base.TestCase): +diff --git a/ironic/tests/unit/common/test_rpc.py b/ironic/tests/unit/common/test_rpc.py +index 6e10aac..5a8f316 100644 +--- a/ironic/tests/unit/common/test_rpc.py ++++ b/ironic/tests/unit/common/test_rpc.py +@@ -65,10 +65,8 @@ class TestRequestContextSerializer(base.TestCase): + + def test_deserialize_context(self): + self.context.user = 'fake-user' +- self.context.tenant = 'fake-tenant' + serialize_values = self.context.to_dict() + new_context = self.serializer.deserialize_context(serialize_values) +- # Ironic RequestContext from_dict will pop 'user' and 'tenant' and +- # initialize to None. ++ # Ironic RequestContext from_dict will pop 'user' and initialize ++ # to None. + self.assertIsNone(new_context.user) +- self.assertIsNone(new_context.tenant) +diff --git a/ironic/tests/unit/common/test_swift.py b/ironic/tests/unit/common/test_swift.py +index f696145..eb91895 100644 +--- a/ironic/tests/unit/common/test_swift.py ++++ b/ironic/tests/unit/common/test_swift.py +@@ -127,6 +127,7 @@ class SwiftTestCase(base.TestCase): connection_obj_mock.get_auth.return_value = auth head_ret_val = {'x-account-meta-temp-url-key': 'secretkey'} connection_obj_mock.head_account.return_value = head_ret_val @@ -428,10 +448,10 @@ index 43e3ef0..b2632c4 100644 gen_temp_url_mock.return_value = 'temp-url-path' temp_url_returned = swiftapi.get_temp_url('container', 'object', 10) connection_obj_mock.get_auth.assert_called_once_with() -diff --git a/ironic/tests/drivers/test_image_cache.py b/ironic/tests/drivers/test_image_cache.py -index 3d666cd..436aa49 100644 ---- a/ironic/tests/drivers/test_image_cache.py -+++ b/ironic/tests/drivers/test_image_cache.py +diff --git a/ironic/tests/unit/drivers/modules/test_image_cache.py b/ironic/tests/unit/drivers/modules/test_image_cache.py +index 1224f52..ed9d83c 100644 +--- a/ironic/tests/unit/drivers/modules/test_image_cache.py ++++ b/ironic/tests/unit/drivers/modules/test_image_cache.py @@ -59,7 +59,7 @@ class TestImageCacheFetch(base.TestCase): self.cache.fetch_image(self.uuid, self.dest_path) self.assertFalse(mock_download.called) @@ -486,7 +506,7 @@ index 3d666cd..436aa49 100644 self.assertEqual(self.uuid, uuid) self.assertNotEqual(self.dest_path, tmp_path) self.assertNotEqual(os.path.dirname(tmp_path), self.master_dir) -@@ -430,7 +430,7 @@ class TestImageCacheCleanUp(base.TestCase): +@@ -446,7 +446,7 @@ class TestImageCacheCleanUp(base.TestCase): @mock.patch.object(utils, 'rmtree_without_raise', autospec=True) @mock.patch.object(image_cache, '_fetch', autospec=True) def test_temp_images_not_cleaned(self, mock_fetch, mock_rmtree): @@ -495,7 +515,7 @@ index 3d666cd..436aa49 100644 with open(tmp_path, 'w') as fp: fp.write("TEST" * 10) -@@ -675,7 +675,8 @@ class TestFetchCleanup(base.TestCase): +@@ -691,7 +691,8 @@ class TestFetchCleanup(base.TestCase): mock_size.return_value = 100 image_cache._fetch('fake', 'fake-uuid', '/foo/bar', force_raw=True) mock_fetch.assert_called_once_with('fake', 'fake-uuid', @@ -505,4 +525,3 @@ index 3d666cd..436aa49 100644 mock_clean.assert_called_once_with('/foo', 100) mock_raw.assert_called_once_with('fake-uuid', '/foo/bar', '/foo/bar.part') - diff --git a/patches/patch-nova-stable-liberty b/patches/patch-nova-stable-mitaka similarity index 84% rename from patches/patch-nova-stable-liberty rename to patches/patch-nova-stable-mitaka index e29466b..3392df1 100644 --- a/patches/patch-nova-stable-liberty +++ b/patches/patch-nova-stable-mitaka @@ -1,41 +1,41 @@ diff --git a/nova/objects/image_meta.py b/nova/objects/image_meta.py -index 15be3f1..83fc2fb 100644 +index 72a16dc..1003c14 100644 --- a/nova/objects/image_meta.py +++ b/nova/objects/image_meta.py -@@ -346,6 +346,7 @@ class ImageMetaProps(base.NovaObject): +@@ -414,6 +414,7 @@ class ImageMetaProps(base.NovaObject): # is a fairly generic type. For a detailed type consider os_distro # instead 'os_type': fields.OSTypeField(), + 'deploy_config': fields.StringField(), } - + # The keys are the legacy property names and diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py -index 031555f..e0368b8 100644 +index 199e351..456c64e 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py -@@ -1180,7 +1180,7 @@ object_data = { - 'HostMapping': '1.0-1a3390a696792a552ab7bd31a77ba9ac', - 'HVSpec': '1.1-6b4f7c0f688cbd03e24142a44eb9010d', - 'ImageMeta': '1.7-642d1b2eb3e880a367f37d72dd76162d', -- 'ImageMetaProps': '1.7-f12fc4cf3e25d616f69a66fb9d2a7aa6', -+ 'ImageMetaProps': '1.7-716042e9e80ea16890f475200940d6f9', - 'Instance': '2.0-ff56804dce87d81d9a04834d4bd1e3d2', - # NOTE(danms): Reviewers: do not approve changes to the Instance1 - # object schema. It is frozen for Liberty and will be removed in +@@ -1126,7 +1126,7 @@ object_data = { + 'HyperVLiveMigrateData': '1.0-0b868dd6228a09c3f3e47016dddf6a1c', + 'HVSpec': '1.2-db672e73304da86139086d003f3977e7', + 'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d', +- 'ImageMetaProps': '1.12-6a132dee47931447bf86c03c7006d96c', ++ 'ImageMetaProps': '1.12-605af4f965158bd805edb60d77678e9f', + 'Instance': '2.1-416fdd0dfc33dfa12ff2cfdd8cc32e17', + 'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914', + 'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33', diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py -index a8c653a..940497c 100644 +index e2a6902..8d8e47f 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py -@@ -799,6 +799,7 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -800,6 +800,7 @@ class IronicDriverTestCase(test.NoDBTestCase): result = self.driver.macs_for_instance(instance) self.assertIsNone(result) - + + @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options') @mock.patch.object(objects.Instance, 'save') @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') @mock.patch.object(FAKE_CLIENT, 'node') -@@ -807,7 +808,7 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -808,7 +809,7 @@ class IronicDriverTestCase(test.NoDBTestCase): @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs') @mock.patch.object(ironic_driver.IronicDriver, '_start_firewall') def _test_spawn(self, mock_sf, mock_pvifs, mock_adf, mock_wait_active, @@ -44,18 +44,19 @@ index a8c653a..940497c 100644 node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) -@@ -845,6 +846,7 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -846,6 +847,7 @@ class IronicDriverTestCase(test.NoDBTestCase): fake_looping_call.start.assert_called_once_with( interval=CONF.ironic.api_retry_interval) fake_looping_call.wait.assert_called_once_with() + mock_sb_options.assert_called_once_with(self.ctx, instance, node_uuid) - + @mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive') @mock.patch.object(configdrive, 'required_by') -@@ -897,14 +899,62 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -898,13 +900,61 @@ class IronicDriverTestCase(test.NoDBTestCase): self.driver.spawn, self.ctx, instance, None, [], None) - mock_destroy.assert_called_once_with(self.ctx, instance, None) - + self.assertEqual(0, mock_destroy.call_count) + +- def _test_add_driver_fields(self, mock_update=None, mock_call=None): + @mock.patch.object(FAKE_CLIENT, 'node') + def test__get_switch_boot_options(self, mock_node): + node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' @@ -96,21 +97,18 @@ index a8c653a..940497c 100644 + + @mock.patch.object(ironic_driver.IronicDriver, + "_get_deploy_config_options") - @mock.patch.object(FAKE_CLIENT.node, 'update') -- def test__add_driver_fields_good(self, mock_update): -+ def test__add_driver_fields_good(self, mock_update, -+ mock_get_depl_conf_opts): ++ def _test_add_driver_fields(self, mock_get_depl_conf_opts, ++ mock_update=None, mock_call=None): node = ironic_utils.get_test_node(driver='fake') - instance = fake_instance.fake_instance_obj(self.ctx, - node=node.uuid) -- image_meta = ironic_utils.get_test_image_meta_object() + instance = fake_instance.fake_instance_obj( + self.ctx, + node=node.uuid, + expected_attrs=('metadata',)) + image_meta = ironic_utils.get_test_image_meta() flavor = ironic_utils.get_test_flavor() + -+ image_meta = ironic_utils.get_test_image_meta_object() + mock_get_depl_conf_opts.return_value = {'foo': 'bar123'} + instance['metadata']['driver_actions'] = 'test_driver_actions' + @@ -119,7 +117,7 @@ index a8c653a..940497c 100644 expected_patch = [{'path': '/instance_info/image_source', 'op': 'add', 'value': image_meta.id}, {'path': '/instance_info/root_gb', 'op': 'add', -@@ -920,21 +970,96 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -920,7 +970,14 @@ class IronicDriverTestCase(test.NoDBTestCase): {'path': '/instance_info/local_gb', 'op': 'add', 'value': str(node.properties.get('local_gb', 0))}, {'path': '/instance_uuid', 'op': 'add', @@ -132,9 +130,10 @@ index a8c653a..940497c 100644 + 'op': 'add', + 'value': 'test_driver_actions'}, + ] - mock_update.assert_called_once_with(node.uuid, expected_patch) - - @mock.patch.object(FAKE_CLIENT.node, 'update') + + if mock_call is not None: + # assert call() is invoked with retry_on_conflict False to +@@ -943,14 +1000,82 @@ class IronicDriverTestCase(test.NoDBTestCase): def test__add_driver_fields_fail(self, mock_update): mock_update.side_effect = ironic_exception.BadRequest() node = ironic_utils.get_test_node(driver='fake') @@ -144,16 +143,16 @@ index a8c653a..940497c 100644 + self.ctx, + node=node.uuid, + expected_attrs=('metadata',)) - image_meta = ironic_utils.get_test_image_meta_object() + image_meta = ironic_utils.get_test_image_meta() flavor = ironic_utils.get_test_flavor() self.assertRaises(exception.InstanceDeployFailure, self.driver._add_driver_fields, node, instance, image_meta, flavor) - + + def test__get_deploy_config_options_all_present(self): + node = ironic_utils.get_test_node( + driver='fake', driver_info={'deploy_config': "node-conf"}) -+ image_meta = ironic_utils.get_test_image_meta_object( ++ image_meta = ironic_utils.get_test_image_meta( + deploy_config="image-conf") + instance = fake_instance.fake_instance_obj( + self.ctx, node=node.uuid, expected_attrs=('metadata',), @@ -178,7 +177,7 @@ index a8c653a..940497c 100644 + "image": "previous_image_conf", + }} + ) -+ image_meta = ironic_utils.get_test_image_meta_object( ++ image_meta = ironic_utils.get_test_image_meta( + deploy_config="image-conf") + instance = fake_instance.fake_instance_obj( + self.ctx, node=node.uuid, expected_attrs=('metadata',)) @@ -193,7 +192,7 @@ index a8c653a..940497c 100644 + + def test__get_deploy_config_options_some_present(self): + node = ironic_utils.get_test_node(driver='fake') -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + instance = fake_instance.fake_instance_obj( + self.ctx, node=node.uuid, expected_attrs=('metadata',), + metadata={'deploy_config': "instance-conf"}) @@ -206,7 +205,7 @@ index a8c653a..940497c 100644 + + def test__get_deploy_config_options_none_present(self): + node = ironic_utils.get_test_node(driver='fake') -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + instance = fake_instance.fake_instance_obj( + self.ctx, node=node.uuid, expected_attrs=('metadata',)) + @@ -216,23 +215,10 @@ index a8c653a..940497c 100644 + expected = {} + self.assertEqual(expected, opts) + - @mock.patch.object(FAKE_CLIENT.node, 'update') - def test__cleanup_deploy_good_with_flavor(self, mock_update): - node = ironic_utils.get_test_node(driver='fake', -@@ -983,8 +1108,10 @@ class IronicDriverTestCase(test.NoDBTestCase): - node = ironic_utils.get_test_node(driver='fake', - instance_uuid=self.instance_uuid) - flavor = ironic_utils.get_test_flavor(extra_specs={}) -- instance = fake_instance.fake_instance_obj(self.ctx, -- node=node.uuid) -+ instance = fake_instance.fake_instance_obj( -+ self.ctx, -+ node=node.uuid, -+ expected_attrs=('metadata',)) - instance.flavor = flavor - self.assertRaises(exception.InstanceTerminationFailure, - self.driver._cleanup_deploy, -@@ -998,7 +1125,8 @@ class IronicDriverTestCase(test.NoDBTestCase): + @mock.patch.object(configdrive, 'required_by') + @mock.patch.object(FAKE_CLIENT, 'node') + def test_spawn_node_driver_validation_fail(self, mock_node, +@@ -959,7 +1084,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor() @@ -240,9 +226,9 @@ index a8c653a..940497c 100644 + instance = fake_instance.fake_instance_obj( + self.ctx, node=node_uuid, expected_attrs=('metadata',)) instance.flavor = flavor - + mock_node.validate.return_value = ironic_utils.get_test_validation( -@@ -1023,7 +1151,8 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -985,7 +1111,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor() @@ -252,7 +238,27 @@ index a8c653a..940497c 100644 instance.flavor = flavor mock_node.get.return_value = node mock_node.validate.return_value = ironic_utils.get_test_validation() -@@ -1053,7 +1182,8 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1018,7 +1145,8 @@ class IronicDriverTestCase(test.NoDBTestCase): + node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' + node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) + flavor = ironic_utils.get_test_flavor() +- instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid) ++ instance = fake_instance.fake_instance_obj( ++ self.ctx, node=node_uuid, expected_attrs=('metadata',)) + instance.flavor = flavor + mock_node.get.return_value = node + mock_node.validate.return_value = ironic_utils.get_test_validation() +@@ -1034,8 +1162,7 @@ class IronicDriverTestCase(test.NoDBTestCase): + mock_node.get.assert_called_once_with( + node_uuid, fields=ironic_driver._NODE_FIELDS) + mock_node.validate.assert_called_once_with(node_uuid) +- mock_cleanup_deploy.assert_called_with(self.ctx, node, instance, None, +- flavor=flavor) ++ mock_cleanup_deploy.assert_called_with(node, instance, None) + + @mock.patch.object(configdrive, 'required_by') + @mock.patch.object(FAKE_CLIENT, 'node') +@@ -1049,7 +1176,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor() @@ -261,8 +267,8 @@ index a8c653a..940497c 100644 + self.ctx, node=node_uuid, expected_attrs=('metadata',)) instance.flavor = flavor image_meta = ironic_utils.get_test_image_meta() - -@@ -1082,7 +1212,8 @@ class IronicDriverTestCase(test.NoDBTestCase): + +@@ -1077,7 +1205,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor() @@ -271,8 +277,8 @@ index a8c653a..940497c 100644 + self.ctx, node=node_uuid, expected_attrs=('metadata',)) instance.flavor = flavor image_meta = ironic_utils.get_test_image_meta() - -@@ -1113,7 +1244,8 @@ class IronicDriverTestCase(test.NoDBTestCase): + +@@ -1107,7 +1236,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor() @@ -281,8 +287,8 @@ index a8c653a..940497c 100644 + self.ctx, node=node_uuid, expected_attrs=('metadata',)) instance.flavor = flavor image_meta = ironic_utils.get_test_image_meta() - -@@ -1146,7 +1278,8 @@ class IronicDriverTestCase(test.NoDBTestCase): + +@@ -1139,7 +1269,8 @@ class IronicDriverTestCase(test.NoDBTestCase): node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid) flavor = ironic_utils.get_test_flavor(ephemeral_gb=1) @@ -292,10 +298,10 @@ index a8c653a..940497c 100644 instance.flavor = flavor mock_node.get_by_instance_uuid.return_value = node mock_node.set_provision_state.return_value = mock.MagicMock() -@@ -1541,15 +1674,16 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1529,15 +1660,16 @@ class IronicDriverTestCase(test.NoDBTestCase): self.driver.refresh_instance_security_rules(fake_group) mock_risr.assert_called_once_with(fake_group) - + + @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options') @mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active') @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall') @@ -311,10 +317,10 @@ index a8c653a..940497c 100644 node_uuid = uuidutils.generate_uuid() node = ironic_utils.get_test_node(uuid=node_uuid, instance_uuid=self.instance_uuid, -@@ -1560,10 +1694,12 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1548,10 +1680,12 @@ class IronicDriverTestCase(test.NoDBTestCase): flavor_id = 5 flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal') - + - instance = fake_instance.fake_instance_obj(self.ctx, - uuid=self.instance_uuid, - node=node_uuid, @@ -326,18 +332,18 @@ index a8c653a..940497c 100644 + instance_type_id=flavor_id, + expected_attrs=('metadata',)) instance.flavor = flavor - + fake_looping_call = FakeLoopingCall() -@@ -1589,6 +1725,7 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1575,6 +1709,7 @@ class IronicDriverTestCase(test.NoDBTestCase): fake_looping_call.start.assert_called_once_with( interval=CONF.ironic.api_retry_interval) fake_looping_call.wait.assert_called_once_with() + mock_sb_options.assert_called_once_with(self.ctx, instance, node_uuid) - + def test_rebuild_preserve_ephemeral(self): self._test_rebuild(preserve=True) -@@ -1598,7 +1735,7 @@ class IronicDriverTestCase(test.NoDBTestCase): - +@@ -1584,7 +1719,7 @@ class IronicDriverTestCase(test.NoDBTestCase): + @mock.patch.object(FAKE_CLIENT.node, 'set_provision_state') @mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields') - @mock.patch.object(FAKE_CLIENT.node, 'get') @@ -345,10 +351,10 @@ index a8c653a..940497c 100644 @mock.patch.object(objects.Instance, 'save') def test_rebuild_failures(self, mock_save, mock_get, mock_driver_fields, mock_set_pstate): -@@ -1612,10 +1749,12 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1598,10 +1733,12 @@ class IronicDriverTestCase(test.NoDBTestCase): flavor_id = 5 flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal') - + - instance = fake_instance.fake_instance_obj(self.ctx, - uuid=self.instance_uuid, - node=node_uuid, @@ -360,12 +366,12 @@ index a8c653a..940497c 100644 + instance_type_id=flavor_id, + expected_attrs=('metadata',)) instance.flavor = flavor - + exceptions = [ -@@ -1631,6 +1770,316 @@ class IronicDriverTestCase(test.NoDBTestCase): +@@ -1617,6 +1754,316 @@ class IronicDriverTestCase(test.NoDBTestCase): injected_files=None, admin_password=None, bdms=None, detach_block_devices=None, attach_block_devices=None) - + + @mock.patch.object(ironic_driver.IronicDriver, '_do_rebuild') + @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options') + @mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active') @@ -386,7 +392,7 @@ index a8c653a..940497c 100644 + instance_info={'multiboot': True}) + mock_get.return_value = node + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + + flavor_id = 5 + flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal') @@ -437,7 +443,7 @@ index a8c653a..940497c 100644 + instance_info={'multiboot': True}) + mock_get.return_value = mock_get_by_instance.return_value = node + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + mock_image_meta_from_dict.return_value = image_meta + + flavor_id = 5 @@ -469,7 +475,7 @@ index a8c653a..940497c 100644 + instance_type_id=5, + instance_info={'multiboot': True}) + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + flavor_id = 5 + instance = fake_instance.fake_instance_obj( + self.ctx, @@ -544,7 +550,7 @@ index a8c653a..940497c 100644 + 'image_source': 'original_image', + }) + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + flavor_id = 5 + instance = fake_instance.fake_instance_obj( + self.ctx, @@ -582,7 +588,7 @@ index a8c653a..940497c 100644 + 'image_source': 'original_image', + }) + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + flavor_id = 5 + instance = fake_instance.fake_instance_obj( + self.ctx, @@ -618,7 +624,7 @@ index a8c653a..940497c 100644 + 'image_source': 'original_image', + }) + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + flavor_id = 5 + instance = fake_instance.fake_instance_obj( + self.ctx, @@ -655,7 +661,7 @@ index a8c653a..940497c 100644 + 'image_source': 'original_image', + }) + -+ image_meta = ironic_utils.get_test_image_meta_object() ++ image_meta = ironic_utils.get_test_image_meta() + flavor_id = 5 + instance = fake_instance.fake_instance_obj( + self.ctx, @@ -676,11 +682,11 @@ index a8c653a..940497c 100644 + instance.metadata['switch_boot_error']) + mock_save.assert_called_once_with() + - - @mock.patch.object(instance_metadata, 'InstanceMetadata') - @mock.patch.object(configdrive, 'ConfigDriveBuilder') + @mock.patch.object(FAKE_CLIENT.node, 'get') + def _test_network_binding_host_id(self, is_neutron, mock_get): + node_uuid = uuidutils.generate_uuid() diff --git a/nova/tests/unit/virt/ironic/utils.py b/nova/tests/unit/virt/ironic/utils.py -index 0e67919..66eede3 100644 +index d25930e..3a37e2e 100644 --- a/nova/tests/unit/virt/ironic/utils.py +++ b/nova/tests/unit/virt/ironic/utils.py @@ -39,7 +39,7 @@ def get_test_node(**kw): @@ -692,34 +698,46 @@ index 0e67919..66eede3 100644 'driver': kw.get('driver', 'fake'), 'driver_info': kw.get('driver_info', {}), 'properties': kw.get('properties', {}), -@@ -91,7 +91,11 @@ def get_test_flavor(**kw): - - +@@ -92,8 +92,13 @@ def get_test_flavor(**kw): + + def get_test_image_meta(**kw): -- return {'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc')} -+ return {'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc'), -+ 'properties': { -+ 'deploy_config': kw.get('deploy_config', ''), -+ 'driver_actions': kw.get('driver_actions', ''), -+ }} - - - def get_test_image_meta_object(**kw): -@@ -134,6 +138,9 @@ class FakeNodeClient(object): +- return objects.ImageMeta.from_dict( +- {'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc')}) ++ return objects.ImageMeta.from_dict({ ++ 'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc'), ++ 'properties': { ++ 'deploy_config': kw.get('deploy_config', ''), ++ 'driver_actions': kw.get('driver_actions', '') ++ } ++ }) + + + class FakePortClient(object): +@@ -131,6 +136,9 @@ class FakeNodeClient(object): def validate(self, node_uuid): pass - + + def vendor_passthru(self, node_uuid, method, args): + pass + - + class FakeClient(object): - + diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py -index 194221e..062f3d7 100644 +index 9cbf4d7..6b5eed9 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py -@@ -391,6 +391,17 @@ class IronicDriver(virt_driver.ComputeDriver): +@@ -76,7 +76,7 @@ _UNPROVISION_STATES = (ironic_states.ACTIVE, ironic_states.DEPLOYFAIL, + + _NODE_FIELDS = ('uuid', 'power_state', 'target_power_state', 'provision_state', + 'target_provision_state', 'last_error', 'maintenance', +- 'properties', 'instance_uuid') ++ 'properties', 'instance_uuid', 'instance_info', 'driver_info') + + + def map_power_state(state): +@@ -357,6 +357,17 @@ class IronicDriver(virt_driver.ComputeDriver): # Associate the node with an instance patch.append({'path': '/instance_uuid', 'op': 'add', 'value': instance.uuid}) @@ -735,23 +753,33 @@ index 194221e..062f3d7 100644 + 'value': instance.metadata.get('driver_actions', '')}) + try: - self.ironicclient.call('node.update', node.uuid, patch) - except ironic.exc.BadRequest: -@@ -400,6 +411,12 @@ class IronicDriver(virt_driver.ComputeDriver): + # FIXME(lucasagomes): The "retry_on_conflict" parameter was added + # to basically causes the deployment to fail faster in case the +@@ -371,6 +382,12 @@ class IronicDriver(virt_driver.ComputeDriver): LOG.error(msg) raise exception.InstanceDeployFailure(msg) - + + def _update_driver_fields_after_switch_boot(self, context, node, + instance, image_meta): + patch = [{'path': '/instance_info/image_source', 'op': 'add', -+ 'value': image_meta.id}] ++ 'value': image_meta.id}] + self.ironicclient.call('node.update', node.uuid, patch) + - def _cleanup_deploy(self, context, node, instance, network_info, - flavor=None): - if flavor is None: -@@ -807,9 +824,19 @@ class IronicDriver(virt_driver.ComputeDriver): - + def _cleanup_deploy(self, node, instance, network_info): + self._unplug_vifs(node, instance, network_info) + self._stop_firewall(instance, network_info) +@@ -744,8 +761,7 @@ class IronicDriver(virt_driver.ComputeDriver): + msg = (_LE("Failed to build configdrive: %s") % + six.text_type(e)) + LOG.error(msg, instance=instance) +- self._cleanup_deploy(context, node, instance, network_info, +- flavor=flavor) ++ self._cleanup_deploy(node, instance, network_info) + + LOG.info(_LI("Config drive for instance %(instance)s on " + "baremetal node %(node)s created."), +@@ -753,9 +769,19 @@ class IronicDriver(virt_driver.ComputeDriver): + # trigger the node deploy try: - self.ironicclient.call("node.set_provision_state", node_uuid, @@ -773,10 +801,10 @@ index 194221e..062f3d7 100644 except Exception as e: with excutils.save_and_reraise_exception(): msg = (_LE("Failed to request Ironic to provision instance " -@@ -834,6 +861,17 @@ class IronicDriver(virt_driver.ComputeDriver): +@@ -777,6 +803,17 @@ class IronicDriver(virt_driver.ComputeDriver): + "baremetal node %(node)s."), {'instance': instance.uuid, 'node': node_uuid}) - self.destroy(context, instance, network_info) + else: + self._get_switch_boot_options(context, instance, node_uuid) + @@ -788,15 +816,15 @@ index 194221e..062f3d7 100644 + available_images = [img['image_name'] for img in + multiboot_meta.get('elements', [])] + instance.metadata['available_images'] = str(available_images) - - def _unprovision(self, ironicclient, instance, node): + + def _unprovision(self, instance, node): """This method is called from destroy() to unprovision -@@ -1188,16 +1226,102 @@ class IronicDriver(virt_driver.ComputeDriver): +@@ -1113,16 +1150,102 @@ class IronicDriver(virt_driver.ComputeDriver): instance.task_state = task_states.REBUILD_SPAWNING instance.save(expected_task_state=[task_states.REBUILDING]) - + - node_uuid = instance.node -- node = self.ironicclient.call("node.get", node_uuid) +- node = self._get_node(node_uuid) + # NOTE(oberezovskyi): Required to get real node uuid assigned to nova + # instance. Workaround after + # Change-Id: I0233f964d8f294f0ffd9edcb16b1aaf93486177f @@ -830,11 +858,11 @@ index 194221e..062f3d7 100644 + recreate=recreate, + block_device_info=block_device_info, + preserve_ephemeral=preserve_ephemeral) - + - self._add_driver_fields(node, instance, image_meta, instance.flavor, - preserve_ephemeral) + self._get_switch_boot_options(context, instance, node.uuid) - + - # Trigger the node rebuild/redeploy. + def _do_switch_boot_device(self, context, ironicclient, node, instance, + image_meta): @@ -881,7 +909,7 @@ index 194221e..062f3d7 100644 + else: + raise exception.InvalidMetadata( + reason="To trigger switch boot device flow, both 'sb_user' " -+ "and 'sb_key' metadata params are required. To "s ++ "and 'sb_key' metadata params are required. To " + "trigger a standard rebuild flow, use " + "force_rebuild=True metadata flag.") + @@ -901,11 +929,10 @@ index 194221e..062f3d7 100644 except (exception.NovaException, # Retry failed ironic.exc.InternalServerError, # Validations ironic.exc.BadRequest) as e: # Maintenance -@@ -1213,3 +1337,22 @@ class IronicDriver(virt_driver.ComputeDriver): - instance) +@@ -1138,6 +1261,25 @@ class IronicDriver(virt_driver.ComputeDriver): timer.start(interval=CONF.ironic.api_retry_interval).wait() LOG.info(_LI('Instance was successfully rebuilt'), instance=instance) -+ + + def _get_deploy_config_options(self, node, instance, image_meta): + # Taking into account previous options, if any. This is to support + # rebuild flow where the user might or might not pass deploy_config @@ -924,3 +951,7 @@ index 194221e..062f3d7 100644 + # Override previous by current. + res.update(curr_options) + return res ++ + def network_binding_host_id(self, context, instance): + """Get host ID to associate with network ports. +