summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2019-01-17 14:33:17 +0000
committerGerrit Code Review <review@openstack.org>2019-01-17 14:33:17 +0000
commita57d58493502c1038d2f6cbf162538a206c63f64 (patch)
treede5d93f79c029713124f933bf69c6f09b68b5c8d
parent4c7a9cb8864e6c1f073bb54f584204e050d8a82b (diff)
parent47e9e9319d25e604ebad8d98330aeff6663688fa (diff)
Merge "Support specifying a subnet for NIC"0.9.0
-rw-r--r--metalsmith/_cmd.py14
-rw-r--r--metalsmith/_nics.py43
-rw-r--r--metalsmith/_provisioner.py14
-rw-r--r--metalsmith/test/test_cmd.py5
-rw-r--r--metalsmith/test/test_provisioner.py54
-rw-r--r--releasenotes/notes/subnet-1c177e4b40cc607c.yaml7
-rw-r--r--roles/metalsmith_deployment/README.rst8
7 files changed, 126 insertions, 19 deletions
diff --git a/metalsmith/_cmd.py b/metalsmith/_cmd.py
index 4d5b3a5..752a417 100644
--- a/metalsmith/_cmd.py
+++ b/metalsmith/_cmd.py
@@ -31,11 +31,9 @@ LOG = logging.getLogger(__name__)
31 31
32class NICAction(argparse.Action): 32class NICAction(argparse.Action):
33 def __call__(self, parser, namespace, values, option_string=None): 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 nics = getattr(namespace, self.dest, None) or [] 35 nics = getattr(namespace, self.dest, None) or []
36 if option_string == '--network': 36 if option_string == '--ip':
37 nics.append({'network': values})
38 elif option_string == '--ip':
39 try: 37 try:
40 network, ip = values.split(':', 1) 38 network, ip = values.split(':', 1)
41 except ValueError: 39 except ValueError:
@@ -43,7 +41,7 @@ class NICAction(argparse.Action):
43 self, '--ip format is NETWORK:IP, got %s' % values) 41 self, '--ip format is NETWORK:IP, got %s' % values)
44 nics.append({'network': network, 'fixed_ip': ip}) 42 nics.append({'network': network, 'fixed_ip': ip})
45 else: 43 else:
46 nics.append({'port': values}) 44 nics.append({option_string[2:]: values})
47 setattr(namespace, self.dest, nics) 45 setattr(namespace, self.dest, nics)
48 46
49 47
@@ -143,8 +141,10 @@ def _parse_args(args, config):
143 help='image MD5 checksum or URL with checksums') 141 help='image MD5 checksum or URL with checksums')
144 deploy.add_argument('--image-kernel', help='URL of the image\'s kernel') 142 deploy.add_argument('--image-kernel', help='URL of the image\'s kernel')
145 deploy.add_argument('--image-ramdisk', help='URL of the image\'s ramdisk') 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)', 144 deploy.add_argument('--network', help='network to create a port on '
147 dest='nics', action=NICAction) 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 deploy.add_argument('--port', help='port to attach (name or UUID)', 148 deploy.add_argument('--port', help='port to attach (name or UUID)',
149 dest='nics', action=NICAction) 149 dest='nics', action=NICAction)
150 deploy.add_argument('--ip', help='attach IP from the network', 150 deploy.add_argument('--ip', help='attach IP from the network',
diff --git a/metalsmith/_nics.py b/metalsmith/_nics.py
index 8be912d..bcfbcbc 100644
--- a/metalsmith/_nics.py
+++ b/metalsmith/_nics.py
@@ -16,6 +16,8 @@
16import collections 16import collections
17import logging 17import logging
18 18
19from openstack import exceptions as sdk_exc
20
19from metalsmith import _utils 21from metalsmith import _utils
20from metalsmith import exceptions 22from metalsmith import exceptions
21 23
@@ -55,10 +57,12 @@ class NICs(object):
55 result.append(('port', self._get_port(nic))) 57 result.append(('port', self._get_port(nic)))
56 elif 'network' in nic: 58 elif 'network' in nic:
57 result.append(('network', self._get_network(nic))) 59 result.append(('network', self._get_network(nic)))
60 elif 'subnet' in nic:
61 result.append(('subnet', self._get_subnet(nic)))
58 else: 62 else:
59 raise exceptions.InvalidNIC( 63 raise exceptions.InvalidNIC(
60 'Unknown NIC record type, export "port" or "network", ' 64 'Unknown NIC record type, export "port", "subnet" or '
61 'got %s' % nic) 65 '"network", got %s' % nic)
62 66
63 self._validated = result 67 self._validated = result
64 68
@@ -67,7 +71,7 @@ class NICs(object):
67 self.validate() 71 self.validate()
68 72
69 for nic_type, nic in self._validated: 73 for nic_type, nic in self._validated:
70 if nic_type == 'network': 74 if nic_type != 'port':
71 port = self._connection.network.create_port(**nic) 75 port = self._connection.network.create_port(**nic)
72 self.created_ports.append(port.id) 76 self.created_ports.append(port.id)
73 LOG.info('Created port %(port)s for node %(node)s with ' 77 LOG.info('Created port %(port)s for node %(node)s with '
@@ -103,7 +107,7 @@ class NICs(object):
103 try: 107 try:
104 port = self._connection.network.find_port( 108 port = self._connection.network.find_port(
105 nic['port'], ignore_missing=False) 109 nic['port'], ignore_missing=False)
106 except Exception as exc: 110 except sdk_exc.SDKException as exc:
107 raise exceptions.InvalidNIC( 111 raise exceptions.InvalidNIC(
108 'Cannot find port %(port)s: %(error)s' % 112 'Cannot find port %(port)s: %(error)s' %
109 {'port': nic['port'], 'error': exc}) 113 {'port': nic['port'], 'error': exc})
@@ -125,7 +129,7 @@ class NICs(object):
125 try: 129 try:
126 network = self._connection.network.find_network( 130 network = self._connection.network.find_network(
127 nic['network'], ignore_missing=False) 131 nic['network'], ignore_missing=False)
128 except Exception as exc: 132 except sdk_exc.SDKException as exc:
129 raise exceptions.InvalidNIC( 133 raise exceptions.InvalidNIC(
130 'Cannot find network %(net)s: %(error)s' % 134 'Cannot find network %(net)s: %(error)s' %
131 {'net': nic['network'], 'error': exc}) 135 {'net': nic['network'], 'error': exc})
@@ -136,6 +140,35 @@ class NICs(object):
136 140
137 return port_args 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
140def detach_and_delete_ports(connection, node, created_ports, attached_ports): 173def detach_and_delete_ports(connection, node, created_ports, attached_ports):
141 """Detach attached port and delete previously created ones. 174 """Detach attached port and delete previously created ones.
diff --git a/metalsmith/_provisioner.py b/metalsmith/_provisioner.py
index ebc9f0f..1d5a1ac 100644
--- a/metalsmith/_provisioner.py
+++ b/metalsmith/_provisioner.py
@@ -217,10 +217,16 @@ class Provisioner(_utils.GetNodeMixin):
217 `Image` name or UUID. 217 `Image` name or UUID.
218 :param nics: List of virtual NICs to attach to physical ports. 218 :param nics: List of virtual NICs to attach to physical ports.
219 Each item is a dict with a key describing the type of the NIC: 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 220
221 to create a port on (``{"network": "<network name or ID>"}``). 221 * ``{"port": "<port name or ID>"}`` to use the provided pre-created
222 A network record can optionally feature a ``fixed_ip`` argument 222 port.
223 to use this specific fixed IP from a suitable subnet. 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 :param root_size_gb: The size of the root partition. By default 230 :param root_size_gb: The size of the root partition. By default
225 the value of the local_gb property is used. 231 the value of the local_gb property is used.
226 :param swap_size_mb: The size of the swap partition. It's an error 232 :param swap_size_mb: The size of the swap partition. It's an error
diff --git a/metalsmith/test/test_cmd.py b/metalsmith/test/test_cmd.py
index c081f62..69d18bc 100644
--- a/metalsmith/test/test_cmd.py
+++ b/metalsmith/test/test_cmd.py
@@ -342,6 +342,11 @@ class TestDeploy(testtools.TestCase):
342 {'nics': [{'network': 'private', 'fixed_ip': '10.0.0.2'}, 342 {'nics': [{'network': 'private', 'fixed_ip': '10.0.0.2'},
343 {'network': 'public', 'fixed_ip': '8.0.8.0'}]}) 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 def test_args_bad_ip(self, mock_pr): 350 def test_args_bad_ip(self, mock_pr):
346 args = ['deploy', '--image', 'myimg', '--resource-class', 'compute', 351 args = ['deploy', '--image', 'myimg', '--resource-class', 'compute',
347 '--ip', 'private:10.0.0.2', '--ip', 'public'] 352 '--ip', 'private:10.0.0.2', '--ip', 'public']
diff --git a/metalsmith/test/test_provisioner.py b/metalsmith/test/test_provisioner.py
index 93d1988..c948ec5 100644
--- a/metalsmith/test/test_provisioner.py
+++ b/metalsmith/test/test_provisioner.py
@@ -466,6 +466,28 @@ class TestProvisionNode(Base):
466 self.api.baremetal.wait_for_nodes_provision_state.called) 466 self.api.baremetal.wait_for_nodes_provision_state.called)
467 self.assertFalse(self.api.network.delete_port.called) 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 def test_whole_disk(self): 491 def test_whole_disk(self):
470 self.image.kernel_id = None 492 self.image.kernel_id = None
471 self.image.ramdisk_id = None 493 self.image.ramdisk_id = None
@@ -1036,7 +1058,8 @@ abcd and-not-image-again
1036 self.assertFalse(self.api.baremetal.set_node_provision_state.called) 1058 self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1037 1059
1038 def test_invalid_network(self): 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 self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found', 1063 self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1041 self.pr.provision_node, 1064 self.pr.provision_node,
1042 self.node, 'image', [{'network': 'network'}]) 1065 self.node, 'image', [{'network': 'network'}])
@@ -1046,7 +1069,8 @@ abcd and-not-image-again
1046 self.assertFalse(self.api.baremetal.set_node_provision_state.called) 1069 self.assertFalse(self.api.baremetal.set_node_provision_state.called)
1047 1070
1048 def test_invalid_port(self): 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 self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found', 1074 self.assertRaisesRegex(exceptions.InvalidNIC, 'Not found',
1051 self.pr.provision_node, 1075 self.pr.provision_node,
1052 self.node, 'image', [{'port': 'port1'}]) 1076 self.node, 'image', [{'port': 'port1'}])
@@ -1055,6 +1079,29 @@ abcd and-not-image-again
1055 self.assertFalse(self.api.network.create_port.called) 1079 self.assertFalse(self.api.network.create_port.called)
1056 self.assertFalse(self.api.baremetal.set_node_provision_state.called) 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 def test_no_local_gb(self): 1105 def test_no_local_gb(self):
1059 self.node.properties = {} 1106 self.node.properties = {}
1060 self.assertRaises(exceptions.UnknownRootDiskSize, 1107 self.assertRaises(exceptions.UnknownRootDiskSize,
@@ -1113,7 +1160,8 @@ abcd and-not-image-again
1113 def test_invalid_nic_type_fields(self): 1160 def test_invalid_nic_type_fields(self):
1114 for item in ({'port': '1234', 'foo': 'bar'}, 1161 for item in ({'port': '1234', 'foo': 'bar'},
1115 {'port': '1234', 'network': '4321'}, 1162 {'port': '1234', 'network': '4321'},
1116 {'network': '4321', 'foo': 'bar'}): 1163 {'network': '4321', 'foo': 'bar'},
1164 {'subnet': '4321', 'foo': 'bar'}):
1117 self.assertRaisesRegex(exceptions.InvalidNIC, 1165 self.assertRaisesRegex(exceptions.InvalidNIC,
1118 'Unexpected fields', 1166 'Unexpected fields',
1119 self.pr.provision_node, 1167 self.pr.provision_node,
diff --git a/releasenotes/notes/subnet-1c177e4b40cc607c.yaml b/releasenotes/notes/subnet-1c177e4b40cc607c.yaml
new file mode 100644
index 0000000..66fac9b
--- /dev/null
+++ b/releasenotes/notes/subnet-1c177e4b40cc607c.yaml
@@ -0,0 +1,7 @@
1---
2features:
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.
diff --git a/roles/metalsmith_deployment/README.rst b/roles/metalsmith_deployment/README.rst
index 2524538..cee7810 100644
--- a/roles/metalsmith_deployment/README.rst
+++ b/roles/metalsmith_deployment/README.rst
@@ -106,6 +106,14 @@ Each instances has the following attributes:
106 nics: 106 nics:
107 - port: b2254316-7867-4615-9fb7-911b3f38ca2a 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``resource_class`` (defaults to ``metalsmith_resource_class``) 117``resource_class`` (defaults to ``metalsmith_resource_class``)
110 requested node's resource class. 118 requested node's resource class.
111``root_size`` (defaults to ``metalsmith_root_size``) 119``root_size`` (defaults to ``metalsmith_root_size``)