Browse Source

Merge "Support specifying a subnet for NIC"

tags/0.9.0^0
Zuul 5 months ago
parent
commit
a57d584935

+ 7
- 7
metalsmith/_cmd.py View File

@@ -31,11 +31,9 @@ LOG = logging.getLogger(__name__)
31 31
 
32 32
 class NICAction(argparse.Action):
33 33
     def __call__(self, parser, namespace, values, option_string=None):
34
-        assert option_string in ('--port', '--network', '--ip')
34
+        assert option_string in ('--port', '--network', '--ip', '--subnet')
35 35
         nics = getattr(namespace, self.dest, None) or []
36
-        if option_string == '--network':
37
-            nics.append({'network': values})
38
-        elif option_string == '--ip':
36
+        if option_string == '--ip':
39 37
             try:
40 38
                 network, ip = values.split(':', 1)
41 39
             except ValueError:
@@ -43,7 +41,7 @@ class NICAction(argparse.Action):
43 41
                     self, '--ip format is NETWORK:IP, got %s' % values)
44 42
             nics.append({'network': network, 'fixed_ip': ip})
45 43
         else:
46
-            nics.append({'port': values})
44
+            nics.append({option_string[2:]: values})
47 45
         setattr(namespace, self.dest, nics)
48 46
 
49 47
 
@@ -143,8 +141,10 @@ def _parse_args(args, config):
143 141
                         help='image MD5 checksum or URL with checksums')
144 142
     deploy.add_argument('--image-kernel', help='URL of the image\'s kernel')
145 143
     deploy.add_argument('--image-ramdisk', help='URL of the image\'s ramdisk')
146
-    deploy.add_argument('--network', help='network to use (name or UUID)',
147
-                        dest='nics', action=NICAction)
144
+    deploy.add_argument('--network', help='network to create a port on '
145
+                        '(name or UUID)', dest='nics', action=NICAction)
146
+    deploy.add_argument('--subnet', help='subnet to create a port on '
147
+                        '(name or UUID)', dest='nics', action=NICAction)
148 148
     deploy.add_argument('--port', help='port to attach (name or UUID)',
149 149
                         dest='nics', action=NICAction)
150 150
     deploy.add_argument('--ip', help='attach IP from the network',

+ 38
- 5
metalsmith/_nics.py View File

@@ -16,6 +16,8 @@
16 16
 import collections
17 17
 import logging
18 18
 
19
+from openstack import exceptions as sdk_exc
20
+
19 21
 from metalsmith import _utils
20 22
 from metalsmith import exceptions
21 23
 
@@ -55,10 +57,12 @@ class NICs(object):
55 57
                 result.append(('port', self._get_port(nic)))
56 58
             elif 'network' in nic:
57 59
                 result.append(('network', self._get_network(nic)))
60
+            elif 'subnet' in nic:
61
+                result.append(('subnet', self._get_subnet(nic)))
58 62
             else:
59 63
                 raise exceptions.InvalidNIC(
60
-                    'Unknown NIC record type, export "port" or "network", '
61
-                    'got %s' % nic)
64
+                    'Unknown NIC record type, export "port", "subnet" or '
65
+                    '"network", got %s' % nic)
62 66
 
63 67
         self._validated = result
64 68
 
@@ -67,7 +71,7 @@ class NICs(object):
67 71
         self.validate()
68 72
 
69 73
         for nic_type, nic in self._validated:
70
-            if nic_type == 'network':
74
+            if nic_type != 'port':
71 75
                 port = self._connection.network.create_port(**nic)
72 76
                 self.created_ports.append(port.id)
73 77
                 LOG.info('Created port %(port)s for node %(node)s with '
@@ -103,7 +107,7 @@ class NICs(object):
103 107
         try:
104 108
             port = self._connection.network.find_port(
105 109
                 nic['port'], ignore_missing=False)
106
-        except Exception as exc:
110
+        except sdk_exc.SDKException as exc:
107 111
             raise exceptions.InvalidNIC(
108 112
                 'Cannot find port %(port)s: %(error)s' %
109 113
                 {'port': nic['port'], 'error': exc})
@@ -125,7 +129,7 @@ class NICs(object):
125 129
         try:
126 130
             network = self._connection.network.find_network(
127 131
                 nic['network'], ignore_missing=False)
128
-        except Exception as exc:
132
+        except sdk_exc.SDKException as exc:
129 133
             raise exceptions.InvalidNIC(
130 134
                 'Cannot find network %(net)s: %(error)s' %
131 135
                 {'net': nic['network'], 'error': exc})
@@ -136,6 +140,35 @@ class NICs(object):
136 140
 
137 141
         return port_args
138 142
 
143
+    def _get_subnet(self, nic):
144
+        """Validate and get the NIC information for a subnet.
145
+
146
+        :param nic: NIC information in the form ``{"subnet": "<id or name>"}``.
147
+        :returns: keyword arguments to use when creating a port.
148
+        """
149
+        unexpected = set(nic) - {'subnet'}
150
+        if unexpected:
151
+            raise exceptions.InvalidNIC(
152
+                'Unexpected fields for a subnet: %s' % ', '.join(unexpected))
153
+
154
+        try:
155
+            subnet = self._connection.network.find_subnet(
156
+                nic['subnet'], ignore_missing=False)
157
+        except sdk_exc.SDKException as exc:
158
+            raise exceptions.InvalidNIC(
159
+                'Cannot find subnet %(sub)s: %(error)s' %
160
+                {'sub': nic['subnet'], 'error': exc})
161
+
162
+        try:
163
+            network = self._connection.network.get_network(subnet.network_id)
164
+        except sdk_exc.SDKException as exc:
165
+            raise exceptions.InvalidNIC(
166
+                'Cannot find network %(net)s for subnet %(sub)s: %(error)s' %
167
+                {'net': subnet.network_id, 'sub': nic['subnet'], 'error': exc})
168
+
169
+        return {'network_id': network.id,
170
+                'fixed_ips': [{'subnet_id': subnet.id}]}
171
+
139 172
 
140 173
 def detach_and_delete_ports(connection, node, created_ports, attached_ports):
141 174
     """Detach attached port and delete previously created ones.

+ 10
- 4
metalsmith/_provisioner.py View File

@@ -217,10 +217,16 @@ class Provisioner(_utils.GetNodeMixin):
217 217
             `Image` name or UUID.
218 218
         :param nics: List of virtual NICs to attach to physical ports.
219 219
             Each item is a dict with a key describing the type of the NIC:
220
-            either a port (``{"port": "<port name or ID>"}``) or a network
221
-            to create a port on (``{"network": "<network name or ID>"}``).
222
-            A network record can optionally feature a ``fixed_ip`` argument
223
-            to use this specific fixed IP from a suitable subnet.
220
+
221
+            * ``{"port": "<port name or ID>"}`` to use the provided pre-created
222
+              port.
223
+            * ``{"network": "<network name or ID>"}`` to create a port on the
224
+              provided network. Optionally, a ``fixed_ip`` argument can be used
225
+              to specify an IP address.
226
+            * ``{"subnet": "<subnet name or ID>"}`` to create a port with an IP
227
+              address from the provided subnet. The network is determined from
228
+              the subnet.
229
+
224 230
         :param root_size_gb: The size of the root partition. By default
225 231
             the value of the local_gb property is used.
226 232
         :param swap_size_mb: The size of the swap partition. It's an error

+ 5
- 0
metalsmith/test/test_cmd.py View File

@@ -342,6 +342,11 @@ class TestDeploy(testtools.TestCase):
342 342
                     {'nics': [{'network': 'private', 'fixed_ip': '10.0.0.2'},
343 343
                               {'network': 'public', 'fixed_ip': '8.0.8.0'}]})
344 344
 
345
+    def test_args_subnet(self, mock_pr):
346
+        args = ['deploy', '--subnet', 'mysubnet', '--image', 'myimg',
347
+                '--resource-class', 'compute']
348
+        self._check(mock_pr, args, {}, {'nics': [{'subnet': 'mysubnet'}]})
349
+
345 350
     def test_args_bad_ip(self, mock_pr):
346 351
         args = ['deploy', '--image', 'myimg', '--resource-class', 'compute',
347 352
                 '--ip', 'private:10.0.0.2', '--ip', 'public']

+ 51
- 3
metalsmith/test/test_provisioner.py View File

@@ -466,6 +466,28 @@ class TestProvisionNode(Base):
466 466
             self.api.baremetal.wait_for_nodes_provision_state.called)
467 467
         self.assertFalse(self.api.network.delete_port.called)
468 468
 
469
+    def test_with_subnet(self):
470
+        inst = self.pr.provision_node(self.node, 'image',
471
+                                      [{'subnet': 'subnet'}])
472
+
473
+        self.assertEqual(inst.uuid, self.node.id)
474
+        self.assertEqual(inst.node, self.node)
475
+
476
+        self.api.network.create_port.assert_called_once_with(
477
+            network_id=self.api.network.get_network.return_value.id,
478
+            fixed_ips=[{'subnet_id':
479
+                        self.api.network.find_subnet.return_value.id}])
480
+        self.api.baremetal.attach_vif_to_node.assert_called_once_with(
481
+            self.node, self.api.network.create_port.return_value.id)
482
+        self.api.baremetal.update_node.assert_called_once_with(
483
+            self.node, extra=self.extra, instance_info=self.instance_info)
484
+        self.api.baremetal.validate_node.assert_called_once_with(self.node)
485
+        self.api.baremetal.set_node_provision_state.assert_called_once_with(
486
+            self.node, 'active', config_drive=mock.ANY)
487
+        self.assertFalse(
488
+            self.api.baremetal.wait_for_nodes_provision_state.called)
489
+        self.assertFalse(self.api.network.delete_port.called)
490
+
469 491
     def test_whole_disk(self):
470 492
         self.image.kernel_id = None
471 493
         self.image.ramdisk_id = None
@@ -1036,7 +1058,8 @@ abcd  and-not-image-again
1036 1058
         self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1037 1059
 
1038 1060
     def test_invalid_network(self):
1039
-        self.api.network.find_network.side_effect = RuntimeError('Not found')
1061
+        self.api.network.find_network.side_effect = os_exc.SDKException(
1062
+            'Not found')
1040 1063
         self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1041 1064
                                self.pr.provision_node,
1042 1065
                                self.node, 'image', [{'network': 'network'}])
@@ -1046,7 +1069,8 @@ abcd  and-not-image-again
1046 1069
         self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1047 1070
 
1048 1071
     def test_invalid_port(self):
1049
-        self.api.network.find_port.side_effect = RuntimeError('Not found')
1072
+        self.api.network.find_port.side_effect = os_exc.SDKException(
1073
+            'Not found')
1050 1074
         self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1051 1075
                                self.pr.provision_node,
1052 1076
                                self.node, 'image', [{'port': 'port1'}])
@@ -1055,6 +1079,29 @@ abcd  and-not-image-again
1055 1079
         self.assertFalse(self.api.network.create_port.called)
1056 1080
         self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1057 1081
 
1082
+    def test_invalid_subnet(self):
1083
+        self.api.network.find_subnet.side_effect = os_exc.SDKException(
1084
+            'Not found')
1085
+        self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1086
+                               self.pr.provision_node,
1087
+                               self.node, 'image', [{'subnet': 'subnet'}])
1088
+        self.api.baremetal.update_node.assert_called_once_with(
1089
+            self.node, extra={}, instance_info={}, instance_id=None)
1090
+        self.assertFalse(self.api.network.create_port.called)
1091
+        self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1092
+
1093
+    def test_invalid_network_of_subnet(self):
1094
+        # NOTE(dtantsur): I doubt this can happen, maybe some race?
1095
+        self.api.network.get_network.side_effect = os_exc.SDKException(
1096
+            'Not found')
1097
+        self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1098
+                               self.pr.provision_node,
1099
+                               self.node, 'image', [{'subnet': 'subnet'}])
1100
+        self.api.baremetal.update_node.assert_called_once_with(
1101
+            self.node, extra={}, instance_info={}, instance_id=None)
1102
+        self.assertFalse(self.api.network.create_port.called)
1103
+        self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1104
+
1058 1105
     def test_no_local_gb(self):
1059 1106
         self.node.properties = {}
1060 1107
         self.assertRaises(exceptions.UnknownRootDiskSize,
@@ -1113,7 +1160,8 @@ abcd  and-not-image-again
1113 1160
     def test_invalid_nic_type_fields(self):
1114 1161
         for item in ({'port': '1234', 'foo': 'bar'},
1115 1162
                      {'port': '1234', 'network': '4321'},
1116
-                     {'network': '4321', 'foo': 'bar'}):
1163
+                     {'network': '4321', 'foo': 'bar'},
1164
+                     {'subnet': '4321', 'foo': 'bar'}):
1117 1165
             self.assertRaisesRegex(exceptions.InvalidNIC,
1118 1166
                                    'Unexpected fields',
1119 1167
                                    self.pr.provision_node,

+ 7
- 0
releasenotes/notes/subnet-1c177e4b40cc607c.yaml View File

@@ -0,0 +1,7 @@
1
+---
2
+features:
3
+  - |
4
+    Allows specifying a subnet for the ``nics`` argument of the
5
+    ``provision_node`` call as ``{"subnet": "<name or ID>"}``.
6
+  - |
7
+    Adds a new CLI argument ``--subnet`` to create a port on the given subnet.

+ 8
- 0
roles/metalsmith_deployment/README.rst View File

@@ -106,6 +106,14 @@ Each instances has the following attributes:
106 106
             nics:
107 107
               - port: b2254316-7867-4615-9fb7-911b3f38ca2a
108 108
 
109
+    ``subnet``
110
+        creates a port on the given subnet, for example:
111
+
112
+        .. code-block:: yaml
113
+
114
+            nics:
115
+              - subnet: private-subnet1
116
+
109 117
 ``resource_class`` (defaults to ``metalsmith_resource_class``)
110 118
     requested node's resource class.
111 119
 ``root_size`` (defaults to ``metalsmith_root_size``)

Loading…
Cancel
Save