Browse Source

Foundation for different deployment sources

Change-Id: I7c9538e37476d9d3ea5b9cc403419dda95cf77cc
Story: #2002048
Task: #26064
Dmitry Tantsur 7 months ago
parent
commit
a34d0e0951
3 changed files with 103 additions and 16 deletions
  1. 9
    16
      metalsmith/_provisioner.py
  2. 73
    0
      metalsmith/sources.py
  3. 21
    0
      metalsmith/test/test_provisioner.py

+ 9
- 16
metalsmith/_provisioner.py View File

@@ -26,6 +26,7 @@ from metalsmith import _os_api
26 26
 from metalsmith import _scheduler
27 27
 from metalsmith import _utils
28 28
 from metalsmith import exceptions
29
+from metalsmith import sources
29 30
 
30 31
 
31 32
 LOG = logging.getLogger(__name__)
@@ -178,7 +179,8 @@ class Provisioner(object):
178 179
         :param node: Node object, UUID or name. Will be reserved first, if
179 180
             not reserved already. Must be in the "available" state with
180 181
             maintenance mode off.
181
-        :param image: Image name or UUID to provision.
182
+        :param image: Image source - one of :mod:`~metalsmith.sources`,
183
+            `Image` name or UUID.
182 184
         :param nics: List of virtual NICs to attach to physical ports.
183 185
             Each item is a dict with a key describing the type of the NIC:
184 186
             either a port (``{"port": "<port name or ID>"}``) or a network
@@ -203,6 +205,9 @@ class Provisioner(object):
203 205
         """
204 206
         if config is None:
205 207
             config = _config.InstanceConfig()
208
+        if isinstance(image, six.string_types):
209
+            image = sources.Glance(image)
210
+
206 211
         node = self._check_node_for_deploy(node)
207 212
         created_ports = []
208 213
         attached_ports = []
@@ -211,14 +216,7 @@ class Provisioner(object):
211 216
             hostname = self._check_hostname(node, hostname)
212 217
             root_disk_size = _utils.get_root_disk(root_disk_size, node)
213 218
 
214
-            try:
215
-                image = self._api.get_image(image)
216
-            except Exception as exc:
217
-                raise exceptions.InvalidImage(
218
-                    'Cannot find image %(image)s: %(error)s' %
219
-                    {'image': image, 'error': exc})
220
-
221
-            LOG.debug('Image: %s', image)
219
+            image._validate(self._api)
222 220
 
223 221
             nics = self._get_nics(nics or [])
224 222
 
@@ -235,17 +233,12 @@ class Provisioner(object):
235 233
 
236 234
             capabilities['boot_option'] = 'netboot' if netboot else 'local'
237 235
 
238
-            updates = {'/instance_info/image_source': image.id,
239
-                       '/instance_info/root_gb': root_disk_size,
236
+            updates = {'/instance_info/root_gb': root_disk_size,
240 237
                        '/instance_info/capabilities': capabilities,
241 238
                        '/extra/%s' % _CREATED_PORTS: created_ports,
242 239
                        '/extra/%s' % _ATTACHED_PORTS: attached_ports,
243 240
                        '/instance_info/%s' % _os_api.HOSTNAME_FIELD: hostname}
244
-
245
-            for prop in ('kernel', 'ramdisk'):
246
-                value = getattr(image, '%s_id' % prop, None)
247
-                if value:
248
-                    updates['/instance_info/%s' % prop] = value
241
+            updates.update(image._node_updates(self._api))
249 242
 
250 243
             LOG.debug('Updating node %(node)s with %(updates)s',
251 244
                       {'node': _utils.log_node(node), 'updates': updates})

+ 73
- 0
metalsmith/sources.py View File

@@ -0,0 +1,73 @@
1
+# Copyright 2018 Red Hat, Inc.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+# implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+
16
+"""Image sources to use when provisioning nodes."""
17
+
18
+import abc
19
+import logging
20
+
21
+import six
22
+
23
+from metalsmith import exceptions
24
+
25
+
26
+LOG = logging.getLogger(__name__)
27
+
28
+
29
+@six.add_metaclass(abc.ABCMeta)
30
+class _Source(object):
31
+
32
+    def _validate(self, api):
33
+        """Validate the source."""
34
+
35
+    @abc.abstractmethod
36
+    def _node_updates(self, api):
37
+        """Updates required for a node to use this source."""
38
+
39
+
40
+class Glance(_Source):
41
+    """Image from the OpenStack Image service."""
42
+
43
+    def __init__(self, image):
44
+        """Create a Glance source.
45
+
46
+        :param image: `Image` object, ID or name.
47
+        """
48
+        self._image_id = image
49
+        self._image_obj = None
50
+
51
+    def _validate(self, api):
52
+        if self._image_obj is not None:
53
+            return
54
+        try:
55
+            self._image_obj = api.get_image(self._image_id)
56
+        except Exception as exc:
57
+            raise exceptions.InvalidImage(
58
+                'Cannot find image %(image)s: %(error)s' %
59
+                {'image': self._image_id, 'error': exc})
60
+
61
+    def _node_updates(self, api):
62
+        self._validate(api)
63
+        LOG.debug('Image: %s', self._image_obj)
64
+
65
+        updates = {
66
+            '/instance_info/image_source': self._image_obj.id
67
+        }
68
+        for prop in ('kernel', 'ramdisk'):
69
+            value = getattr(self._image_obj, '%s_id' % prop, None)
70
+            if value:
71
+                updates['/instance_info/%s' % prop] = value
72
+
73
+        return updates

+ 21
- 0
metalsmith/test/test_provisioner.py View File

@@ -22,6 +22,7 @@ from metalsmith import _instance
22 22
 from metalsmith import _os_api
23 23
 from metalsmith import _provisioner
24 24
 from metalsmith import exceptions
25
+from metalsmith import sources
25 26
 
26 27
 
27 28
 class Base(testtools.TestCase):
@@ -206,6 +207,26 @@ class TestProvisionNode(Base):
206 207
         self.assertFalse(self.api.release_node.called)
207 208
         self.assertFalse(self.api.delete_port.called)
208 209
 
210
+    def test_ok_with_source(self):
211
+        inst = self.pr.provision_node(self.node, sources.Glance('image'),
212
+                                      [{'network': 'network'}])
213
+
214
+        self.assertEqual(inst.uuid, self.node.uuid)
215
+        self.assertEqual(inst.node, self.node)
216
+
217
+        self.api.create_port.assert_called_once_with(
218
+            network_id=self.api.get_network.return_value.id)
219
+        self.api.attach_port_to_node.assert_called_once_with(
220
+            self.node.uuid, self.api.create_port.return_value.id)
221
+        self.api.update_node.assert_called_once_with(self.node, self.updates)
222
+        self.api.validate_node.assert_called_once_with(self.node,
223
+                                                       validate_deploy=True)
224
+        self.api.node_action.assert_called_once_with(self.node, 'active',
225
+                                                     configdrive=mock.ANY)
226
+        self.assertFalse(self.wait_mock.called)
227
+        self.assertFalse(self.api.release_node.called)
228
+        self.assertFalse(self.api.delete_port.called)
229
+
209 230
     def test_with_config(self):
210 231
         config = mock.MagicMock(spec=_config.InstanceConfig)
211 232
         inst = self.pr.provision_node(self.node, 'image',

Loading…
Cancel
Save