Browse Source

Accept hostname in reserve_node in addition to provision_node

With the allocation API we will need to provide the hostname as
the allocation name. Thus, we have to do it earlier.

Change-Id: I8afd8af23ad929fd9768e95a82fecd114fdcbfd9
Dmitry Tantsur 1 month ago
parent
commit
9be7472236

+ 2
- 2
metalsmith/_cmd.py View File

@@ -71,14 +71,14 @@ def _do_deploy(api, args, formatter):
71 71
                             conductor_group=args.conductor_group,
72 72
                             capabilities=capabilities,
73 73
                             traits=args.trait,
74
-                            candidates=args.candidate)
74
+                            candidates=args.candidate,
75
+                            hostname=args.hostname)
75 76
     instance = api.provision_node(node,
76 77
                                   image=source,
77 78
                                   nics=args.nics,
78 79
                                   root_size_gb=args.root_size,
79 80
                                   swap_size_mb=args.swap_size,
80 81
                                   config=config,
81
-                                  hostname=args.hostname,
82 82
                                   netboot=args.netboot,
83 83
                                   wait=wait)
84 84
     formatter.deploy(instance)

+ 3
- 2
metalsmith/_config.py View File

@@ -60,13 +60,14 @@ class InstanceConfig(object):
60 60
             kwargs.setdefault('ssh_authorized_keys', self.ssh_keys)
61 61
         self.users.append(kwargs)
62 62
 
63
-    def build_configdrive(self, node, hostname):
63
+    def build_configdrive(self, node):
64 64
         """Make the config drive.
65 65
 
66 66
         :param node: `Node` object.
67
-        :param hostname: instance hostname.
68 67
         :return: configdrive contents as a base64-encoded string.
69 68
         """
69
+        hostname = node.instance_info.get(_utils.HOSTNAME_FIELD)
70
+
70 71
         # NOTE(dtantsur): CirrOS does not understand lists
71 72
         if isinstance(self.ssh_keys, list):
72 73
             ssh_keys = {str(i): v for i, v in enumerate(self.ssh_keys)}

+ 1
- 1
metalsmith/_instance.py View File

@@ -94,7 +94,7 @@ class Instance(object):
94 94
     @property
95 95
     def hostname(self):
96 96
         """Node's hostname."""
97
-        return self._node.instance_info.get(_utils.GetNodeMixin.HOSTNAME_FIELD)
97
+        return self._node.instance_info.get(_utils.HOSTNAME_FIELD)
98 98
 
99 99
     def ip_addresses(self):
100 100
         """Returns IP addresses for this instance.

+ 22
- 23
metalsmith/_provisioner.py View File

@@ -34,7 +34,8 @@ LOG = logging.getLogger(__name__)
34 34
 
35 35
 _CREATED_PORTS = 'metalsmith_created_ports'
36 36
 _ATTACHED_PORTS = 'metalsmith_attached_ports'
37
-_PRESERVE_INSTANCE_INFO_KEYS = {'capabilities', 'traits'}
37
+_PRESERVE_INSTANCE_INFO_KEYS = {'capabilities', 'traits',
38
+                                _utils.HOSTNAME_FIELD}
38 39
 
39 40
 
40 41
 class Provisioner(_utils.GetNodeMixin):
@@ -67,7 +68,7 @@ class Provisioner(_utils.GetNodeMixin):
67 68
 
68 69
     def reserve_node(self, resource_class, conductor_group=None,
69 70
                      capabilities=None, traits=None, candidates=None,
70
-                     predicate=None):
71
+                     predicate=None, hostname=None):
71 72
         """Find and reserve a suitable node.
72 73
 
73 74
         Example::
@@ -88,10 +89,13 @@ class Provisioner(_utils.GetNodeMixin):
88 89
         :param predicate: Custom predicate to run on nodes. A callable that
89 90
             accepts a node and returns ``True`` if it should be included,
90 91
             ``False`` otherwise. Any exceptions are propagated to the caller.
92
+        :param hostname: Hostname to assign to the instance. Defaults to the
93
+            node's name or UUID.
91 94
         :return: reserved `Node` object.
92 95
         :raises: :py:class:`metalsmith.exceptions.ReservationFailed`
93 96
         """
94 97
         capabilities = capabilities or {}
98
+        self._check_hostname(hostname)
95 99
 
96 100
         if candidates:
97 101
             nodes = [self._get_node(node) for node in candidates]
@@ -127,7 +131,8 @@ class Provisioner(_utils.GetNodeMixin):
127 131
         if traits:
128 132
             instance_info['traits'] = traits
129 133
         reserver = _scheduler.IronicReserver(self.connection,
130
-                                             instance_info)
134
+                                             instance_info,
135
+                                             hostname)
131 136
 
132 137
         node = _scheduler.schedule_node(nodes, filters, reserver,
133 138
                                         dry_run=self._dry_run)
@@ -170,33 +175,25 @@ class Provisioner(_utils.GetNodeMixin):
170 175
 
171 176
         return node
172 177
 
173
-    def _check_hostname(self, node, hostname):
178
+    def _check_hostname(self, hostname, node=None):
174 179
         """Check the provided host name.
175 180
 
176
-        If the ``hostname`` is not provided, use either the name or the UUID,
177
-        whichever is appropriate for a host name.
178
-
179
-        :return: appropriate hostname
180 181
         :raises: ValueError on inappropriate value of ``hostname``
181 182
         """
182 183
         if hostname is None:
183
-            if node.name and _utils.is_hostname_safe(node.name):
184
-                return node.name
185
-            else:
186
-                return node.id
184
+            return
187 185
 
188 186
         if not _utils.is_hostname_safe(hostname):
189 187
             raise ValueError("%s cannot be used as a hostname" % hostname)
190 188
 
191 189
         existing = self._find_node_by_hostname(hostname)
192
-        if existing is not None and existing.id != node.id:
190
+        if (existing is not None and node is not None
191
+                and existing.id != node.id):
193 192
             raise ValueError("The following node already uses hostname "
194 193
                              "%(host)s: %(node)s" %
195 194
                              {'host': hostname,
196 195
                               'node': _utils.log_res(existing)})
197 196
 
198
-        return hostname
199
-
200 197
     def provision_node(self, node, image, nics=None, root_size_gb=None,
201 198
                        swap_size_mb=None, config=None, hostname=None,
202 199
                        netboot=False, capabilities=None, traits=None,
@@ -234,8 +231,8 @@ class Provisioner(_utils.GetNodeMixin):
234 231
             to specify it for a whole disk image.
235 232
         :param config: :py:class:`metalsmith.InstanceConfig` object with
236 233
             the configuration to pass to the instance.
237
-        :param hostname: Hostname to assign to the instance. Defaults to the
238
-            node's name or UUID.
234
+        :param hostname: Hostname to assign to the instance. If provided,
235
+            overrides the ``hostname`` passed to ``reserve_node``.
239 236
         :param netboot: Whether to use networking boot for final instances.
240 237
         :param capabilities: Requested capabilities of the node. If present,
241 238
             overwrites the capabilities set by :meth:`reserve_node`.
@@ -261,7 +258,7 @@ class Provisioner(_utils.GetNodeMixin):
261 258
         nics = _nics.NICs(self.connection, node, nics)
262 259
 
263 260
         try:
264
-            hostname = self._check_hostname(node, hostname)
261
+            self._check_hostname(hostname, node=node)
265 262
             root_size_gb = _utils.get_root_disk(root_size_gb, node)
266 263
 
267 264
             image._validate(self.connection)
@@ -283,7 +280,12 @@ class Provisioner(_utils.GetNodeMixin):
283 280
             instance_info = self._clean_instance_info(node.instance_info)
284 281
             instance_info['root_gb'] = root_size_gb
285 282
             instance_info['capabilities'] = capabilities
286
-            instance_info[self.HOSTNAME_FIELD] = hostname
283
+            if hostname:
284
+                instance_info[_utils.HOSTNAME_FIELD] = hostname
285
+            elif not instance_info.get(_utils.HOSTNAME_FIELD):
286
+                instance_info[_utils.HOSTNAME_FIELD] = _utils.default_hostname(
287
+                    node)
288
+
287 289
             extra = node.extra.copy()
288 290
             extra[_CREATED_PORTS] = nics.created_ports
289 291
             extra[_ATTACHED_PORTS] = nics.attached_ports
@@ -303,10 +305,7 @@ class Provisioner(_utils.GetNodeMixin):
303 305
 
304 306
             LOG.debug('Generating a configdrive for node %s',
305 307
                       _utils.log_res(node))
306
-            cd = config.build_configdrive(node, hostname)
307
-            # TODO(dtantsur): move this to openstacksdk?
308
-            if not isinstance(cd, six.string_types):
309
-                cd = cd.decode('utf-8')
308
+            cd = config.build_configdrive(node)
310 309
             LOG.debug('Starting provisioning of node %s', _utils.log_res(node))
311 310
             self.connection.baremetal.set_node_provision_state(
312 311
                 node, 'active', config_drive=cd)

+ 9
- 1
metalsmith/_scheduler.py View File

@@ -261,10 +261,11 @@ class CustomPredicateFilter(Filter):
261 261
 
262 262
 class IronicReserver(Reserver):
263 263
 
264
-    def __init__(self, connection, instance_info=None):
264
+    def __init__(self, connection, instance_info=None, hostname=None):
265 265
         self._connection = connection
266 266
         self._failed_nodes = []
267 267
         self._iinfo = instance_info or {}
268
+        self.hostname = hostname
268 269
 
269 270
     def validate(self, node):
270 271
         try:
@@ -276,10 +277,17 @@ class IronicReserver(Reserver):
276 277
             LOG.warning(message)
277 278
             raise exceptions.ValidationFailed(message)
278 279
 
280
+    def _get_hostname(self, node):
281
+        if self.hostname is None:
282
+            return _utils.default_hostname(node)
283
+        else:
284
+            return self.hostname
285
+
279 286
     def __call__(self, node):
280 287
         try:
281 288
             self.validate(node)
282 289
             iinfo = dict(node.instance_info or {}, **self._iinfo)
290
+            iinfo[_utils.HOSTNAME_FIELD] = self._get_hostname(node)
283 291
             return self._connection.baremetal.update_node(
284 292
                 node, instance_id=node.id, instance_info=iinfo)
285 293
         except sdk_exc.SDKException:

+ 11
- 3
metalsmith/_utils.py View File

@@ -107,11 +107,19 @@ class DuplicateHostname(sdk_exc.SDKException, exceptions.Error):
107 107
     pass
108 108
 
109 109
 
110
+HOSTNAME_FIELD = 'metalsmith_hostname'
111
+
112
+
113
+def default_hostname(node):
114
+    if node.name and is_hostname_safe(node.name):
115
+        return node.name
116
+    else:
117
+        return node.id
118
+
119
+
110 120
 class GetNodeMixin(object):
111 121
     """A helper mixin for getting nodes with hostnames."""
112 122
 
113
-    HOSTNAME_FIELD = 'metalsmith_hostname'
114
-
115 123
     _node_list = None
116 124
 
117 125
     def _available_nodes(self):
@@ -128,7 +136,7 @@ class GetNodeMixin(object):
128 136
         """A helper to find a node by metalsmith hostname."""
129 137
         nodes = self._node_list or self._nodes_for_lookup()
130 138
         existing = [n for n in nodes
131
-                    if n.instance_info.get(self.HOSTNAME_FIELD) == hostname]
139
+                    if n.instance_info.get(HOSTNAME_FIELD) == hostname]
132 140
         if len(existing) > 1:
133 141
             raise DuplicateHostname(
134 142
                 "More than one node found with hostname %(host)s: %(nodes)s" %

+ 3
- 3
metalsmith/test/test_cmd.py View File

@@ -46,7 +46,8 @@ class TestDeploy(testtools.TestCase):
46 46
                                 conductor_group=None,
47 47
                                 capabilities={},
48 48
                                 traits=[],
49
-                                candidates=None)
49
+                                candidates=None,
50
+                                hostname=None)
50 51
         reserve_defaults.update(reserve_args)
51 52
 
52 53
         provision_defaults = dict(image=mock.ANY,
@@ -54,7 +55,6 @@ class TestDeploy(testtools.TestCase):
54 55
                                   root_size_gb=None,
55 56
                                   swap_size_mb=None,
56 57
                                   config=mock.ANY,
57
-                                  hostname=None,
58 58
                                   netboot=False,
59 59
                                   wait=1800)
60 60
         provision_defaults.update(provision_args)
@@ -369,7 +369,7 @@ class TestDeploy(testtools.TestCase):
369 369
 
370 370
         args = ['deploy', '--network', 'mynet', '--image', 'myimg',
371 371
                 '--hostname', 'host', '--resource-class', 'compute']
372
-        self._check(mock_pr, args, {}, {'hostname': 'host'})
372
+        self._check(mock_pr, args, {'hostname': 'host'}, {})
373 373
 
374 374
         self.mock_print.assert_has_calls([
375 375
             mock.call(mock.ANY, node='123', state='ACTIVE'),

+ 4
- 1
metalsmith/test/test_config.py View File

@@ -20,6 +20,7 @@ from openstack.baremetal import configdrive
20 20
 import testtools
21 21
 
22 22
 from metalsmith import _config
23
+from metalsmith import _utils
23 24
 
24 25
 
25 26
 class TestInstanceConfig(testtools.TestCase):
@@ -38,9 +39,11 @@ class TestInstanceConfig(testtools.TestCase):
38 39
                       'files': [],
39 40
                       'meta': {}}
40 41
         expected_m.update(expected_metadata)
42
+        self.node.instance_info = {_utils.HOSTNAME_FIELD:
43
+                                   expected_m.get('hostname')}
41 44
 
42 45
         with mock.patch.object(configdrive, 'build', autospec=True) as mb:
43
-            result = config.build_configdrive(self.node, "example.com")
46
+            result = config.build_configdrive(self.node)
44 47
             mb.assert_called_once_with(expected_m, mock.ANY)
45 48
             self.assertIs(result, mb.return_value)
46 49
             user_data = mb.call_args[0][1]

+ 71
- 18
metalsmith/test/test_provisioner.py View File

@@ -103,7 +103,9 @@ class TestReserveNode(Base):
103 103
         kwargs.setdefault('instance_id', None)
104 104
         kwargs.setdefault('is_maintenance', False)
105 105
         kwargs.setdefault('resource_class', self.RSC)
106
-        return mock.Mock(spec=NODE_FIELDS, **kwargs)
106
+        result = mock.Mock(spec=NODE_FIELDS, **kwargs)
107
+        result.name = kwargs.get('name')
108
+        return result
107 109
 
108 110
     def test_no_nodes(self):
109 111
         self.api.baremetal.nodes.return_value = []
@@ -120,7 +122,30 @@ class TestReserveNode(Base):
120 122
 
121 123
         self.assertIn(node, nodes)
122 124
         self.api.baremetal.update_node.assert_called_once_with(
123
-            node, instance_id=node.id, instance_info={})
125
+            node, instance_id=node.id,
126
+            instance_info={'metalsmith_hostname': node.id})
127
+
128
+    def test_with_hostname(self):
129
+        nodes = [self._node()]
130
+        self.api.baremetal.nodes.return_value = nodes
131
+
132
+        node = self.pr.reserve_node(self.RSC, hostname='example.com')
133
+
134
+        self.assertIn(node, nodes)
135
+        self.api.baremetal.update_node.assert_called_once_with(
136
+            node, instance_id=node.id,
137
+            instance_info={'metalsmith_hostname': 'example.com'})
138
+
139
+    def test_with_name_as_hostname(self):
140
+        nodes = [self._node(name='example.com')]
141
+        self.api.baremetal.nodes.return_value = nodes
142
+
143
+        node = self.pr.reserve_node(self.RSC)
144
+
145
+        self.assertIn(node, nodes)
146
+        self.api.baremetal.update_node.assert_called_once_with(
147
+            node, instance_id=node.id,
148
+            instance_info={'metalsmith_hostname': 'example.com'})
124 149
 
125 150
     def test_with_capabilities(self):
126 151
         nodes = [
@@ -135,7 +160,8 @@ class TestReserveNode(Base):
135 160
         self.assertIs(node, expected)
136 161
         self.api.baremetal.update_node.assert_called_once_with(
137 162
             node, instance_id=node.id,
138
-            instance_info={'capabilities': {'answer': '42'}})
163
+            instance_info={'capabilities': {'answer': '42'},
164
+                           'metalsmith_hostname': node.id})
139 165
 
140 166
     def test_with_traits(self):
141 167
         nodes = [self._node(properties={'local_gb': 100}, traits=traits)
@@ -149,7 +175,8 @@ class TestReserveNode(Base):
149 175
         self.assertIs(node, expected)
150 176
         self.api.baremetal.update_node.assert_called_once_with(
151 177
             node, instance_id=node.id,
152
-            instance_info={'traits': ['foo', 'answer:42']})
178
+            instance_info={'traits': ['foo', 'answer:42'],
179
+                           'metalsmith_hostname': node.id})
153 180
 
154 181
     def test_custom_predicate(self):
155 182
         nodes = [self._node(properties={'local_gb': i})
@@ -162,7 +189,8 @@ class TestReserveNode(Base):
162 189
 
163 190
         self.assertEqual(node, nodes[1])
164 191
         self.api.baremetal.update_node.assert_called_once_with(
165
-            node, instance_id=node.id, instance_info={})
192
+            node, instance_id=node.id,
193
+            instance_info={'metalsmith_hostname': node.id})
166 194
 
167 195
     def test_custom_predicate_false(self):
168 196
         nodes = [self._node() for _ in range(3)]
@@ -184,7 +212,8 @@ class TestReserveNode(Base):
184 212
         self.assertEqual(node, nodes[0])
185 213
         self.assertFalse(self.api.baremetal.nodes.called)
186 214
         self.api.baremetal.update_node.assert_called_once_with(
187
-            node, instance_id=node.id, instance_info={})
215
+            node, instance_id=node.id,
216
+            instance_info={'metalsmith_hostname': node.id})
188 217
 
189 218
     def test_provided_nodes(self):
190 219
         nodes = [self._node(), self._node()]
@@ -194,7 +223,8 @@ class TestReserveNode(Base):
194 223
         self.assertEqual(node, nodes[0])
195 224
         self.assertFalse(self.api.baremetal.nodes.called)
196 225
         self.api.baremetal.update_node.assert_called_once_with(
197
-            node, instance_id=node.id, instance_info={})
226
+            node, instance_id=node.id,
227
+            instance_info={'metalsmith_hostname': node.id})
198 228
 
199 229
     def test_nodes_filtered(self):
200 230
         nodes = [self._node(resource_class='banana'),
@@ -210,7 +240,8 @@ class TestReserveNode(Base):
210 240
         self.assertFalse(self.api.baremetal.nodes.called)
211 241
         self.api.baremetal.update_node.assert_called_once_with(
212 242
             node, instance_id=node.id,
213
-            instance_info={'capabilities': {'cat': 'meow'}})
243
+            instance_info={'capabilities': {'cat': 'meow'},
244
+                           'metalsmith_hostname': node.id})
214 245
 
215 246
     def test_nodes_filtered_by_conductor_group(self):
216 247
         nodes = [self._node(conductor_group='loc1'),
@@ -230,7 +261,8 @@ class TestReserveNode(Base):
230 261
         self.assertFalse(self.api.baremetal.nodes.called)
231 262
         self.api.baremetal.update_node.assert_called_once_with(
232 263
             node, instance_id=node.id,
233
-            instance_info={'capabilities': {'cat': 'meow'}})
264
+            instance_info={'capabilities': {'cat': 'meow'},
265
+                           'metalsmith_hostname': node.id})
234 266
 
235 267
     def test_provided_nodes_no_match(self):
236 268
         nodes = [
@@ -262,7 +294,7 @@ class TestProvisionNode(Base):
262 294
             'image_source': self.image.id,
263 295
             'root_gb': 99,  # 100 - 1
264 296
             'capabilities': {'boot_option': 'local'},
265
-            _utils.GetNodeMixin.HOSTNAME_FIELD: 'control-0'
297
+            _utils.HOSTNAME_FIELD: 'control-0'
266 298
         }
267 299
         self.extra = {
268 300
             'metalsmith_created_ports': [
@@ -291,8 +323,7 @@ class TestProvisionNode(Base):
291 323
         self.api.baremetal.update_node.assert_called_once_with(
292 324
             self.node, instance_info=self.instance_info, extra=self.extra)
293 325
         self.api.baremetal.validate_node.assert_called_once_with(self.node)
294
-        self.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
295
-                                                      self.node.name)
326
+        self.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
296 327
         self.api.baremetal.set_node_provision_state.assert_called_once_with(
297 328
             self.node, 'active', config_drive=mock.ANY)
298 329
         self.assertFalse(self.api.network.delete_port.called)
@@ -346,8 +377,7 @@ class TestProvisionNode(Base):
346 377
         self.assertEqual(inst.uuid, self.node.id)
347 378
         self.assertEqual(inst.node, self.node)
348 379
 
349
-        config.build_configdrive.assert_called_once_with(
350
-            self.node, self.node.name)
380
+        config.build_configdrive.assert_called_once_with(self.node)
351 381
         self.api.network.create_port.assert_called_once_with(
352 382
             network_id=self.api.network.find_network.return_value.id)
353 383
         self.api.baremetal.attach_vif_to_node.assert_called_once_with(
@@ -369,7 +399,31 @@ class TestProvisionNode(Base):
369 399
         inst = self.pr.provision_node(self.node, 'image',
370 400
                                       [{'network': 'network'}],
371 401
                                       hostname=hostname)
372
-        self.instance_info[_utils.GetNodeMixin.HOSTNAME_FIELD] = hostname
402
+        self.instance_info[_utils.HOSTNAME_FIELD] = hostname
403
+
404
+        self.assertEqual(inst.uuid, self.node.id)
405
+        self.assertEqual(inst.node, self.node)
406
+
407
+        self.api.network.create_port.assert_called_once_with(
408
+            network_id=self.api.network.find_network.return_value.id)
409
+        self.api.baremetal.attach_vif_to_node.assert_called_once_with(
410
+            self.node, self.api.network.create_port.return_value.id)
411
+        self.api.baremetal.update_node.assert_called_once_with(
412
+            self.node, instance_info=self.instance_info, extra=self.extra)
413
+        self.api.baremetal.validate_node.assert_called_once_with(self.node)
414
+        self.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
415
+        self.api.baremetal.set_node_provision_state.assert_called_once_with(
416
+            self.node, 'active', config_drive=mock.ANY)
417
+        self.assertFalse(
418
+            self.api.baremetal.wait_for_nodes_provision_state.called)
419
+        self.assertFalse(self.api.network.delete_port.called)
420
+
421
+    def test_existing_hostname(self):
422
+        hostname = 'control-0.example.com'
423
+        self.node.instance_info[_utils.HOSTNAME_FIELD] = hostname
424
+        inst = self.pr.provision_node(self.node, 'image',
425
+                                      [{'network': 'network'}])
426
+        self.instance_info[_utils.HOSTNAME_FIELD] = hostname
373 427
 
374 428
         self.assertEqual(inst.uuid, self.node.id)
375 429
         self.assertEqual(inst.node, self.node)
@@ -381,8 +435,7 @@ class TestProvisionNode(Base):
381 435
         self.api.baremetal.update_node.assert_called_once_with(
382 436
             self.node, instance_info=self.instance_info, extra=self.extra)
383 437
         self.api.baremetal.validate_node.assert_called_once_with(self.node)
384
-        self.configdrive_mock.assert_called_once_with(mock.ANY, self.node,
385
-                                                      hostname)
438
+        self.configdrive_mock.assert_called_once_with(mock.ANY, self.node)
386 439
         self.api.baremetal.set_node_provision_state.assert_called_once_with(
387 440
             self.node, 'active', config_drive=mock.ANY)
388 441
         self.assertFalse(
@@ -393,7 +446,7 @@ class TestProvisionNode(Base):
393 446
         self.node.name = 'node_1'
394 447
         inst = self.pr.provision_node(self.node, 'image',
395 448
                                       [{'network': 'network'}])
396
-        self.instance_info[_utils.GetNodeMixin.HOSTNAME_FIELD] = '000'
449
+        self.instance_info[_utils.HOSTNAME_FIELD] = '000'
397 450
 
398 451
         self.assertEqual(inst.uuid, self.node.id)
399 452
         self.assertEqual(inst.node, self.node)

+ 36
- 3
metalsmith/test/test_scheduler.py View File

@@ -204,6 +204,8 @@ class TestIronicReserver(testtools.TestCase):
204 204
         super(TestIronicReserver, self).setUp()
205 205
         self.node = mock.Mock(spec=['id', 'name', 'instance_info'],
206 206
                               instance_info={})
207
+        self.node.id = 'abcd'
208
+        self.node.name = None
207 209
         self.api = mock.Mock(spec=['baremetal'])
208 210
         self.api.baremetal = mock.Mock(spec=['update_node', 'validate_node'])
209 211
         self.api.baremetal.update_node.side_effect = (
@@ -220,7 +222,27 @@ class TestIronicReserver(testtools.TestCase):
220 222
         self.api.baremetal.validate_node.assert_called_with(
221 223
             self.node, required=('power', 'management'))
222 224
         self.api.baremetal.update_node.assert_called_once_with(
223
-            self.node, instance_id=self.node.id, instance_info={})
225
+            self.node, instance_id=self.node.id,
226
+            instance_info={'metalsmith_hostname': 'abcd'})
227
+
228
+    def test_name_as_hostname(self):
229
+        self.node.name = 'example.com'
230
+        self.assertEqual(self.node, self.reserver(self.node))
231
+        self.api.baremetal.validate_node.assert_called_with(
232
+            self.node, required=('power', 'management'))
233
+        self.api.baremetal.update_node.assert_called_once_with(
234
+            self.node, instance_id=self.node.id,
235
+            instance_info={'metalsmith_hostname': 'example.com'})
236
+
237
+    def test_name_cannot_be_hostname(self):
238
+        # This should not ever happen, but checking just in case
239
+        self.node.name = 'banana!'
240
+        self.assertEqual(self.node, self.reserver(self.node))
241
+        self.api.baremetal.validate_node.assert_called_with(
242
+            self.node, required=('power', 'management'))
243
+        self.api.baremetal.update_node.assert_called_once_with(
244
+            self.node, instance_id=self.node.id,
245
+            instance_info={'metalsmith_hostname': 'abcd'})
224 246
 
225 247
     def test_with_instance_info(self):
226 248
         self.reserver = _scheduler.IronicReserver(self.api,
@@ -230,7 +252,17 @@ class TestIronicReserver(testtools.TestCase):
230 252
             self.node, required=('power', 'management'))
231 253
         self.api.baremetal.update_node.assert_called_once_with(
232 254
             self.node, instance_id=self.node.id,
233
-            instance_info={'cat': 'meow'})
255
+            instance_info={'cat': 'meow', 'metalsmith_hostname': 'abcd'})
256
+
257
+    def test_with_hostname(self):
258
+        self.reserver = _scheduler.IronicReserver(self.api,
259
+                                                  hostname='example.com')
260
+        self.assertEqual(self.node, self.reserver(self.node))
261
+        self.api.baremetal.validate_node.assert_called_with(
262
+            self.node, required=('power', 'management'))
263
+        self.api.baremetal.update_node.assert_called_once_with(
264
+            self.node, instance_id=self.node.id,
265
+            instance_info={'metalsmith_hostname': 'example.com'})
234 266
 
235 267
     def test_reservation_failed(self):
236 268
         self.api.baremetal.update_node.side_effect = (
@@ -240,7 +272,8 @@ class TestIronicReserver(testtools.TestCase):
240 272
         self.api.baremetal.validate_node.assert_called_with(
241 273
             self.node, required=('power', 'management'))
242 274
         self.api.baremetal.update_node.assert_called_once_with(
243
-            self.node, instance_id=self.node.id, instance_info={})
275
+            self.node, instance_id=self.node.id,
276
+            instance_info={'metalsmith_hostname': 'abcd'})
244 277
 
245 278
     def test_validation_failed(self):
246 279
         self.api.baremetal.validate_node.side_effect = (

+ 4
- 0
releasenotes/notes/reserve-hostname-85d02321156bde3b.yaml View File

@@ -0,0 +1,4 @@
1
+---
2
+features:
3
+  - |
4
+    The ``reserve_node`` call now also accepts ``hostname``.

Loading…
Cancel
Save