Support stable/mitaka

Add support for stable/mitaka including new patches for ironic
and nova.

Change-Id: Ie566dd22aaddc30858438a98767ef962182e97d9
This commit is contained in:
Doug Szumski 2016-06-02 03:58:40 +01:00 committed by Bogun Dmitriy
parent 52d26cafdd
commit 65f5f65984
4 changed files with 249 additions and 195 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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.