Merge ~mpontillo/maas:pod-compose--use-given-ifnames into maas:master

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: 2f1464293285e0fe33f193c7a1297d01f672da9b
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:pod-compose--use-given-ifnames
Merge into: maas:master
Diff against target: 180 lines (+110/-3)
3 files modified
src/maasserver/forms/pods.py (+3/-1)
src/maasserver/models/bmc.py (+26/-2)
src/maasserver/models/tests/test_bmc.py (+81/-0)
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
MAAS Lander unittests Pending
Review via email: mp+352733@code.launchpad.net

Commit message

Use interface labels from contraints when composing machines.

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Nicely done. +1

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
2f14642... by Mike Pontillo

Fix lint.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
2index 4bd535b..d83ea00 100644
3--- a/src/maasserver/forms/pods.py
4+++ b/src/maasserver/forms/pods.py
5@@ -538,9 +538,11 @@ class ComposeMachineForm(forms.Form):
6 discovered_machine, self.request.user,
7 skip_commissioning=skip_commissioning,
8 creation_type=creation_type,
9+ interfaces=self.get_value_for('interfaces'),
10 domain=self.get_value_for('domain'),
11 pool=self.get_value_for('pool'),
12- zone=self.get_value_for('zone'))
13+ zone=self.get_value_for('zone'),
14+ )
15 self.pod.sync_hints(pod_hints)
16 return created_machine
17
18diff --git a/src/maasserver/models/bmc.py b/src/maasserver/models/bmc.py
19index 52aebe0..b95ace5 100644
20--- a/src/maasserver/models/bmc.py
21+++ b/src/maasserver/models/bmc.py
22@@ -79,6 +79,7 @@ from provisioningserver.drivers.pod import BlockDeviceType
23 from provisioningserver.drivers.power.registry import PowerDriverRegistry
24 from provisioningserver.enum import MACVLAN_MODE_CHOICES
25 from provisioningserver.logger import get_maas_logger
26+from provisioningserver.utils.constraints import LabeledConstraintMap
27 from provisioningserver.utils.twisted import asynchronous
28 from twisted.internet.defer import inlineCallbacks
29
30@@ -668,7 +669,8 @@ class Pod(BMC):
31 def create_machine(
32 self, discovered_machine, commissioning_user,
33 skip_commissioning=False,
34- creation_type=NODE_CREATION_TYPE.PRE_EXISTING, **kwargs):
35+ creation_type=NODE_CREATION_TYPE.PRE_EXISTING, interfaces=None,
36+ **kwargs):
37 """Create's a `Machine` from `discovered_machines` for this pod."""
38 if skip_commissioning:
39 status = NODE_STATUS.READY
40@@ -695,6 +697,9 @@ class Pod(BMC):
41 if pool is None:
42 pool = self.pool
43
44+ if interfaces is not None:
45+ assert isinstance(interfaces, LabeledConstraintMap)
46+
47 # Create the machine.
48 machine = Machine(
49 hostname=discovered_machine.hostname,
50@@ -754,11 +759,30 @@ class Pod(BMC):
51 if skip_commissioning:
52 machine.set_default_storage_layout()
53
54+ # Enumerating the LabeledConstraintMap of interfaces will yield the
55+ # name of each interface, in the same order that they will exist
56+ # on the hypervisor. (This is a fortunate coincidence, since
57+ # dictionaries in Python 3.6+ preserve insertion order.)
58+ if interfaces is not None:
59+ interface_names = [
60+ label[:15]
61+ for label in interfaces
62+ ]
63+ else:
64+ interface_names = []
65+ if len(discovered_machine.interfaces) > len(interface_names):
66+ # The lists should never have different lengths, but use default
67+ # names for all interfaces to avoid conflicts, just in case.
68+ # (This also happens if no interface labels were supplied.)
69+ interface_names = [
70+ 'eth%d' % i
71+ for i in range(len(discovered_machine.interfaces))
72+ ]
73 # Create the discovered interface and set the default networking
74 # configuration.
75 for idx, discovered_nic in enumerate(discovered_machine.interfaces):
76 interface = self._create_interface(
77- discovered_nic, machine, name='eth%d' % idx)
78+ discovered_nic, machine, name=interface_names[idx])
79 if discovered_nic.boot:
80 machine.boot_interface = interface
81 machine.save(update_fields=['boot_interface'])
82diff --git a/src/maasserver/models/tests/test_bmc.py b/src/maasserver/models/tests/test_bmc.py
83index 6276e65..66ff22b 100644
84--- a/src/maasserver/models/tests/test_bmc.py
85+++ b/src/maasserver/models/tests/test_bmc.py
86@@ -58,6 +58,7 @@ from provisioningserver.drivers.pod import (
87 DiscoveredPodStoragePool,
88 )
89 from provisioningserver.rpc.cluster import DecomposeMachine
90+from provisioningserver.utils.constraints import LabeledConstraintMap
91 from testtools.matchers import (
92 Equals,
93 HasLength,
94@@ -968,6 +969,86 @@ class TestPod(MAASServerTestCase):
95 machine = pod.create_machine(discovered_machine, factory.make_User())
96 self.assertTrue(tag in machine.tags.all())
97
98+ def test_create_machine_sets_interface_names_using_constraint_labels(self):
99+ discovered_machine = self.make_discovered_machine()
100+ self.patch(Machine, "set_default_storage_layout")
101+ self.patch(Machine, "set_initial_networking_configuration")
102+ self.patch(Machine, "start_commissioning")
103+ fabric = factory.make_Fabric()
104+ vlan = factory.make_VLAN(
105+ fabric=fabric, dhcp_on=True,
106+ primary_rack=factory.make_RackController())
107+ vlan2 = factory.make_VLAN(
108+ fabric=fabric, dhcp_on=False,
109+ primary_rack=factory.make_RackController())
110+ vlan3 = factory.make_VLAN(
111+ fabric=fabric, dhcp_on=False,
112+ primary_rack=factory.make_RackController())
113+ pod = factory.make_Pod()
114+ machine = pod.create_machine(
115+ discovered_machine, factory.make_User(),
116+ interfaces=LabeledConstraintMap(
117+ 'maas0:vlan=id:%d;maas1:vlan=id:%d;maas2:vlan=id:%d' % (
118+ vlan.id, vlan2.id, vlan3.id))
119+ )
120+ # Check that the interface names match the labels provided in the
121+ # constraints string.
122+ self.assertItemsEqual(
123+ ['maas0', 'maas1', 'maas2'],
124+ list(machine.interface_set.order_by('id').values_list(
125+ 'name', flat=True)))
126+
127+ def test_create_machine_uses_default_ifnames_if_discovered_mismatch(self):
128+ """This makes sure that if the discovered machine comes back with
129+ a different number of interfaces than the constraint string, the
130+ default (ethX) names are used.
131+ """
132+ discovered_machine = self.make_discovered_machine()
133+ self.patch(Machine, "set_default_storage_layout")
134+ self.patch(Machine, "set_initial_networking_configuration")
135+ self.patch(Machine, "start_commissioning")
136+ fabric = factory.make_Fabric()
137+ vlan = factory.make_VLAN(
138+ fabric=fabric, dhcp_on=True,
139+ primary_rack=factory.make_RackController())
140+ vlan2 = factory.make_VLAN(
141+ fabric=fabric, dhcp_on=False,
142+ primary_rack=factory.make_RackController())
143+ pod = factory.make_Pod()
144+ # The constraint here as two labels, but the discovered machine will
145+ # have three interfaces.
146+ machine = pod.create_machine(
147+ discovered_machine, factory.make_User(),
148+ interfaces=LabeledConstraintMap(
149+ 'maas0:vlan=id:%d;maas1:vlan=id:%d' % (
150+ vlan.id, vlan2.id))
151+ )
152+ # Check that the interface names use the ethX numbering, since the
153+ # provided constraints won't match the number of interfaces that were
154+ # returned.
155+ self.assertItemsEqual(
156+ ['eth0', 'eth1', 'eth2'],
157+ list(machine.interface_set.order_by('id').values_list(
158+ 'name', flat=True)))
159+
160+ def test_create_machine_uses_default_names_if_no_interfaces(self):
161+ discovered_machine = self.make_discovered_machine()
162+ self.patch(Machine, "set_default_storage_layout")
163+ self.patch(Machine, "set_initial_networking_configuration")
164+ self.patch(Machine, "start_commissioning")
165+ fabric = factory.make_Fabric()
166+ factory.make_VLAN(
167+ fabric=fabric, dhcp_on=True,
168+ primary_rack=factory.make_RackController())
169+ pod = factory.make_Pod()
170+ machine = pod.create_machine(discovered_machine, factory.make_User())
171+ # Check that the interface names match the labels provided in the
172+ # constraints string.
173+ self.assertItemsEqual(
174+ ['eth0', 'eth1', 'eth2'],
175+ list(machine.interface_set.order_by('id').values_list(
176+ 'name', flat=True)))
177+
178 def test_sync_pod_creates_new_machines_connected_to_dhcp_vlan(self):
179 discovered = self.make_discovered_pod()
180 mock_set_default_storage_layout = self.patch(

Subscribers

People subscribed via source and target branches