Browse Source

Fixed compatibiliy with stable/liberty

Change-Id: I2e79db9816e7cd7c55318dcb20c72bd7671c00d2
tags/liberty-eol^0
Mark Goddard 3 years ago
parent
commit
210a6383e4

+ 5
- 5
bareon_ironic/bareon.py View File

@@ -14,7 +14,7 @@
14 14
 # limitations under the License.
15 15
 
16 16
 from ironic.drivers import base
17
-from ironic.drivers.modules import discoverd
17
+from ironic.drivers.modules import inspector
18 18
 from ironic.drivers.modules import ipmitool
19 19
 from ironic.drivers.modules import ssh
20 20
 
@@ -39,7 +39,7 @@ class BareonSwiftAndIPMIToolDriver(base.BaseDriver):
39 39
         self.deploy = bareon_swift.BareonSwiftDeploy()
40 40
         self.management = ipmitool.IPMIManagement()
41 41
         self.vendor = bareon_swift.BareonSwiftVendor()
42
-        self.inspect = discoverd.DiscoverdInspect.create_if_enabled(
42
+        self.inspect = inspector.Inspector.create_if_enabled(
43 43
             'BareonSwiftAndIPMIToolDriver')
44 44
 
45 45
 
@@ -61,7 +61,7 @@ class BareonSwiftAndSSHDriver(base.BaseDriver):
61 61
         self.deploy = bareon_swift.BareonSwiftDeploy()
62 62
         self.management = ssh.SSHManagement()
63 63
         self.vendor = bareon_swift.BareonSwiftVendor()
64
-        self.inspect = discoverd.DiscoverdInspect.create_if_enabled(
64
+        self.inspect = inspector.Inspector.create_if_enabled(
65 65
             'BareonSwiftAndSSHDriver')
66 66
 
67 67
 
@@ -82,7 +82,7 @@ class BareonRsyncAndIPMIToolDriver(base.BaseDriver):
82 82
         self.deploy = bareon_rsync.BareonRsyncDeploy()
83 83
         self.management = ipmitool.IPMIManagement()
84 84
         self.vendor = bareon_rsync.BareonRsyncVendor()
85
-        self.inspect = discoverd.DiscoverdInspect.create_if_enabled(
85
+        self.inspect = inspector.Inspector.create_if_enabled(
86 86
             'BareonRsyncAndIPMIToolDriver')
87 87
 
88 88
 
@@ -104,5 +104,5 @@ class BareonRsyncAndSSHDriver(base.BaseDriver):
104 104
         self.deploy = bareon_rsync.BareonRsyncDeploy()
105 105
         self.management = ssh.SSHManagement()
106 106
         self.vendor = bareon_rsync.BareonRsyncVendor()
107
-        self.inspect = discoverd.DiscoverdInspect.create_if_enabled(
107
+        self.inspect = inspector.Inspector.create_if_enabled(
108 108
             'BareonRsyncAndSSHDriver')

+ 3
- 3
bareon_ironic/modules/bareon_base.py View File

@@ -27,6 +27,9 @@ import six
27 27
 from oslo_concurrency import processutils
28 28
 from oslo_config import cfg
29 29
 from oslo_utils import excutils
30
+from oslo_utils import fileutils
31
+from oslo_log import log
32
+from oslo_service import loopingcall
30 33
 
31 34
 from ironic.common import boot_devices
32 35
 from ironic.common import dhcp_factory
@@ -45,9 +48,6 @@ from ironic.drivers import base
45 48
 from ironic.drivers.modules import deploy_utils
46 49
 from ironic.drivers.modules import image_cache
47 50
 from ironic.objects import node as db_node
48
-from ironic.openstack.common import fileutils
49
-from ironic.openstack.common import log
50
-from ironic.openstack.common import loopingcall
51 51
 
52 52
 from bareon_ironic.modules import bareon_exception
53 53
 from bareon_ironic.modules import bareon_utils

+ 1
- 1
bareon_ironic/modules/bareon_utils.py View File

@@ -23,6 +23,7 @@ import tempfile
23 23
 import six
24 24
 from oslo_concurrency import processutils
25 25
 from oslo_config import cfg
26
+from oslo_log import log as logging
26 27
 from oslo_utils import strutils
27 28
 
28 29
 from ironic.common import dhcp_factory
@@ -30,7 +31,6 @@ from ironic.common import exception
30 31
 from ironic.common import keystone
31 32
 from ironic.common import utils
32 33
 from ironic.common.i18n import _, _LW
33
-from ironic.openstack.common import log as logging
34 34
 
35 35
 LOG = logging.getLogger(__name__)
36 36
 CONF = cfg.CONF

+ 1
- 1
bareon_ironic/modules/resources/actions.py View File

@@ -23,9 +23,9 @@ import tempfile
23 23
 
24 24
 from oslo_concurrency import processutils
25 25
 from oslo_config import cfg
26
+from oslo_log import log
26 27
 
27 28
 from ironic.common import exception
28
-from ironic.openstack.common import log
29 29
 
30 30
 from bareon_ironic.modules import bareon_utils
31 31
 from bareon_ironic.modules.resources import resources

+ 1
- 1
bareon_ironic/modules/resources/image_service.py View File

@@ -22,6 +22,7 @@ import uuid
22 22
 
23 23
 from oslo_config import cfg
24 24
 from oslo_concurrency import processutils
25
+from oslo_log import log as logging
25 26
 from oslo_utils import uuidutils
26 27
 import requests
27 28
 import six
@@ -29,7 +30,6 @@ import six.moves.urllib.parse as urlparse
29 30
 
30 31
 from ironic.common import exception
31 32
 from ironic.common.i18n import _
32
-from ironic.openstack.common import log as logging
33 33
 from ironic.common import image_service
34 34
 from ironic.common import keystone
35 35
 from ironic.common import utils

+ 2
- 2
bareon_ironic/modules/resources/resources.py View File

@@ -19,14 +19,14 @@ import os
19 19
 
20 20
 from oslo_config import cfg
21 21
 from oslo_serialization import jsonutils
22
+from oslo_utils import fileutils
23
+from oslo_log import log
22 24
 from six.moves.urllib import parse
23 25
 
24 26
 from ironic.common import exception
25 27
 from ironic.common import utils
26 28
 from ironic.common.i18n import _
27 29
 from ironic.drivers.modules import image_cache
28
-from ironic.openstack.common import fileutils
29
-from ironic.openstack.common import log
30 30
 
31 31
 from bareon_ironic.modules import bareon_exception
32 32
 from bareon_ironic.modules import bareon_utils

+ 1
- 2
bareon_ironic/modules/resources/rsync.py View File

@@ -17,8 +17,7 @@
17 17
 import os
18 18
 
19 19
 from oslo_config import cfg
20
-
21
-from ironic.openstack.common import log
20
+from oslo_log import log
22 21
 
23 22
 rsync_opts = [
24 23
     cfg.StrOpt('rsync_server',

+ 0
- 311
patches/patch-ironic-stable-kilo View File

@@ -1,311 +0,0 @@
1
-diff --git a/ironic/api/config.py b/ironic/api/config.py
2
-index 38938c1..18c82fd 100644
3
---- a/ironic/api/config.py
4
-+++ b/ironic/api/config.py
5
-@@ -31,7 +31,8 @@ app = {
6
-         '/',
7
-         '/v1',
8
-         '/v1/drivers/[a-z_]*/vendor_passthru/lookup',
9
--        '/v1/nodes/[a-z0-9\-]+/vendor_passthru/heartbeat'
10
-+        '/v1/nodes/[a-z0-9\-]+/vendor_passthru/heartbeat',
11
-+        '/v1/nodes/[a-z0-9\-]+/vendor_passthru/pass_deploy_info',
12
-     ],
13
- }
14
- 
15
-diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
16
-index ce48e09..0df9a3f 100644
17
---- a/ironic/api/controllers/v1/node.py
18
-+++ b/ironic/api/controllers/v1/node.py
19
-@@ -381,13 +381,17 @@ class NodeStatesController(rest.RestController):
20
-         rpc_node = api_utils.get_rpc_node(node_ident)
21
-         topic = pecan.request.rpcapi.get_topic_for(rpc_node)
22
- 
23
-+        driver = api_utils.get_driver_by_name(rpc_node.driver)
24
-+        driver_can_terminate = (driver and
25
-+                                driver.deploy.can_terminate_deployment)
26
-         # Normally, we let the task manager recognize and deal with
27
-         # NodeLocked exceptions. However, that isn't done until the RPC calls
28
-         # below. In order to main backward compatibility with our API HTTP
29
-         # response codes, we have this check here to deal with cases where
30
-         # a node is already being operated on (DEPLOYING or such) and we
31
-         # want to continue returning 409. Without it, we'd return 400.
32
--        if rpc_node.reservation:
33
-+        if (not (target == ir_states.DELETED and driver_can_terminate) and
34
-+                rpc_node.reservation):
35
-             raise exception.NodeLocked(node=rpc_node.uuid,
36
-                                        host=rpc_node.reservation)
37
- 
38
-diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py
39
-index 6132e12..91ca0f2 100644
40
---- a/ironic/api/controllers/v1/utils.py
41
-+++ b/ironic/api/controllers/v1/utils.py
42
-@@ -19,6 +19,7 @@ from oslo_utils import uuidutils
43
- import pecan
44
- import wsme
45
- 
46
-+from ironic.common import driver_factory
47
- from ironic.common import exception
48
- from ironic.common.i18n import _
49
- from ironic.common import utils
50
-@@ -102,3 +103,12 @@ def is_valid_node_name(name):
51
-     :returns: True if the name is valid, False otherwise.
52
-     """
53
-     return utils.is_hostname_safe(name) and (not uuidutils.is_uuid_like(name))
54
-+
55
-+
56
-+def get_driver_by_name(driver_name):
57
-+    _driver_factory = driver_factory.DriverFactory()
58
-+    try:
59
-+        driver = _driver_factory[driver_name]
60
-+        return driver.obj
61
-+    except Exception:
62
-+        return None
63
-diff --git a/ironic/common/context.py b/ironic/common/context.py
64
-index aaeffb3..d167e26 100644
65
---- a/ironic/common/context.py
66
-+++ b/ironic/common/context.py
67
-@@ -63,5 +63,4 @@ class RequestContext(context.RequestContext):
68
-     @classmethod
69
-     def from_dict(cls, values):
70
-         values.pop('user', None)
71
--        values.pop('tenant', None)
72
-         return cls(**values)
73
-diff --git a/ironic/common/states.py b/ironic/common/states.py
74
-index 7ebd052..df30c2f 100644
75
---- a/ironic/common/states.py
76
-+++ b/ironic/common/states.py
77
-@@ -218,6 +218,9 @@ machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers)
78
- # A deployment may fail
79
- machine.add_transition(DEPLOYING, DEPLOYFAIL, 'fail')
80
- 
81
-+# A deployment may be terminated
82
-+machine.add_transition(DEPLOYING, DELETING, 'delete')
83
-+
84
- # A failed deployment may be retried
85
- # ironic/conductor/manager.py:do_node_deploy()
86
- machine.add_transition(DEPLOYFAIL, DEPLOYING, 'rebuild')
87
-diff --git a/ironic/common/swift.py b/ironic/common/swift.py
88
-index a4444e2..4cc36c4 100644
89
---- a/ironic/common/swift.py
90
-+++ b/ironic/common/swift.py
91
-@@ -23,6 +23,7 @@ from swiftclient import utils as swift_utils
92
- from ironic.common import exception
93
- from ironic.common.i18n import _
94
- from ironic.common import keystone
95
-+from ironic.common import utils
96
- from ironic.openstack.common import log as logging
97
- 
98
- swift_opts = [
99
-@@ -36,6 +37,13 @@ swift_opts = [
100
- CONF = cfg.CONF
101
- CONF.register_opts(swift_opts, group='swift')
102
- 
103
-+CONF.import_opt('swift_endpoint_url',
104
-+                'ironic.common.glance_service.v2.image_service',
105
-+                group='glance')
106
-+CONF.import_opt('swift_api_version',
107
-+                'ironic.common.glance_service.v2.image_service',
108
-+                group='glance')
109
-+
110
- CONF.import_opt('admin_user', 'keystonemiddleware.auth_token',
111
-                 group='keystone_authtoken')
112
- CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token',
113
-@@ -60,7 +68,9 @@ class SwiftAPI(object):
114
-                  tenant_name=CONF.keystone_authtoken.admin_tenant_name,
115
-                  key=CONF.keystone_authtoken.admin_password,
116
-                  auth_url=CONF.keystone_authtoken.auth_uri,
117
--                 auth_version=CONF.keystone_authtoken.auth_version):
118
-+                 auth_version=CONF.keystone_authtoken.auth_version,
119
-+                 preauthtoken=None,
120
-+                 preauthtenant=None):
121
-         """Constructor for creating a SwiftAPI object.
122
- 
123
-         :param user: the name of the user for Swift account
124
-@@ -68,15 +78,40 @@ class SwiftAPI(object):
125
-         :param key: the 'password' or key to authenticate with
126
-         :param auth_url: the url for authentication
127
-         :param auth_version: the version of api to use for authentication
128
-+        :param preauthtoken: authentication token (if you have already
129
-+                     authenticated) note authurl/user/key/tenant_name
130
-+                     are not required when specifying preauthtoken
131
-+        :param preauthtenant a tenant that will be accessed using the
132
-+                     preauthtoken
133
-         """
134
--        auth_url = keystone.get_keystone_url(auth_url, auth_version)
135
--        params = {'retries': CONF.swift.swift_max_retries,
136
--                  'insecure': CONF.keystone_authtoken.insecure,
137
--                  'user': user,
138
--                  'tenant_name': tenant_name,
139
--                  'key': key,
140
--                  'authurl': auth_url,
141
--                  'auth_version': auth_version}
142
-+        params = {
143
-+            'retries': CONF.swift.swift_max_retries,
144
-+            'insecure': CONF.keystone_authtoken.insecure
145
-+        }
146
-+
147
-+        if preauthtoken:
148
-+            # Determining swift url for the user's tenant account.
149
-+            tenant_id = utils.get_tenant_id(tenant_name=preauthtenant)
150
-+            url = "{endpoint}/{api_ver}/AUTH_{tenant}".format(
151
-+                endpoint=CONF.glance.swift_endpoint_url,
152
-+                api_ver=CONF.glance.swift_api_version,
153
-+                tenant=tenant_id
154
-+            )
155
-+            # authurl/user/key/tenant_name are not required when specifying
156
-+            # preauthtoken
157
-+            params.update({
158
-+                'preauthtoken': preauthtoken,
159
-+                'preauthurl': url
160
-+            })
161
-+        else:
162
-+            auth_url = keystone.get_keystone_url(auth_url, auth_version)
163
-+            params.update({
164
-+                'user': user,
165
-+                'tenant_name': tenant_name,
166
-+                'key': key,
167
-+                'authurl': auth_url,
168
-+                'auth_version': auth_version
169
-+            })
170
- 
171
-         self.connection = swift_client.Connection(**params)
172
- 
173
-@@ -128,8 +163,8 @@ class SwiftAPI(object):
174
-             operation = _("head account")
175
-             raise exception.SwiftOperationError(operation=operation,
176
-                                                 error=e)
177
--
178
--        storage_url, token = self.connection.get_auth()
179
-+        storage_url = (self.connection.os_options.get('object_storage_url') or
180
-+                       self.connection.get_auth()[0])
181
-         parse_result = parse.urlparse(storage_url)
182
-         swift_object_path = '/'.join((parse_result.path, container, object))
183
-         temp_url_key = account_info['x-account-meta-temp-url-key']
184
-@@ -186,3 +221,23 @@ class SwiftAPI(object):
185
-         except swift_exceptions.ClientException as e:
186
-             operation = _("post object")
187
-             raise exception.SwiftOperationError(operation=operation, error=e)
188
-+
189
-+    def get_object(self, container, object, object_headers=None,
190
-+                   chunk_size=None):
191
-+        """Get Swift object.
192
-+
193
-+        :param container: The name of the container in which Swift object
194
-+            is placed.
195
-+        :param object: The name of the object in Swift
196
-+        :param object_headers: the headers for the object to pass to Swift
197
-+        :param chunk_size: size of the chunk used read to read from response
198
-+        :returns: Tuple (body, headers)
199
-+        :raises: SwiftOperationError, if operation with Swift fails.
200
-+        """
201
-+        try:
202
-+            return self.connection.get_object(container, object,
203
-+                                              headers=object_headers,
204
-+                                              resp_chunk_size=chunk_size)
205
-+        except swift_exceptions.ClientException as e:
206
-+            operation = _("get object")
207
-+            raise exception.SwiftOperationError(operation=operation, error=e)
208
-diff --git a/ironic/common/utils.py b/ironic/common/utils.py
209
-index 3633f82..4d1ca28 100644
210
---- a/ironic/common/utils.py
211
-+++ b/ironic/common/utils.py
212
-@@ -38,6 +38,7 @@ from ironic.common import exception
213
- from ironic.common.i18n import _
214
- from ironic.common.i18n import _LE
215
- from ironic.common.i18n import _LW
216
-+from ironic.common import keystone
217
- from ironic.openstack.common import log as logging
218
- 
219
- utils_opts = [
220
-@@ -536,3 +537,8 @@ def dd(src, dst, *args):
221
- def is_http_url(url):
222
-     url = url.lower()
223
-     return url.startswith('http://') or url.startswith('https://')
224
-+
225
-+
226
-+def get_tenant_id(tenant_name):
227
-+    ksclient = keystone._get_ksclient()
228
-+    return ksclient.tenants.find(name=tenant_name).to_dict()['id']
229
-diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py
230
-index c2b75bc..53f516b 100644
231
---- a/ironic/conductor/manager.py
232
-+++ b/ironic/conductor/manager.py
233
-@@ -766,6 +766,11 @@ class ConductorManager(periodic_task.PeriodicTasks):
234
-         """
235
-         LOG.debug("RPC do_node_tear_down called for node %s." % node_id)
236
- 
237
-+        with task_manager.acquire(context, node_id, shared=True) as task:
238
-+            if (task.node.provision_state == states.DEPLOYING and
239
-+                    task.driver.deploy.can_terminate_deployment):
240
-+                task.driver.deploy.terminate_deployment(task)
241
-+
242
-         with task_manager.acquire(context, node_id, shared=False) as task:
243
-             try:
244
-                 # NOTE(ghe): Valid power driver values are needed to perform
245
-diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py
246
-index e0685d0..d1fa4bc 100644
247
---- a/ironic/drivers/base.py
248
-+++ b/ironic/drivers/base.py
249
-@@ -318,6 +318,13 @@ class DeployInterface(BaseInterface):
250
-         """
251
-         pass
252
- 
253
-+    def terminate_deployment(self, *args, **kwargs):
254
-+        pass
255
-+
256
-+    @property
257
-+    def can_terminate_deployment(self):
258
-+        return False
259
-+
260
- 
261
- @six.add_metaclass(abc.ABCMeta)
262
- class PowerInterface(BaseInterface):
263
-diff --git a/ironic/drivers/modules/image_cache.py b/ironic/drivers/modules/image_cache.py
264
-index d7b27c0..eb3ec55 100644
265
---- a/ironic/drivers/modules/image_cache.py
266
-+++ b/ironic/drivers/modules/image_cache.py
267
-@@ -25,9 +25,9 @@ import uuid
268
- 
269
- from oslo_concurrency import lockutils
270
- from oslo_config import cfg
271
-+from oslo_utils import uuidutils
272
- 
273
- from ironic.common import exception
274
--from ironic.common.glance_service import service_utils
275
- from ironic.common.i18n import _LI
276
- from ironic.common.i18n import _LW
277
- from ironic.common import images
278
-@@ -100,15 +100,15 @@ class ImageCache(object):
279
- 
280
-         # TODO(ghe): have hard links and counts the same behaviour in all fs
281
- 
282
--        # NOTE(vdrok): File name is converted to UUID if it's not UUID already,
283
--        # so that two images with same file names do not collide
284
--        if service_utils.is_glance_image(href):
285
--            master_file_name = service_utils.parse_image_ref(href)[0]
286
-+        if uuidutils.is_uuid_like(href):
287
-+            master_file_name = href
288
-+        elif (self._image_service and
289
-+              hasattr(self._image_service, 'get_image_unique_id')):
290
-+            master_file_name = self._image_service.get_image_unique_id(href)
291
-         else:
292
--            # NOTE(vdrok): Doing conversion of href in case it's unicode
293
--            # string, UUID cannot be generated for unicode strings on python 2.
294
-             master_file_name = str(uuid.uuid5(uuid.NAMESPACE_URL,
295
-                                               href.encode('utf-8')))
296
-+
297
-         master_path = os.path.join(self.master_dir, master_file_name)
298
- 
299
-         if CONF.parallel_image_downloads:
300
-diff --git a/ironic/tests/test_swift.py b/ironic/tests/test_swift.py
301
-index 9daa06e..aaa1b7c 100644
302
---- a/ironic/tests/test_swift.py
303
-+++ b/ironic/tests/test_swift.py
304
-@@ -113,6 +113,7 @@ class SwiftTestCase(base.TestCase):
305
-         connection_obj_mock.get_auth.return_value = auth
306
-         head_ret_val = {'x-account-meta-temp-url-key': 'secretkey'}
307
-         connection_obj_mock.head_account.return_value = head_ret_val
308
-+        connection_obj_mock.os_options = {}
309
-         gen_temp_url_mock.return_value = 'temp-url-path'
310
-         temp_url_returned = swiftapi.get_temp_url('container', 'object', 10)
311
-         connection_obj_mock.get_auth.assert_called_once_with()

+ 508
- 0
patches/patch-ironic-stable-liberty View File

@@ -0,0 +1,508 @@
1
+diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py
2
+index d95298f..1b99c68 100644
3
+--- a/ironic/api/controllers/v1/node.py
4
++++ b/ironic/api/controllers/v1/node.py
5
+@@ -452,8 +452,13 @@ class NodeStatesController(rest.RestController):
6
+             raise exception.NodeInMaintenance(op=_('provisioning'),
7
+                                               node=rpc_node.uuid)
8
+ 
9
++        driver = api_utils.get_driver_by_name(rpc_node.driver)
10
++        driver_can_terminate = (driver and
11
++                                driver.deploy.can_terminate_deployment)
12
++
13
+         m = ir_states.machine.copy()
14
+         m.initialize(rpc_node.provision_state)
15
++
16
+         if not m.is_actionable_event(ir_states.VERBS.get(target, target)):
17
+             # Normally, we let the task manager recognize and deal with
18
+             # NodeLocked exceptions. However, that isn't done until the RPC
19
+@@ -470,6 +475,16 @@ class NodeStatesController(rest.RestController):
20
+                 action=target, node=rpc_node.uuid,
21
+                 state=rpc_node.provision_state)
22
+ 
23
++        # Note(obereozvskyi): we need to check weather driver supports deploy
24
++        # terminating
25
++        if (m.current_state == ir_states.DEPLOYING and
26
++            target == ir_states.DELETED and
27
++            not driver_can_terminate):
28
++
29
++            raise exception.InvalidStateRequested(
30
++                action=target, node=rpc_node.uuid,
31
++                state=rpc_node.provision_state)
32
++
33
+         if configdrive and target != ir_states.ACTIVE:
34
+             msg = (_('Adding a config drive is only supported when setting '
35
+                      'provision state to %s') % ir_states.ACTIVE)
36
+diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py
37
+index 538ca45..3715d41 100644
38
+--- a/ironic/api/controllers/v1/utils.py
39
++++ b/ironic/api/controllers/v1/utils.py
40
+@@ -23,6 +23,7 @@ from webob.static import FileIter
41
+ import wsme
42
+ 
43
+ from ironic.api.controllers.v1 import versions
44
++from ironic.common import driver_factory
45
+ from ironic.common import exception
46
+ from ironic.common.i18n import _
47
+ from ironic.common import states
48
+@@ -109,7 +110,16 @@ def is_valid_node_name(name):
49
+     :param: name: the node name to check.
50
+     :returns: True if the name is valid, False otherwise.
51
+     """
52
+-    return is_valid_logical_name(name) and not uuidutils.is_uuid_like(name)
53
++    return utils.is_hostname_safe(name) and (not uuidutils.is_uuid_like(name))
54
++
55
++
56
++def get_driver_by_name(driver_name):
57
++    _driver_factory = driver_factory.DriverFactory()
58
++    try:
59
++        driver = _driver_factory[driver_name]
60
++        return driver.obj
61
++    except Exception:
62
++        return None
63
+ 
64
+ 
65
+ def is_valid_logical_name(name):
66
+diff --git a/ironic/common/context.py b/ironic/common/context.py
67
+index ccd2222..b8186c9 100644
68
+--- a/ironic/common/context.py
69
++++ b/ironic/common/context.py
70
+@@ -65,5 +65,4 @@ class RequestContext(context.RequestContext):
71
+     @classmethod
72
+     def from_dict(cls, values):
73
+         values.pop('user', None)
74
+-        values.pop('tenant', None)
75
+         return cls(**values)
76
+diff --git a/ironic/common/images.py b/ironic/common/images.py
77
+index 5b00e65..28e6bd7 100644
78
+--- a/ironic/common/images.py
79
++++ b/ironic/common/images.py
80
+@@ -328,16 +328,17 @@ def convert_image(source, dest, out_format, run_as_root=False):
81
+     utils.execute(*cmd, run_as_root=run_as_root)
82
+ 
83
+ 
84
+-def fetch(context, image_href, path, force_raw=False):
85
++def fetch(context, image_href, path, force_raw=False, image_service=None):
86
+     # TODO(vish): Improve context handling and add owner and auth data
87
+     #             when it is added to glance.  Right now there is no
88
+     #             auth checking in glance, so we assume that access was
89
+     #             checked before we got here.
90
+-    image_service = service.get_image_service(image_href,
91
+-                                              context=context)
92
+-    LOG.debug("Using %(image_service)s to download image %(image_href)s." %
93
+-              {'image_service': image_service.__class__,
94
+-               'image_href': image_href})
95
++    if not image_service:
96
++        image_service = service.get_image_service(image_href,
97
++                                                  context=context)
98
++        LOG.debug("Using %(image_service)s to download image %(image_href)s." %
99
++                  {'image_service': image_service.__class__,
100
++                   'image_href': image_href})
101
+ 
102
+     with fileutils.remove_path_on_error(path):
103
+         with open(path, "wb") as image_file:
104
+diff --git a/ironic/common/states.py b/ironic/common/states.py
105
+index e61c807..2523a7f 100644
106
+--- a/ironic/common/states.py
107
++++ b/ironic/common/states.py
108
+@@ -245,6 +245,9 @@ machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers)
109
+ # A deployment may fail
110
+ machine.add_transition(DEPLOYING, DEPLOYFAIL, 'fail')
111
+ 
112
++# A deployment may be terminated
113
++machine.add_transition(DEPLOYING, DELETING, 'delete')
114
++
115
+ # A failed deployment may be retried
116
+ # ironic/conductor/manager.py:do_node_deploy()
117
+ machine.add_transition(DEPLOYFAIL, DEPLOYING, 'rebuild')
118
+diff --git a/ironic/common/swift.py b/ironic/common/swift.py
119
+index 8fa2d65..14d6b55 100644
120
+--- a/ironic/common/swift.py
121
++++ b/ironic/common/swift.py
122
+@@ -24,6 +24,7 @@ from swiftclient import utils as swift_utils
123
+ from ironic.common import exception
124
+ from ironic.common.i18n import _
125
+ from ironic.common import keystone
126
++from ironic.common import utils
127
+ 
128
+ swift_opts = [
129
+     cfg.IntOpt('swift_max_retries',
130
+@@ -36,6 +37,13 @@ swift_opts = [
131
+ CONF = cfg.CONF
132
+ CONF.register_opts(swift_opts, group='swift')
133
+ 
134
++CONF.import_opt('swift_endpoint_url',
135
++                'ironic.common.glance_service.v2.image_service',
136
++                group='glance')
137
++CONF.import_opt('swift_api_version',
138
++                'ironic.common.glance_service.v2.image_service',
139
++                group='glance')
140
++
141
+ CONF.import_opt('admin_user', 'keystonemiddleware.auth_token',
142
+                 group='keystone_authtoken')
143
+ CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token',
144
+@@ -62,7 +70,9 @@ class SwiftAPI(object):
145
+                  tenant_name=CONF.keystone_authtoken.admin_tenant_name,
146
+                  key=CONF.keystone_authtoken.admin_password,
147
+                  auth_url=CONF.keystone_authtoken.auth_uri,
148
+-                 auth_version=CONF.keystone_authtoken.auth_version):
149
++                 auth_version=CONF.keystone_authtoken.auth_version,
150
++                 preauthtoken=None,
151
++                 preauthtenant=None):
152
+         """Constructor for creating a SwiftAPI object.
153
+ 
154
+         :param user: the name of the user for Swift account
155
+@@ -70,16 +80,41 @@ class SwiftAPI(object):
156
+         :param key: the 'password' or key to authenticate with
157
+         :param auth_url: the url for authentication
158
+         :param auth_version: the version of api to use for authentication
159
++        :param preauthtoken: authentication token (if you have already
160
++                     authenticated) note authurl/user/key/tenant_name
161
++                     are not required when specifying preauthtoken
162
++        :param preauthtenant a tenant that will be accessed using the
163
++                     preauthtoken
164
+         """
165
+-        auth_url = keystone.get_keystone_url(auth_url, auth_version)
166
+-        params = {'retries': CONF.swift.swift_max_retries,
167
+-                  'insecure': CONF.keystone_authtoken.insecure,
168
+-                  'cacert': CONF.keystone_authtoken.cafile,
169
+-                  'user': user,
170
+-                  'tenant_name': tenant_name,
171
+-                  'key': key,
172
+-                  'authurl': auth_url,
173
+-                  'auth_version': auth_version}
174
++        params = {
175
++            'retries': CONF.swift.swift_max_retries,
176
++            'insecure': CONF.keystone_authtoken.insecure,
177
++            'cacert': CONF.keystone_authtoken.cafile
178
++        }
179
++
180
++        if preauthtoken:
181
++            # Determining swift url for the user's tenant account.
182
++            tenant_id = utils.get_tenant_id(tenant_name=preauthtenant)
183
++            url = "{endpoint}/{api_ver}/AUTH_{tenant}".format(
184
++                endpoint=CONF.glance.swift_endpoint_url,
185
++                api_ver=CONF.glance.swift_api_version,
186
++                tenant=tenant_id
187
++            )
188
++            # authurl/user/key/tenant_name are not required when specifying
189
++            # preauthtoken
190
++            params.update({
191
++                'preauthtoken': preauthtoken,
192
++                'preauthurl': url
193
++            })
194
++        else:
195
++            auth_url = keystone.get_keystone_url(auth_url, auth_version)
196
++            params.update({
197
++                'user': user,
198
++                'tenant_name': tenant_name,
199
++                'key': key,
200
++                'authurl': auth_url,
201
++                'auth_version': auth_version
202
++            })
203
+ 
204
+         self.connection = swift_client.Connection(**params)
205
+ 
206
+@@ -131,8 +166,8 @@ class SwiftAPI(object):
207
+             operation = _("head account")
208
+             raise exception.SwiftOperationError(operation=operation,
209
+                                                 error=e)
210
+-
211
+-        storage_url, token = self.connection.get_auth()
212
++        storage_url = (self.connection.os_options.get('object_storage_url') or
213
++                       self.connection.get_auth()[0])
214
+         parse_result = parse.urlparse(storage_url)
215
+         swift_object_path = '/'.join((parse_result.path, container, object))
216
+         temp_url_key = account_info['x-account-meta-temp-url-key']
217
+@@ -189,3 +224,23 @@ class SwiftAPI(object):
218
+         except swift_exceptions.ClientException as e:
219
+             operation = _("post object")
220
+             raise exception.SwiftOperationError(operation=operation, error=e)
221
++
222
++    def get_object(self, container, object, object_headers=None,
223
++                   chunk_size=None):
224
++        """Get Swift object.
225
++
226
++        :param container: The name of the container in which Swift object
227
++            is placed.
228
++        :param object: The name of the object in Swift
229
++        :param object_headers: the headers for the object to pass to Swift
230
++        :param chunk_size: size of the chunk used read to read from response
231
++        :returns: Tuple (body, headers)
232
++        :raises: SwiftOperationError, if operation with Swift fails.
233
++        """
234
++        try:
235
++            return self.connection.get_object(container, object,
236
++                                              headers=object_headers,
237
++                                              resp_chunk_size=chunk_size)
238
++        except swift_exceptions.ClientException as e:
239
++            operation = _("get object")
240
++            raise exception.SwiftOperationError(operation=operation, error=e)
241
+diff --git a/ironic/common/utils.py b/ironic/common/utils.py
242
+index f863087..ed4398f 100644
243
+--- a/ironic/common/utils.py
244
++++ b/ironic/common/utils.py
245
+@@ -42,6 +42,7 @@ from ironic.common import exception
246
+ from ironic.common.i18n import _
247
+ from ironic.common.i18n import _LE
248
+ from ironic.common.i18n import _LW
249
++from ironic.common import keystone
250
+ 
251
+ utils_opts = [
252
+     cfg.StrOpt('rootwrap_config',
253
+@@ -560,6 +561,11 @@ def is_http_url(url):
254
+     return url.startswith('http://') or url.startswith('https://')
255
+ 
256
+ 
257
++def get_tenant_id(tenant_name):
258
++    ksclient = keystone._get_ksclient()
259
++    return ksclient.tenants.find(name=tenant_name).to_dict()['id']
260
++
261
++
262
+ def check_dir(directory_to_check=None, required_space=1):
263
+     """Check a directory is usable.
264
+ 
265
+diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py
266
+index b4bee31..e5bb190 100644
267
+--- a/ironic/conductor/manager.py
268
++++ b/ironic/conductor/manager.py
269
+@@ -768,6 +768,10 @@ class ConductorManager(periodic_task.PeriodicTasks):
270
+         """
271
+         LOG.debug("RPC do_node_tear_down called for node %s." % node_id)
272
+ 
273
++        with task_manager.acquire(context, node_id, shared=True) as task:
274
++            if (task.node.provision_state == states.DEPLOYING and
275
++                    task.driver.deploy.can_terminate_deployment):
276
++                task.driver.deploy.terminate_deployment(task)
277
+         with task_manager.acquire(context, node_id, shared=False,
278
+                                   purpose='node tear down') as task:
279
+             try:
280
+diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py
281
+index 098b7a0..6ebe05d 100644
282
+--- a/ironic/drivers/base.py
283
++++ b/ironic/drivers/base.py
284
+@@ -345,6 +345,13 @@ class DeployInterface(BaseInterface):
285
+         """
286
+         pass
287
+ 
288
++    def terminate_deployment(self, *args, **kwargs):
289
++        pass
290
++
291
++    @property
292
++    def can_terminate_deployment(self):
293
++        return False
294
++
295
+ 
296
+ @six.add_metaclass(abc.ABCMeta)
297
+ class BootInterface(object):
298
+diff --git a/ironic/drivers/modules/image_cache.py b/ironic/drivers/modules/image_cache.py
299
+index 8bb1e23..8e1a921 100644
300
+--- a/ironic/drivers/modules/image_cache.py
301
++++ b/ironic/drivers/modules/image_cache.py
302
+@@ -27,6 +27,7 @@ from oslo_concurrency import lockutils
303
+ from oslo_config import cfg
304
+ from oslo_log import log as logging
305
+ from oslo_utils import fileutils
306
++from oslo_utils import uuidutils
307
+ import six
308
+ 
309
+ from ironic.common import exception
310
+@@ -60,7 +61,8 @@ _cache_cleanup_list = []
311
+ class ImageCache(object):
312
+     """Class handling access to cache for master images."""
313
+ 
314
+-    def __init__(self, master_dir, cache_size, cache_ttl):
315
++    def __init__(self, master_dir, cache_size, cache_ttl,
316
++                 image_service=None):
317
+         """Constructor.
318
+ 
319
+         :param master_dir: cache directory to work on
320
+@@ -70,6 +72,7 @@ class ImageCache(object):
321
+         self.master_dir = master_dir
322
+         self._cache_size = cache_size
323
+         self._cache_ttl = cache_ttl
324
++        self._image_service = image_service
325
+         if master_dir is not None:
326
+             fileutils.ensure_tree(master_dir)
327
+ 
328
+@@ -94,23 +97,28 @@ class ImageCache(object):
329
+             # NOTE(ghe): We don't share images between instances/hosts
330
+             if not CONF.parallel_image_downloads:
331
+                 with lockutils.lock(img_download_lock_name, 'ironic-'):
332
+-                    _fetch(ctx, href, dest_path, force_raw)
333
++                    _fetch(ctx, href, dest_path,
334
++                           image_service=self._image_service,
335
++                           force_raw=force_raw)
336
+             else:
337
+-                _fetch(ctx, href, dest_path, force_raw)
338
++                _fetch(ctx, href, dest_path, image_service=self._image_service,
339
++                       force_raw=force_raw)
340
+             return
341
+ 
342
+         # TODO(ghe): have hard links and counts the same behaviour in all fs
343
+ 
344
+-        # NOTE(vdrok): File name is converted to UUID if it's not UUID already,
345
+-        # so that two images with same file names do not collide
346
+-        if service_utils.is_glance_image(href):
347
+-            master_file_name = service_utils.parse_image_ref(href)[0]
348
++        if uuidutils.is_uuid_like(href):
349
++            master_file_name = href
350
++
351
++        elif (self._image_service and
352
++              hasattr(self._image_service, 'get_image_unique_id')):
353
++            master_file_name = self._image_service.get_image_unique_id(href)
354
++
355
+         else:
356
+-            # NOTE(vdrok): Doing conversion of href in case it's unicode
357
+-            # string, UUID cannot be generated for unicode strings on python 2.
358
+             href_encoded = href.encode('utf-8') if six.PY2 else href
359
+             master_file_name = str(uuid.uuid5(uuid.NAMESPACE_URL,
360
+                                               href_encoded))
361
++
362
+         master_path = os.path.join(self.master_dir, master_file_name)
363
+ 
364
+         if CONF.parallel_image_downloads:
365
+@@ -121,8 +129,8 @@ class ImageCache(object):
366
+             # NOTE(vdrok): After rebuild requested image can change, so we
367
+             # should ensure that dest_path and master_path (if exists) are
368
+             # pointing to the same file and their content is up to date
369
+-            cache_up_to_date = _delete_master_path_if_stale(master_path, href,
370
+-                                                            ctx)
371
++            cache_up_to_date = _delete_master_path_if_stale(
372
++                master_path, href, ctx, img_service=self._image_service)
373
+             dest_up_to_date = _delete_dest_path_if_stale(master_path,
374
+                                                          dest_path)
375
+ 
376
+@@ -168,7 +176,8 @@ class ImageCache(object):
377
+         tmp_path = os.path.join(tmp_dir, href.split('/')[-1])
378
+ 
379
+         try:
380
+-            _fetch(ctx, href, tmp_path, force_raw)
381
++            _fetch(ctx, href, tmp_path, force_raw,
382
++                   image_service=self._image_service)
383
+             # NOTE(dtantsur): no need for global lock here - master_path
384
+             # will have link count >1 at any moment, so won't be cleaned up
385
+             os.link(tmp_path, master_path)
386
+@@ -308,10 +317,11 @@ def _free_disk_space_for(path):
387
+     return stat.f_frsize * stat.f_bavail
388
+ 
389
+ 
390
+-def _fetch(context, image_href, path, force_raw=False):
391
++def _fetch(context, image_href, path, force_raw=False, image_service=None):
392
+     """Fetch image and convert to raw format if needed."""
393
+     path_tmp = "%s.part" % path
394
+-    images.fetch(context, image_href, path_tmp, force_raw=False)
395
++    images.fetch(context, image_href, path_tmp, force_raw=False,
396
++                 image_service=image_service)
397
+     # Notes(yjiang5): If glance can provide the virtual size information,
398
+     # then we can firstly clean cache and then invoke images.fetch().
399
+     if force_raw:
400
+@@ -384,7 +394,7 @@ def cleanup(priority):
401
+     return _add_property_to_class_func
402
+ 
403
+ 
404
+-def _delete_master_path_if_stale(master_path, href, ctx):
405
++def _delete_master_path_if_stale(master_path, href, ctx, img_service=None):
406
+     """Delete image from cache if it is not up to date with href contents.
407
+ 
408
+     :param master_path: path to an image in master cache
409
+@@ -397,7 +407,8 @@ def _delete_master_path_if_stale(master_path, href, ctx):
410
+         # Glance image contents cannot be updated without changing image's UUID
411
+         return os.path.exists(master_path)
412
+     if os.path.exists(master_path):
413
+-        img_service = image_service.get_image_service(href, context=ctx)
414
++        if not img_service:
415
++            img_service = image_service.get_image_service(href, context=ctx)
416
+         img_mtime = img_service.show(href).get('updated_at')
417
+         if not img_mtime:
418
+             # This means that href is not a glance image and doesn't have an
419
+diff --git a/ironic/tests/common/test_swift.py b/ironic/tests/common/test_swift.py
420
+index 43e3ef0..b2632c4 100644
421
+--- a/ironic/tests/common/test_swift.py
422
++++ b/ironic/tests/common/test_swift.py
423
+@@ -120,6 +120,7 @@ class SwiftTestCase(base.TestCase):
424
+         connection_obj_mock.get_auth.return_value = auth
425
+         head_ret_val = {'x-account-meta-temp-url-key': 'secretkey'}
426
+         connection_obj_mock.head_account.return_value = head_ret_val
427
++        connection_obj_mock.os_options = {}
428
+         gen_temp_url_mock.return_value = 'temp-url-path'
429
+         temp_url_returned = swiftapi.get_temp_url('container', 'object', 10)
430
+         connection_obj_mock.get_auth.assert_called_once_with()
431
+diff --git a/ironic/tests/drivers/test_image_cache.py b/ironic/tests/drivers/test_image_cache.py
432
+index 3d666cd..436aa49 100644
433
+--- a/ironic/tests/drivers/test_image_cache.py
434
++++ b/ironic/tests/drivers/test_image_cache.py
435
+@@ -59,7 +59,7 @@ class TestImageCacheFetch(base.TestCase):
436
+         self.cache.fetch_image(self.uuid, self.dest_path)
437
+         self.assertFalse(mock_download.called)
438
+         mock_fetch.assert_called_once_with(
439
+-            None, self.uuid, self.dest_path, True)
440
++            None, self.uuid, self.dest_path, True, image_service=None)
441
+         self.assertFalse(mock_clean_up.called)
442
+ 
443
+     @mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
444
+@@ -75,7 +75,7 @@ class TestImageCacheFetch(base.TestCase):
445
+             mock_clean_up):
446
+         self.cache.fetch_image(self.uuid, self.dest_path)
447
+         mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
448
+-                                               None)
449
++                                               None, img_service=None)
450
+         mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
451
+         self.assertFalse(mock_link.called)
452
+         self.assertFalse(mock_download.called)
453
+@@ -94,7 +94,7 @@ class TestImageCacheFetch(base.TestCase):
454
+             mock_clean_up):
455
+         self.cache.fetch_image(self.uuid, self.dest_path)
456
+         mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
457
+-                                               None)
458
++                                               None, img_service=None)
459
+         mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
460
+         mock_link.assert_called_once_with(self.master_path, self.dest_path)
461
+         self.assertFalse(mock_download.called)
462
+@@ -113,7 +113,7 @@ class TestImageCacheFetch(base.TestCase):
463
+             mock_clean_up):
464
+         self.cache.fetch_image(self.uuid, self.dest_path)
465
+         mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
466
+-                                               None)
467
++                                               None, img_service=None)
468
+         mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
469
+         self.assertFalse(mock_link.called)
470
+         mock_download.assert_called_once_with(
471
+@@ -134,7 +134,7 @@ class TestImageCacheFetch(base.TestCase):
472
+             mock_clean_up):
473
+         self.cache.fetch_image(self.uuid, self.dest_path)
474
+         mock_cache_upd.assert_called_once_with(self.master_path, self.uuid,
475
+-                                               None)
476
++                                               None, img_service=None)
477
+         mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
478
+         self.assertFalse(mock_link.called)
479
+         mock_download.assert_called_once_with(
480
+@@ -158,7 +158,7 @@ class TestImageCacheFetch(base.TestCase):
481
+ 
482
+     @mock.patch.object(image_cache, '_fetch', autospec=True)
483
+     def test__download_image(self, mock_fetch):
484
+-        def _fake_fetch(ctx, uuid, tmp_path, *args):
485
++        def _fake_fetch(ctx, uuid, tmp_path, *args, **kwargs):
486
+             self.assertEqual(self.uuid, uuid)
487
+             self.assertNotEqual(self.dest_path, tmp_path)
488
+             self.assertNotEqual(os.path.dirname(tmp_path), self.master_dir)
489
+@@ -430,7 +430,7 @@ class TestImageCacheCleanUp(base.TestCase):
490
+     @mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
491
+     @mock.patch.object(image_cache, '_fetch', autospec=True)
492
+     def test_temp_images_not_cleaned(self, mock_fetch, mock_rmtree):
493
+-        def _fake_fetch(ctx, uuid, tmp_path, *args):
494
++        def _fake_fetch(ctx, uuid, tmp_path, *args, **kwargs):
495
+             with open(tmp_path, 'w') as fp:
496
+                 fp.write("TEST" * 10)
497
+ 
498
+@@ -675,7 +675,8 @@ class TestFetchCleanup(base.TestCase):
499
+         mock_size.return_value = 100
500
+         image_cache._fetch('fake', 'fake-uuid', '/foo/bar', force_raw=True)
501
+         mock_fetch.assert_called_once_with('fake', 'fake-uuid',
502
+-                                           '/foo/bar.part', force_raw=False)
503
++                                           '/foo/bar.part', image_service=None,
504
++                                           force_raw=False)
505
+         mock_clean.assert_called_once_with('/foo', 100)
506
+         mock_raw.assert_called_once_with('fake-uuid', '/foo/bar',
507
+                                          '/foo/bar.part')
508
+

patches/patch-nova-stable-kilo → patches/patch-nova-stable-liberty View File

@@ -1,98 +1,41 @@
1
+diff --git a/nova/objects/image_meta.py b/nova/objects/image_meta.py
2
+index 15be3f1..83fc2fb 100644
3
+--- a/nova/objects/image_meta.py
4
++++ b/nova/objects/image_meta.py
5
+@@ -346,6 +346,7 @@ class ImageMetaProps(base.NovaObject):
6
+         # is a fairly generic type. For a detailed type consider os_distro
7
+         # instead
8
+         'os_type': fields.OSTypeField(),
9
++        'deploy_config': fields.StringField(),
10
+     }
11
+
12
+     # The keys are the legacy property names and
13
+diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py
14
+index 031555f..e0368b8 100644
15
+--- a/nova/tests/unit/objects/test_objects.py
16
++++ b/nova/tests/unit/objects/test_objects.py
17
+@@ -1180,7 +1180,7 @@ object_data = {
18
+     'HostMapping': '1.0-1a3390a696792a552ab7bd31a77ba9ac',
19
+     'HVSpec': '1.1-6b4f7c0f688cbd03e24142a44eb9010d',
20
+     'ImageMeta': '1.7-642d1b2eb3e880a367f37d72dd76162d',
21
+-    'ImageMetaProps': '1.7-f12fc4cf3e25d616f69a66fb9d2a7aa6',
22
++    'ImageMetaProps': '1.7-716042e9e80ea16890f475200940d6f9',
23
+     'Instance': '2.0-ff56804dce87d81d9a04834d4bd1e3d2',
24
+     # NOTE(danms): Reviewers: do not approve changes to the Instance1
25
+     # object schema. It is frozen for Liberty and will be removed in
1 26
 diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py
2
-index b19c6eb..6305ff7 100644
27
+index a8c653a..940497c 100644
3 28
 --- a/nova/tests/unit/virt/ironic/test_driver.py
4 29
 +++ b/nova/tests/unit/virt/ironic/test_driver.py
5
-@@ -24,6 +24,7 @@ from oslo_utils import uuidutils
6
- from nova.api.metadata import base as instance_metadata
7
- from nova.compute import power_state as nova_states
8
- from nova.compute import task_states
9
-+from nova.compute import vm_states
10
- from nova import context as nova_context
11
- from nova import exception
12
- from nova import objects
13
-@@ -143,8 +144,9 @@ class IronicDriverTestCase(test.NoDBTestCase):
14
-                           ironic_driver._validate_instance_and_node,
15
-                           ironicclient, instance)
16
- 
17
-+    @mock.patch.object(objects.Instance, 'refresh')
18
-     @mock.patch.object(ironic_driver, '_validate_instance_and_node')
19
--    def test__wait_for_active_pass(self, fake_validate):
20
-+    def test__wait_for_active_pass(self, fake_validate, fake_refresh):
21
-         instance = fake_instance.fake_instance_obj(self.ctx,
22
-                 uuid=uuidutils.generate_uuid())
23
-         node = ironic_utils.get_test_node(
24
-@@ -152,10 +154,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
25
- 
26
-         fake_validate.return_value = node
27
-         self.driver._wait_for_active(FAKE_CLIENT, instance)
28
--        self.assertTrue(fake_validate.called)
29
-+        fake_validate.assert_called_once_with(FAKE_CLIENT, instance)
30
-+        fake_refresh.assert_called_once_with()
31
- 
32
-+    @mock.patch.object(objects.Instance, 'refresh')
33
-     @mock.patch.object(ironic_driver, '_validate_instance_and_node')
34
--    def test__wait_for_active_done(self, fake_validate):
35
-+    def test__wait_for_active_done(self, fake_validate, fake_refresh):
36
-         instance = fake_instance.fake_instance_obj(self.ctx,
37
-                 uuid=uuidutils.generate_uuid())
38
-         node = ironic_utils.get_test_node(
39
-@@ -165,10 +169,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
40
-         self.assertRaises(loopingcall.LoopingCallDone,
41
-                 self.driver._wait_for_active,
42
-                 FAKE_CLIENT, instance)
43
--        self.assertTrue(fake_validate.called)
44
-+        fake_validate.assert_called_once_with(FAKE_CLIENT, instance)
45
-+        fake_refresh.assert_called_once_with()
46
- 
47
-+    @mock.patch.object(objects.Instance, 'refresh')
48
-     @mock.patch.object(ironic_driver, '_validate_instance_and_node')
49
--    def test__wait_for_active_fail(self, fake_validate):
50
-+    def test__wait_for_active_fail(self, fake_validate, fake_refresh):
51
-         instance = fake_instance.fake_instance_obj(self.ctx,
52
-                 uuid=uuidutils.generate_uuid())
53
-         node = ironic_utils.get_test_node(
54
-@@ -178,7 +184,31 @@ class IronicDriverTestCase(test.NoDBTestCase):
55
-         self.assertRaises(exception.InstanceDeployFailure,
56
-                 self.driver._wait_for_active,
57
-                 FAKE_CLIENT, instance)
58
--        self.assertTrue(fake_validate.called)
59
-+        fake_validate.assert_called_once_with(FAKE_CLIENT, instance)
60
-+        fake_refresh.assert_called_once_with()
61
-+
62
-+    @mock.patch.object(objects.Instance, 'refresh')
63
-+    @mock.patch.object(ironic_driver, '_validate_instance_and_node')
64
-+    def _wait_for_active_abort(self, instance_params, fake_validate,
65
-+                              fake_refresh):
66
-+        instance = fake_instance.fake_instance_obj(self.ctx,
67
-+                uuid=uuidutils.generate_uuid(),
68
-+                **instance_params)
69
-+        self.assertRaises(exception.InstanceDeployFailure,
70
-+                self.driver._wait_for_active,
71
-+                FAKE_CLIENT, instance)
72
-+        # Assert _validate_instance_and_node wasn't called
73
-+        self.assertFalse(fake_validate.called)
74
-+        fake_refresh.assert_called_once_with()
75
-+
76
-+    def test__wait_for_active_abort_deleting(self):
77
-+        self._wait_for_active_abort({'task_state': task_states.DELETING})
78
-+
79
-+    def test__wait_for_active_abort_deleted(self):
80
-+        self._wait_for_active_abort({'vm_state': vm_states.DELETED})
81
-+
82
-+    def test__wait_for_active_abort_error(self):
83
-+        self._wait_for_active_abort({'vm_state': vm_states.ERROR})
84
- 
85
-     @mock.patch.object(ironic_driver, '_validate_instance_and_node')
86
-     def test__wait_for_power_state_pass(self, fake_validate):
87
-@@ -626,6 +656,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
30
+@@ -799,6 +799,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
88 31
          result = self.driver.macs_for_instance(instance)
89 32
          self.assertIsNone(result)
90
- 
33
+
91 34
 +    @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options')
92 35
      @mock.patch.object(objects.Instance, 'save')
93 36
      @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
94 37
      @mock.patch.object(FAKE_CLIENT, 'node')
95
-@@ -634,7 +665,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
38
+@@ -807,7 +808,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
96 39
      @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
97 40
      @mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
98 41
      def _test_spawn(self, mock_sf, mock_pvifs, mock_adf, mock_wait_active,
@@ -101,18 +44,18 @@ index b19c6eb..6305ff7 100644
101 44
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
102 45
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
103 46
          instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
104
-@@ -668,6 +699,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
47
+@@ -845,6 +846,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
105 48
          fake_looping_call.start.assert_called_once_with(
106 49
              interval=CONF.ironic.api_retry_interval)
107 50
          fake_looping_call.wait.assert_called_once_with()
108 51
 +        mock_sb_options.assert_called_once_with(self.ctx, instance, node_uuid)
109
- 
52
+
110 53
      @mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
111 54
      @mock.patch.object(configdrive, 'required_by')
112
-@@ -720,14 +752,61 @@ class IronicDriverTestCase(test.NoDBTestCase):
55
+@@ -897,14 +899,62 @@ class IronicDriverTestCase(test.NoDBTestCase):
113 56
              self.driver.spawn, self.ctx, instance, None, [], None)
114 57
          mock_destroy.assert_called_once_with(self.ctx, instance, None)
115
- 
58
+
116 59
 +    @mock.patch.object(FAKE_CLIENT, 'node')
117 60
 +    def test__get_switch_boot_options(self, mock_node):
118 61
 +        node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
@@ -160,23 +103,25 @@ index b19c6eb..6305ff7 100644
160 103
          node = ironic_utils.get_test_node(driver='fake')
161 104
 -        instance = fake_instance.fake_instance_obj(self.ctx,
162 105
 -                                                   node=node.uuid)
106
+-        image_meta = ironic_utils.get_test_image_meta_object()
163 107
 +        instance = fake_instance.fake_instance_obj(
164 108
 +            self.ctx,
165 109
 +            node=node.uuid,
166 110
 +            expected_attrs=('metadata',))
167
-         image_meta = ironic_utils.get_test_image_meta()
168
-+        mock_get_depl_conf_opts.return_value = {'foo': 'bar123'}
169
-+        instance['metadata']['driver_actions'] = {'bar': 'foo123'}
170 111
          flavor = ironic_utils.get_test_flavor()
112
++
113
++        image_meta = ironic_utils.get_test_image_meta_object()
114
++        mock_get_depl_conf_opts.return_value = {'foo': 'bar123'}
115
++        instance['metadata']['driver_actions'] = 'test_driver_actions'
171 116
 +
172 117
          self.driver._add_driver_fields(node, instance, image_meta, flavor)
173 118
 +
174 119
          expected_patch = [{'path': '/instance_info/image_source', 'op': 'add',
175
-                            'value': image_meta['id']},
120
+                            'value': image_meta.id},
176 121
                            {'path': '/instance_info/root_gb', 'op': 'add',
177
-@@ -735,21 +814,96 @@ class IronicDriverTestCase(test.NoDBTestCase):
178
-                           {'path': '/instance_info/swap_mb', 'op': 'add',
179
-                            'value': str(flavor['swap'])},
122
+@@ -920,21 +970,96 @@ class IronicDriverTestCase(test.NoDBTestCase):
123
+                           {'path': '/instance_info/local_gb', 'op': 'add',
124
+                            'value': str(node.properties.get('local_gb', 0))},
180 125
                            {'path': '/instance_uuid', 'op': 'add',
181 126
 -                           'value': instance.uuid}]
182 127
 +                           'value': instance.uuid},
@@ -185,10 +130,10 @@ index b19c6eb..6305ff7 100644
185 130
 +                           'value': {'foo': 'bar123'}},
186 131
 +                          {'path': '/instance_info/driver_actions',
187 132
 +                           'op': 'add',
188
-+                           'value': {'bar': 'foo123'}},
133
++                           'value': 'test_driver_actions'},
189 134
 +                          ]
190 135
          mock_update.assert_called_once_with(node.uuid, expected_patch)
191
- 
136
+
192 137
      @mock.patch.object(FAKE_CLIENT.node, 'update')
193 138
      def test__add_driver_fields_fail(self, mock_update):
194 139
          mock_update.side_effect = ironic_exception.BadRequest()
@@ -199,16 +144,16 @@ index b19c6eb..6305ff7 100644
199 144
 +            self.ctx,
200 145
 +            node=node.uuid,
201 146
 +            expected_attrs=('metadata',))
202
-         image_meta = ironic_utils.get_test_image_meta()
147
+         image_meta = ironic_utils.get_test_image_meta_object()
203 148
          flavor = ironic_utils.get_test_flavor()
204 149
          self.assertRaises(exception.InstanceDeployFailure,
205 150
                            self.driver._add_driver_fields,
206 151
                            node, instance, image_meta, flavor)
207
- 
152
+
208 153
 +    def test__get_deploy_config_options_all_present(self):
209 154
 +        node = ironic_utils.get_test_node(
210 155
 +            driver='fake', driver_info={'deploy_config': "node-conf"})
211
-+        image_meta = ironic_utils.get_test_image_meta(
156
++        image_meta = ironic_utils.get_test_image_meta_object(
212 157
 +            deploy_config="image-conf")
213 158
 +        instance = fake_instance.fake_instance_obj(
214 159
 +            self.ctx, node=node.uuid, expected_attrs=('metadata',),
@@ -233,7 +178,7 @@ index b19c6eb..6305ff7 100644
233 178
 +                "image": "previous_image_conf",
234 179
 +            }}
235 180
 +        )
236
-+        image_meta = ironic_utils.get_test_image_meta(
181
++        image_meta = ironic_utils.get_test_image_meta_object(
237 182
 +            deploy_config="image-conf")
238 183
 +        instance = fake_instance.fake_instance_obj(
239 184
 +            self.ctx, node=node.uuid, expected_attrs=('metadata',))
@@ -248,7 +193,7 @@ index b19c6eb..6305ff7 100644
248 193
 +
249 194
 +    def test__get_deploy_config_options_some_present(self):
250 195
 +        node = ironic_utils.get_test_node(driver='fake')
251
-+        image_meta = ironic_utils.get_test_image_meta()
196
++        image_meta = ironic_utils.get_test_image_meta_object()
252 197
 +        instance = fake_instance.fake_instance_obj(
253 198
 +            self.ctx, node=node.uuid, expected_attrs=('metadata',),
254 199
 +            metadata={'deploy_config': "instance-conf"})
@@ -261,7 +206,7 @@ index b19c6eb..6305ff7 100644
261 206
 +
262 207
 +    def test__get_deploy_config_options_none_present(self):
263 208
 +        node = ironic_utils.get_test_node(driver='fake')
264
-+        image_meta = ironic_utils.get_test_image_meta()
209
++        image_meta = ironic_utils.get_test_image_meta_object()
265 210
 +        instance = fake_instance.fake_instance_obj(
266 211
 +            self.ctx, node=node.uuid, expected_attrs=('metadata',))
267 212
 +
@@ -274,7 +219,7 @@ index b19c6eb..6305ff7 100644
274 219
      @mock.patch.object(FAKE_CLIENT.node, 'update')
275 220
      def test__cleanup_deploy_good_with_flavor(self, mock_update):
276 221
          node = ironic_utils.get_test_node(driver='fake',
277
-@@ -781,8 +935,10 @@ class IronicDriverTestCase(test.NoDBTestCase):
222
+@@ -983,8 +1108,10 @@ class IronicDriverTestCase(test.NoDBTestCase):
278 223
          node = ironic_utils.get_test_node(driver='fake',
279 224
                                            instance_uuid=self.instance_uuid)
280 225
          flavor = ironic_utils.get_test_flavor(extra_specs={})
@@ -287,7 +232,7 @@ index b19c6eb..6305ff7 100644
287 232
          instance.flavor = flavor
288 233
          self.assertRaises(exception.InstanceTerminationFailure,
289 234
                            self.driver._cleanup_deploy,
290
-@@ -796,7 +952,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
235
+@@ -998,7 +1125,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
291 236
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
292 237
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
293 238
          flavor = ironic_utils.get_test_flavor()
@@ -295,9 +240,9 @@ index b19c6eb..6305ff7 100644
295 240
 +        instance = fake_instance.fake_instance_obj(
296 241
 +            self.ctx, node=node_uuid, expected_attrs=('metadata',))
297 242
          instance.flavor = flavor
298
- 
243
+
299 244
          mock_node.validate.return_value = ironic_utils.get_test_validation(
300
-@@ -821,7 +978,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
245
+@@ -1023,7 +1151,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
301 246
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
302 247
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
303 248
          flavor = ironic_utils.get_test_flavor()
@@ -307,7 +252,7 @@ index b19c6eb..6305ff7 100644
307 252
          instance.flavor = flavor
308 253
          mock_node.get.return_value = node
309 254
          mock_node.validate.return_value = ironic_utils.get_test_validation()
310
-@@ -851,7 +1009,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
255
+@@ -1053,7 +1182,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
311 256
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
312 257
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
313 258
          flavor = ironic_utils.get_test_flavor()
@@ -316,8 +261,8 @@ index b19c6eb..6305ff7 100644
316 261
 +            self.ctx, node=node_uuid, expected_attrs=('metadata',))
317 262
          instance.flavor = flavor
318 263
          image_meta = ironic_utils.get_test_image_meta()
319
- 
320
-@@ -880,7 +1039,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
264
+
265
+@@ -1082,7 +1212,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
321 266
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
322 267
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
323 268
          flavor = ironic_utils.get_test_flavor()
@@ -326,9 +271,9 @@ index b19c6eb..6305ff7 100644
326 271
 +            self.ctx, node=node_uuid, expected_attrs=('metadata',))
327 272
          instance.flavor = flavor
328 273
          image_meta = ironic_utils.get_test_image_meta()
329
- 
330
-@@ -912,7 +1072,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
331
-         fake_net_info = utils.get_test_network_info()
274
+
275
+@@ -1113,7 +1244,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
276
+         node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
332 277
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
333 278
          flavor = ironic_utils.get_test_flavor()
334 279
 -        instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
@@ -336,8 +281,8 @@ index b19c6eb..6305ff7 100644
336 281
 +            self.ctx, node=node_uuid, expected_attrs=('metadata',))
337 282
          instance.flavor = flavor
338 283
          image_meta = ironic_utils.get_test_image_meta()
339
- 
340
-@@ -945,7 +1106,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
284
+
285
+@@ -1146,7 +1278,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
341 286
          node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
342 287
          node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
343 288
          flavor = ironic_utils.get_test_flavor(ephemeral_gb=1)
@@ -347,71 +292,17 @@ index b19c6eb..6305ff7 100644
347 292
          instance.flavor = flavor
348 293
          mock_node.get_by_instance_uuid.return_value = node
349 294
          mock_node.set_provision_state.return_value = mock.MagicMock()
350
-@@ -957,12 +1119,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
351
- 
352
-     @mock.patch.object(FAKE_CLIENT, 'node')
353
-     @mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
354
--    def test_destroy(self, mock_cleanup_deploy, mock_node):
355
-+    def _test_destroy(self, state, mock_cleanup_deploy, mock_node):
356
-         node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
357
-         network_info = 'foo'
358
- 
359
-         node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
360
--                                          provision_state=ironic_states.ACTIVE)
361
-+                                          provision_state=state)
362
-         instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
363
- 
364
-         def fake_set_provision_state(*_):
365
-@@ -971,29 +1133,22 @@ class IronicDriverTestCase(test.NoDBTestCase):
366
-         mock_node.get_by_instance_uuid.return_value = node
367
-         mock_node.set_provision_state.side_effect = fake_set_provision_state
368
-         self.driver.destroy(self.ctx, instance, network_info, None)
369
--        mock_node.set_provision_state.assert_called_once_with(node_uuid,
370
--                                                              'deleted')
371
-+
372
-         mock_node.get_by_instance_uuid.assert_called_with(instance.uuid)
373
-         mock_cleanup_deploy.assert_called_with(self.ctx, node,
374
-                                                instance, network_info)
375
- 
376
--    @mock.patch.object(FAKE_CLIENT, 'node')
377
--    @mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
378
--    def test_destroy_ignore_unexpected_state(self, mock_cleanup_deploy,
379
--                                             mock_node):
380
--        node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
381
--        network_info = 'foo'
382
-+        # For states that makes sense check if set_provision_state has
383
-+        # been called
384
-+        if state in ironic_driver._UNPROVISION_STATES:
385
-+            mock_node.set_provision_state.assert_called_once_with(
386
-+                node_uuid, 'deleted')
387
-+        else:
388
-+            self.assertFalse(mock_node.set_provision_state.called)
389
- 
390
--        node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid,
391
--                                        provision_state=ironic_states.DELETING)
392
--        instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
393
--
394
--        mock_node.get_by_instance_uuid.return_value = node
395
--        self.driver.destroy(self.ctx, instance, network_info, None)
396
--        self.assertFalse(mock_node.set_provision_state.called)
397
--        mock_node.get_by_instance_uuid.assert_called_with(instance.uuid)
398
--        mock_cleanup_deploy.assert_called_with(self.ctx, node, instance,
399
--                                               network_info)
400
-+    def test_destroy(self):
401
-+        for state in ironic_states.PROVISION_STATE_LIST:
402
-+            self._test_destroy(state)
403
- 
404
-     @mock.patch.object(FAKE_CLIENT, 'node')
405
-     @mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
406
-@@ -1287,6 +1442,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
295
+@@ -1541,15 +1674,16 @@ class IronicDriverTestCase(test.NoDBTestCase):
407 296
          self.driver.refresh_instance_security_rules(fake_group)
408 297
          mock_risr.assert_called_once_with(fake_group)
409
- 
298
+
410 299
 +    @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options')
411 300
      @mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
412 301
      @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
413 302
      @mock.patch.object(FAKE_CLIENT.node, 'set_provision_state')
414
-@@ -1295,7 +1451,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
303
+     @mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields')
304
+-    @mock.patch.object(FAKE_CLIENT.node, 'get')
305
++    @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
415 306
      @mock.patch.object(objects.Instance, 'save')
416 307
      def _test_rebuild(self, mock_save, mock_get, mock_driver_fields,
417 308
                        mock_set_pstate, mock_looping, mock_wait_active,
@@ -420,10 +311,10 @@ index b19c6eb..6305ff7 100644
420 311
          node_uuid = uuidutils.generate_uuid()
421 312
          node = ironic_utils.get_test_node(uuid=node_uuid,
422 313
                                            instance_uuid=self.instance_uuid,
423
-@@ -1306,10 +1462,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
314
+@@ -1560,10 +1694,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
424 315
          flavor_id = 5
425 316
          flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal')
426
- 
317
+
427 318
 -        instance = fake_instance.fake_instance_obj(self.ctx,
428 319
 -                                                   uuid=self.instance_uuid,
429 320
 -                                                   node=node_uuid,
@@ -435,20 +326,29 @@ index b19c6eb..6305ff7 100644
435 326
 +            instance_type_id=flavor_id,
436 327
 +            expected_attrs=('metadata',))
437 328
          instance.flavor = flavor
438
- 
329
+
439 330
          fake_looping_call = FakeLoopingCall()
440
-@@ -1333,6 +1491,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
331
+@@ -1589,6 +1725,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
441 332
          fake_looping_call.start.assert_called_once_with(
442 333
              interval=CONF.ironic.api_retry_interval)
443 334
          fake_looping_call.wait.assert_called_once_with()
444 335
 +        mock_sb_options.assert_called_once_with(self.ctx, instance, node_uuid)
445
- 
336
+
446 337
      def test_rebuild_preserve_ephemeral(self):
447 338
          self._test_rebuild(preserve=True)
448
-@@ -1356,10 +1515,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
339
+@@ -1598,7 +1735,7 @@ class IronicDriverTestCase(test.NoDBTestCase):
340
+
341
+     @mock.patch.object(FAKE_CLIENT.node, 'set_provision_state')
342
+     @mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields')
343
+-    @mock.patch.object(FAKE_CLIENT.node, 'get')
344
++    @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
345
+     @mock.patch.object(objects.Instance, 'save')
346
+     def test_rebuild_failures(self, mock_save, mock_get, mock_driver_fields,
347
+                               mock_set_pstate):
348
+@@ -1612,10 +1749,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
449 349
          flavor_id = 5
450 350
          flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal')
451
- 
351
+
452 352
 -        instance = fake_instance.fake_instance_obj(self.ctx,
453 353
 -                                                   uuid=self.instance_uuid,
454 354
 -                                                   node=node_uuid,
@@ -460,19 +360,19 @@ index b19c6eb..6305ff7 100644
460 360
 +            instance_type_id=flavor_id,
461 361
 +            expected_attrs=('metadata',))
462 362
          instance.flavor = flavor
463
- 
363
+
464 364
          exceptions = [
465
-@@ -1375,6 +1536,305 @@ class IronicDriverTestCase(test.NoDBTestCase):
365
+@@ -1631,6 +1770,316 @@ class IronicDriverTestCase(test.NoDBTestCase):
466 366
                  injected_files=None, admin_password=None, bdms=None,
467 367
                  detach_block_devices=None, attach_block_devices=None)
468
- 
368
+
469 369
 +    @mock.patch.object(ironic_driver.IronicDriver, '_do_rebuild')
470 370
 +    @mock.patch.object(ironic_driver.IronicDriver, '_get_switch_boot_options')
471 371
 +    @mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
472 372
 +    @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
473 373
 +    @mock.patch.object(FAKE_CLIENT.node, 'set_provision_state')
474 374
 +    @mock.patch.object(ironic_driver.IronicDriver, '_add_driver_fields')
475
-+    @mock.patch.object(FAKE_CLIENT.node, 'get')
375
++    @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
476 376
 +    @mock.patch.object(objects.Instance, 'save')
477 377
 +    def test_rebuild_multiboot_force_rebuild(self, mock_save, mock_get,
478 378
 +                                             mock_driver_fields,
@@ -486,7 +386,8 @@ index b19c6eb..6305ff7 100644
486 386
 +                                          instance_info={'multiboot': True})
487 387
 +        mock_get.return_value = node
488 388
 +
489
-+        image_meta = ironic_utils.get_test_image_meta()
389
++        image_meta = ironic_utils.get_test_image_meta_object()
390
++
490 391
 +        flavor_id = 5
491 392
 +        flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal')
492 393
 +
@@ -502,11 +403,14 @@ index b19c6eb..6305ff7 100644
502 403
 +        fake_looping_call = FakeLoopingCall()
503 404
 +        mock_looping.return_value = fake_looping_call
504 405
 +
505
-+        self.driver.rebuild(
506
-+            context=self.ctx, instance=instance, image_meta=image_meta,
507
-+            injected_files=None, admin_password=None, bdms=None,
508
-+            detach_block_devices=None, attach_block_devices=None,
509
-+            preserve_ephemeral=False)
406
++        with mock.patch.object(objects.ImageMeta,
407
++                               'from_dict') as mock_image_meta_from_dict:
408
++            mock_image_meta_from_dict.return_value = image_meta
409
++            self.driver.rebuild(
410
++                context=self.ctx, instance=instance, image_meta=image_meta,
411
++                injected_files=None, admin_password=None, bdms=None,
412
++                detach_block_devices=None, attach_block_devices=None,
413
++                preserve_ephemeral=False)
510 414
 +
511 415
 +        rebuild_mock.assert_called_once_with(
512 416
 +            self.ctx, FAKE_CLIENT_WRAPPER, node, instance, image_meta,
@@ -517,18 +421,25 @@ index b19c6eb..6305ff7 100644
517 421
 +            block_device_info=None,
518 422
 +            preserve_ephemeral=False)
519 423
 +
424
++    @mock.patch.object(objects.ImageMeta, 'from_dict')
425
++    @mock.patch.object(FAKE_CLIENT.node, 'get_by_instance_uuid')
520 426
 +    @mock.patch.object(FAKE_CLIENT.node, 'get')
521 427
 +    @mock.patch.object(ironic_driver.IronicDriver, '_do_switch_boot_device')
522 428
 +    @mock.patch.object(objects.Instance, 'save')
523 429
 +    def test_rebuild_multiboot_switch_boot(self, mock_save,
524
-+                                           mock_sb, mock_get):
430
++                                           mock_sb, mock_get,
431
++                                           mock_get_by_instance,
432
++                                           mock_image_meta_from_dict):
525 433
 +        node_uuid = uuidutils.generate_uuid()
526 434
 +        node = ironic_utils.get_test_node(uuid=node_uuid,
527 435
 +                                          instance_uuid=self.instance_uuid,
528 436
 +                                          instance_type_id=5,
529 437
 +                                          instance_info={'multiboot': True})
530
-+        mock_get.return_value = node
531
-+        image_meta = ironic_utils.get_test_image_meta()
438
++        mock_get.return_value = mock_get_by_instance.return_value = node
439
++
440
++        image_meta = ironic_utils.get_test_image_meta_object()
441
++        mock_image_meta_from_dict.return_value = image_meta
442
++
532 443
 +        flavor_id = 5
533 444
 +        instance = fake_instance.fake_instance_obj(
534 445
 +            self.ctx,
@@ -558,7 +469,7 @@ index b19c6eb..6305ff7 100644
558 469
 +                                          instance_type_id=5,
559 470
 +                                          instance_info={'multiboot': True})
560 471
 +
561
-+        image_meta = ironic_utils.get_test_image_meta()
472
++        image_meta = ironic_utils.get_test_image_meta_object()
562 473
 +        flavor_id = 5
563 474
 +        instance = fake_instance.fake_instance_obj(
564 475
 +            self.ctx,
@@ -573,13 +484,13 @@ index b19c6eb..6305ff7 100644
573 484
 +            node, instance, image_meta)
574 485
 +
575 486
 +        vp_mock.assert_called_once_with(node_uuid, 'switch_boot',
576
-+                                        {'image': image_meta['id'],
487
++                                        {'image': image_meta.id,
577 488
 +                                         'ssh_user': 'usr1',
578 489
 +                                         'ssh_key': 'key1'})
579 490
 +        sp_mock.assert_called_once_with(node_uuid, 'reboot')
580 491
 +        upd_mock.assert_called_once_with(
581 492
 +            node_uuid, [{'path': '/instance_info/image_source', 'op': 'add',
582
-+                         'value': image_meta['id']}])
493
++                         'value': image_meta.id}])
583 494
 +
584 495
 +    @mock.patch.object(FAKE_CLIENT.node, 'vendor_passthru')
585 496
 +    @mock.patch.object(FAKE_CLIENT.node, 'set_power_state')
@@ -633,7 +544,7 @@ index b19c6eb..6305ff7 100644
633 544
 +                           'image_source': 'original_image',
634 545
 +                           })
635 546
 +
636
-+        image_meta = ironic_utils.get_test_image_meta()
547
++        image_meta = ironic_utils.get_test_image_meta_object()
637 548
 +        flavor_id = 5
638 549
 +        instance = fake_instance.fake_instance_obj(
639 550
 +            self.ctx,
@@ -671,7 +582,7 @@ index b19c6eb..6305ff7 100644
671 582
 +                           'image_source': 'original_image',
672 583
 +                           })
673 584
 +
674
-+        image_meta = ironic_utils.get_test_image_meta()
585
++        image_meta = ironic_utils.get_test_image_meta_object()
675 586
 +        flavor_id = 5
676 587
 +        instance = fake_instance.fake_instance_obj(
677 588
 +            self.ctx,
@@ -707,7 +618,7 @@ index b19c6eb..6305ff7 100644
707 618
 +                           'image_source': 'original_image',
708 619
 +                           })
709 620
 +
710
-+        image_meta = ironic_utils.get_test_image_meta()
621
++        image_meta = ironic_utils.get_test_image_meta_object()
711 622
 +        flavor_id = 5
712 623
 +        instance = fake_instance.fake_instance_obj(
713 624
 +            self.ctx,
@@ -744,7 +655,7 @@ index b19c6eb..6305ff7 100644
744 655
 +                           'image_source': 'original_image',
745 656
 +                           })
746 657
 +
747
-+        image_meta = ironic_utils.get_test_image_meta()
658
++        image_meta = ironic_utils.get_test_image_meta_object()
748 659
 +        flavor_id = 5
749 660
 +        instance = fake_instance.fake_instance_obj(
750 661
 +            self.ctx,
@@ -765,24 +676,25 @@ index b19c6eb..6305ff7 100644
765 676
 +                      instance.metadata['switch_boot_error'])
766 677
 +        mock_save.assert_called_once_with()
767 678
 +
768
- 
679
+
769 680
  @mock.patch.object(instance_metadata, 'InstanceMetadata')
770 681
  @mock.patch.object(configdrive, 'ConfigDriveBuilder')
771 682
 diff --git a/nova/tests/unit/virt/ironic/utils.py b/nova/tests/unit/virt/ironic/utils.py
772
-index d43f290..f3ec825 100644
683
+index 0e67919..66eede3 100644
773 684
 --- a/nova/tests/unit/virt/ironic/utils.py
774 685
 +++ b/nova/tests/unit/virt/ironic/utils.py
775
-@@ -42,6 +42,7 @@ def get_test_node(**kw):
686
+@@ -39,7 +39,7 @@ def get_test_node(**kw):
687
+                                                  ironic_states.NOSTATE),
688
+                 'last_error': kw.get('last_error'),
689
+                 'instance_uuid': kw.get('instance_uuid'),
690
+-                'instance_info': kw.get('instance_info'),
691
++                'instance_info': kw.get('instance_info', {}),
776 692
                  'driver': kw.get('driver', 'fake'),
777 693
                  'driver_info': kw.get('driver_info', {}),
778 694
                  'properties': kw.get('properties', {}),
779
-+                'instance_info': kw.get('instance_info', {}),
780
-                 'reservation': kw.get('reservation'),
781
-                 'maintenance': kw.get('maintenance', False),
782
-                 'extra': kw.get('extra', {}),
783
-@@ -72,7 +73,11 @@ def get_test_flavor(**kw):
784
- 
785
- 
695
+@@ -91,7 +91,11 @@ def get_test_flavor(**kw):
696
+
697
+
786 698
  def get_test_image_meta(**kw):
787 699
 -    return {'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc')}
788 700
 +    return {'id': kw.get('id', 'cccccccc-cccc-cccc-cccc-cccccccccccc'),
@@ -790,43 +702,24 @@ index d43f290..f3ec825 100644
790 702
 +                'deploy_config': kw.get('deploy_config', ''),
791 703
 +                'driver_actions': kw.get('driver_actions', ''),
792 704
 +            }}
793
- 
794
- 
795
- class FakePortClient(object):
796
-@@ -110,6 +115,9 @@ class FakeNodeClient(object):
705
+
706
+
707
+ def get_test_image_meta_object(**kw):
708
+@@ -134,6 +138,9 @@ class FakeNodeClient(object):
797 709
      def validate(self, node_uuid):
798 710
          pass
799
- 
711
+
800 712
 +    def vendor_passthru(self, node_uuid, method, args):
801 713
 +        pass
802 714
 +
803
- 
715
+
804 716
  class FakeClient(object):
805
- 
717
+
806 718
 diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py
807
-index b21c782..81dcdba 100644
719
+index 194221e..062f3d7 100644
808 720
 --- a/nova/virt/ironic/driver.py
809 721
 +++ b/nova/virt/ironic/driver.py
810
-@@ -40,6 +40,7 @@ from nova.compute import hv_type
811
- from nova.compute import power_state
812
- from nova.compute import task_states
813
- from nova.compute import vm_mode
814
-+from nova.compute import vm_states
815
- from nova import context as nova_context
816
- from nova import exception
817
- from nova.i18n import _
818
-@@ -107,6 +108,10 @@ _POWER_STATE_MAP = {
819
-     ironic_states.POWER_OFF: power_state.SHUTDOWN,
820
- }
821
- 
822
-+_UNPROVISION_STATES = (ironic_states.ACTIVE, ironic_states.DEPLOYFAIL,
823
-+                       ironic_states.ERROR, ironic_states.DEPLOYWAIT,
824
-+                       ironic_states.DEPLOYING)
825
-+
826
- 
827
- def map_power_state(state):
828
-     try:
829
-@@ -326,6 +331,17 @@ class IronicDriver(virt_driver.ComputeDriver):
722
+@@ -391,6 +391,17 @@ class IronicDriver(virt_driver.ComputeDriver):
830 723
          # Associate the node with an instance
831 724
          patch.append({'path': '/instance_uuid', 'op': 'add',
832 725
                        'value': instance.uuid})
@@ -844,35 +737,21 @@ index b21c782..81dcdba 100644
844 737
          try:
845 738
              self.ironicclient.call('node.update', node.uuid, patch)
846 739
          except ironic.exc.BadRequest:
847
-@@ -335,6 +351,13 @@ class IronicDriver(virt_driver.ComputeDriver):
740
+@@ -400,6 +411,12 @@ class IronicDriver(virt_driver.ComputeDriver):
848 741
              LOG.error(msg)
849 742
              raise exception.InstanceDeployFailure(msg)
850
- 
743
+
851 744
 +    def _update_driver_fields_after_switch_boot(self, context, node,
852 745
 +                                                instance, image_meta):
853
-+        patch = []
854
-+        patch.append({'path': '/instance_info/image_source', 'op': 'add',
855
-+                      'value': image_meta.get('id')})
746
++        patch = [{'path': '/instance_info/image_source', 'op': 'add',
747
++                      'value': image_meta.id}]
856 748
 +        self.ironicclient.call('node.update', node.uuid, patch)
857 749
 +
858 750
      def _cleanup_deploy(self, context, node, instance, network_info,
859 751
                          flavor=None):
860 752
          if flavor is None:
861
-@@ -358,6 +381,12 @@ class IronicDriver(virt_driver.ComputeDriver):
862
- 
863
-     def _wait_for_active(self, ironicclient, instance):
864
-         """Wait for the node to be marked as ACTIVE in Ironic."""
865
-+        instance.refresh()
866
-+        if (instance.task_state == task_states.DELETING or
867
-+            instance.vm_state in (vm_states.ERROR, vm_states.DELETED)):
868
-+            raise exception.InstanceDeployFailure(
869
-+                _("Instance %s provisioning was aborted") % instance.uuid)
870
-+
871
-         node = _validate_instance_and_node(ironicclient, instance)
872
-         if node.provision_state == ironic_states.ACTIVE:
873
-             # job is done
874
-@@ -714,9 +743,19 @@ class IronicDriver(virt_driver.ComputeDriver):
875
- 
753
+@@ -807,9 +824,19 @@ class IronicDriver(virt_driver.ComputeDriver):
754
+
876 755
          # trigger the node deploy
877 756
          try:
878 757
 -            self.ironicclient.call("node.set_provision_state", node_uuid,
@@ -894,7 +773,7 @@ index b21c782..81dcdba 100644
894 773
          except Exception as e:
895 774
              with excutils.save_and_reraise_exception():
896 775
                  msg = (_LE("Failed to request Ironic to provision instance "
897
-@@ -739,6 +778,17 @@ class IronicDriver(virt_driver.ComputeDriver):
776
+@@ -834,6 +861,17 @@ class IronicDriver(virt_driver.ComputeDriver):
898 777
                               {'instance': instance.uuid,
899 778
                                'node': node_uuid})
900 779
                  self.destroy(context, instance, network_info)
@@ -909,27 +788,21 @@ index b21c782..81dcdba 100644
909 788
 +            available_images = [img['image_name'] for img in
910 789
 +                                multiboot_meta.get('elements', [])]
911 790
 +            instance.metadata['available_images'] = str(available_images)
912
- 
791
+
913 792
      def _unprovision(self, ironicclient, instance, node):
914 793
          """This method is called from destroy() to unprovision
915
-@@ -814,10 +864,7 @@ class IronicDriver(virt_driver.ComputeDriver):
916
-             #             without raising any exceptions.
917
-             return
918
- 
919
--        if node.provision_state in (ironic_states.ACTIVE,
920
--                                    ironic_states.DEPLOYFAIL,
921
--                                    ironic_states.ERROR,
922
--                                    ironic_states.DEPLOYWAIT):
923
-+        if node.provision_state in _UNPROVISION_STATES:
924
-             self._unprovision(self.ironicclient, instance, node)
925
- 
926
-         self._cleanup_deploy(context, node, instance, network_info)
927
-@@ -1074,24 +1121,127 @@ class IronicDriver(virt_driver.ComputeDriver):
928
-         node_uuid = instance.node
929
-         node = self.ironicclient.call("node.get", node_uuid)
930
- 
931
--        self._add_driver_fields(node, instance, image_meta, instance.flavor,
932
--                                preserve_ephemeral)
794
+@@ -1188,16 +1226,102 @@ class IronicDriver(virt_driver.ComputeDriver):
795
+         instance.task_state = task_states.REBUILD_SPAWNING
796
+         instance.save(expected_task_state=[task_states.REBUILDING])
797
+
798
+-        node_uuid = instance.node
799
+-        node = self.ironicclient.call("node.get", node_uuid)
800
++        # NOTE(oberezovskyi): Required to get real node uuid assigned to nova
801
++        # instance. Workaround after
802
++        # Change-Id: I0233f964d8f294f0ffd9edcb16b1aaf93486177f
803
++        node = self.ironicclient.call("node.get_by_instance_uuid",
804
++                                      instance.uuid)
805
++
933 806
 +        # NOTE(lobur): set_provision_state to
934 807
 +        # ACTIVE, REBUILD, and switch_boot_device are the only Ironic API
935 808
 +        # calls where the user context needs to be passed to Ironic. This
@@ -957,28 +830,32 @@ index b21c782..81dcdba 100644
957 830
 +                recreate=recreate,
958 831
 +                block_device_info=block_device_info,
959 832
 +                preserve_ephemeral=preserve_ephemeral)
960
- 
833
+
834
+-        self._add_driver_fields(node, instance, image_meta, instance.flavor,
835
+-                                preserve_ephemeral)
836
++        self._get_switch_boot_options(context, instance, node.uuid)
837
+
961 838
 -        # Trigger the node rebuild/redeploy.
962
-+        self._get_switch_boot_options(context, instance, node_uuid)
963
-+
964 839
 +    def _do_switch_boot_device(self, context, ironicclient, node, instance,
965 840
 +                               image_meta):
966 841
 +        old_image_ref = node.instance_info.get("image_source", "")
967
-+        try:
842
+         try:
843
+-            self.ironicclient.call("node.set_provision_state",
844
+-                              node_uuid, ironic_states.REBUILD)
968 845
 +            sb_user, sb_key = self._get_switch_boot_user_key(instance.metadata)
969 846
 +            args = dict(ssh_user=sb_user,
970 847
 +                        ssh_key=sb_key,
971
-+                        image=image_meta['id'])
848
++                        image=image_meta.id)
972 849
 +            ironicclient.call("node.vendor_passthru",
973 850
 +                              node.uuid, "switch_boot",
974 851
 +                              args)
975 852
 +            self.ironicclient.call("node.set_power_state", node.uuid, 'reboot')
976 853
 +            self._update_driver_fields_after_switch_boot(
977 854
 +                context, node, instance, image_meta)
978
-+        except (exception.InvalidMetadata,       # Bad Nova API call
979
-+                exception.NovaException,         # Retry failed
855
++        except (exception.InvalidMetadata,  # Bad Nova API call
856
++                exception.NovaException,  # Retry failed
980 857
 +                ironic.exc.InternalServerError,  # Validations
981
-+                ironic.exc.BadRequest) as e:     # Maintenance or no such API
858
++                ironic.exc.BadRequest) as e:  # Maintenance or no such API
982 859
 +            # Ironic Vendor API always return 200/400/500, so the only way
983 860
 +            # to check the error is introspecting its message.
984 861
 +            if "Already in desired boot device" in six.text_type(e):
@@ -1004,7 +881,7 @@ index b21c782..81dcdba 100644
1004 881
 +        else:
1005 882
 +            raise exception.InvalidMetadata(
1006 883
 +                reason="To trigger switch boot device flow, both 'sb_user' "
1007
-+                       "and 'sb_key' metadata params are required. To "
884
++                       "and 'sb_key' metadata params are required. To "s
1008 885
 +                       "trigger a standard rebuild flow, use "
1009 886
 +                       "force_rebuild=True metadata flag.")
1010 887
 +
@@ -1017,31 +894,17 @@ index b21c782..81dcdba 100644
1017 894
 +
1018 895
 +        self._add_driver_fields(node, instance, image_meta,
1019 896
 +                                instance.flavor, preserve_ephemeral)
1020
-         try:
1021
--            self.ironicclient.call("node.set_provision_state",
1022
--                              node_uuid, ironic_states.REBUILD)
897
++        try:
1023 898
 +
1024 899
 +            ironicclient.call("node.set_provision_state",
1025 900
 +                              node.uuid, ironic_states.REBUILD)
1026 901
          except (exception.NovaException,         # Retry failed
1027 902
                  ironic.exc.InternalServerError,  # Validations
1028 903
                  ironic.exc.BadRequest) as e:     # Maintenance
1029
-             msg = (_("Failed to request Ironic to rebuild instance "
1030
--                     "%(inst)s: %(reason)s") % {'inst': instance.uuid,
1031
--                                                'reason': six.text_type(e)})
1032
-+                     "%(inst)s: %(reason)s") %
1033
-+                   {'inst': instance.uuid,
1034
-+                    'reason': six.text_type(e)})
1035
-             raise exception.InstanceDeployFailure(msg)
1036
- 
1037
--        # Although the target provision state is REBUILD, it will actually go
1038
--        # to ACTIVE once the redeploy is finished.
1039
-+        # Although the target provision state is REBUILD, it will
1040
-+        # actually go to ACTIVE once the redeploy is finished.
1041
-         timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active,
1042
-                                                      self.ironicclient,
904
+@@ -1213,3 +1337,22 @@ class IronicDriver(virt_driver.ComputeDriver):
1043 905
                                                       instance)
1044 906
          timer.start(interval=CONF.ironic.api_retry_interval).wait()
907
+         LOG.info(_LI('Instance was successfully rebuilt'), instance=instance)
1045 908
 +
1046 909
 +    def _get_deploy_config_options(self, node, instance, image_meta):
1047 910
 +        # Taking into account previous options, if any. This is to support
@@ -1051,7 +914,7 @@ index b21c782..81dcdba 100644
1051 914
 +        res = node.instance_info.get('deploy_config_options', {})
1052 915
 +
1053 916
 +        curr_options = {
1054
-+            'image': image_meta.get('properties', {}).get('deploy_config', ''),
917
++            'image': image_meta.properties.get('deploy_config', ''),
1055 918
 +            'instance': instance.metadata.get('deploy_config', ''),
1056 919
 +            'node': node.driver_info.get('deploy_config', ''),
1057 920
 +        }
@@ -1061,21 +924,3 @@ index b21c782..81dcdba 100644
1061 924
 +        # Override previous by current.
1062 925
 +        res.update(curr_options)
1063 926
 +        return res
1064
-diff --git a/nova/virt/ironic/ironic_states.py b/nova/virt/ironic/ironic_states.py
1065
-index e521f16..a02ddcf 100644
1066
---- a/nova/virt/ironic/ironic_states.py
1067
-+++ b/nova/virt/ironic/ironic_states.py
1068
-@@ -138,3 +138,13 @@ POWER_OFF = 'power off'
1069
- 
1070
- REBOOT = 'rebooting'
1071
- """ Node is rebooting. """
1072
-+
1073
-+##################
1074
-+# Helper constants
1075
-+##################
1076
-+
1077
-+PROVISION_STATE_LIST = (NOSTATE, MANAGEABLE, AVAILABLE, ACTIVE, DEPLOYWAIT,
1078
-+                        DEPLOYING, DEPLOYFAIL, DEPLOYDONE, DELETING, DELETED,
1079
-+                        CLEANING, CLEANFAIL, ERROR, REBUILD,
1080
-+                        INSPECTING, INSPECTFAIL)
1081
-+""" A list of all provision states. """

Loading…
Cancel
Save