Merge lp:~blake-rouse/maas/fix-1566336-1.9 into lp:maas/1.9
- fix-1566336-1.9
- Merge into 1.9
Proposed by
Blake Rouse
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4552 |
Proposed branch: | lp:~blake-rouse/maas/fix-1566336-1.9 |
Merge into: | lp:maas/1.9 |
Diff against target: |
1205 lines (+409/-138) 10 files modified
src/maasserver/api/tests/test_interfaces.py (+21/-13) src/maasserver/forms_interface.py (+55/-12) src/maasserver/models/interface.py (+3/-2) src/maasserver/models/signals/interfaces.py (+58/-18) src/maasserver/models/signals/tests/test_interfaces.py (+46/-0) src/maasserver/models/tests/test_interface.py (+55/-23) src/maasserver/models/tests/test_staticipaddress.py (+3/-0) src/maasserver/testing/factory.py (+17/-4) src/maasserver/tests/test_forms_interface.py (+137/-57) src/maasserver/websockets/handlers/tests/test_node.py (+14/-9) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-1566336-1.9 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+291009@code.launchpad.net |
Commit message
Remove links on parents when added to a bond. Only allow physical interface and bond interface to be attached to the untagged VLAN on a fabric.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/api/tests/test_interfaces.py' | |||
2 | --- src/maasserver/api/tests/test_interfaces.py 2015-12-04 21:23:28 +0000 | |||
3 | +++ src/maasserver/api/tests/test_interfaces.py 2016-04-05 16:02:24 +0000 | |||
4 | @@ -56,14 +56,14 @@ | |||
5 | 56 | def make_complex_interface(node, name=None): | 56 | def make_complex_interface(node, name=None): |
6 | 57 | """Makes interface with parents and children.""" | 57 | """Makes interface with parents and children.""" |
7 | 58 | fabric = factory.make_Fabric() | 58 | fabric = factory.make_Fabric() |
9 | 59 | vlan_5 = factory.make_VLAN(vid=5, fabric=fabric) | 59 | untagged = fabric.get_default_vlan() |
10 | 60 | nic_0 = factory.make_Interface( | 60 | nic_0 = factory.make_Interface( |
12 | 61 | INTERFACE_TYPE.PHYSICAL, vlan=vlan_5, node=node) | 61 | INTERFACE_TYPE.PHYSICAL, vlan=untagged, node=node) |
13 | 62 | nic_1 = factory.make_Interface( | 62 | nic_1 = factory.make_Interface( |
15 | 63 | INTERFACE_TYPE.PHYSICAL, vlan=vlan_5, node=node) | 63 | INTERFACE_TYPE.PHYSICAL, vlan=untagged, node=node) |
16 | 64 | parents = [nic_0, nic_1] | 64 | parents = [nic_0, nic_1] |
17 | 65 | bond_interface = factory.make_Interface( | 65 | bond_interface = factory.make_Interface( |
19 | 66 | INTERFACE_TYPE.BOND, mac_address=nic_0.mac_address, vlan=vlan_5, | 66 | INTERFACE_TYPE.BOND, mac_address=nic_0.mac_address, vlan=untagged, |
20 | 67 | parents=parents, name=name) | 67 | parents=parents, name=name) |
21 | 68 | vlan_10 = factory.make_VLAN(vid=10, fabric=fabric) | 68 | vlan_10 = factory.make_VLAN(vid=10, fabric=fabric) |
22 | 69 | vlan_nic_10 = factory.make_Interface( | 69 | vlan_nic_10 = factory.make_Interface( |
23 | @@ -117,7 +117,8 @@ | |||
24 | 117 | node = factory.make_Node(status=status) | 117 | node = factory.make_Node(status=status) |
25 | 118 | mac = factory.make_mac_address() | 118 | mac = factory.make_mac_address() |
26 | 119 | name = factory.make_name("eth") | 119 | name = factory.make_name("eth") |
28 | 120 | vlan = factory.make_VLAN() | 120 | fabric = factory.make_Fabric() |
29 | 121 | vlan = fabric.get_default_vlan() | ||
30 | 121 | tags = [ | 122 | tags = [ |
31 | 122 | factory.make_name("tag") | 123 | factory.make_name("tag") |
32 | 123 | for _ in range(3) | 124 | for _ in range(3) |
33 | @@ -150,7 +151,8 @@ | |||
34 | 150 | owner=self.logged_in_user, installable=False, parent=parent) | 151 | owner=self.logged_in_user, installable=False, parent=parent) |
35 | 151 | mac = factory.make_mac_address() | 152 | mac = factory.make_mac_address() |
36 | 152 | name = factory.make_name("eth") | 153 | name = factory.make_name("eth") |
38 | 153 | vlan = factory.make_VLAN() | 154 | fabric = factory.make_Fabric() |
39 | 155 | vlan = fabric.get_default_vlan() | ||
40 | 154 | tags = [ | 156 | tags = [ |
41 | 155 | factory.make_name("tag") | 157 | factory.make_name("tag") |
42 | 156 | for _ in range(3) | 158 | for _ in range(3) |
43 | @@ -183,7 +185,8 @@ | |||
44 | 183 | node = factory.make_Node(status=status) | 185 | node = factory.make_Node(status=status) |
45 | 184 | mac = factory.make_mac_address() | 186 | mac = factory.make_mac_address() |
46 | 185 | name = factory.make_name("eth") | 187 | name = factory.make_name("eth") |
48 | 186 | vlan = factory.make_VLAN() | 188 | fabric = factory.make_Fabric() |
49 | 189 | vlan = fabric.get_default_vlan() | ||
50 | 187 | tags = [ | 190 | tags = [ |
51 | 188 | factory.make_name("tag") | 191 | factory.make_name("tag") |
52 | 189 | for _ in range(3) | 192 | for _ in range(3) |
53 | @@ -279,7 +282,8 @@ | |||
54 | 279 | interface_on_other_node = factory.make_Interface( | 282 | interface_on_other_node = factory.make_Interface( |
55 | 280 | INTERFACE_TYPE.PHYSICAL) | 283 | INTERFACE_TYPE.PHYSICAL) |
56 | 281 | name = factory.make_name("eth") | 284 | name = factory.make_name("eth") |
58 | 282 | vlan = factory.make_VLAN() | 285 | fabric = factory.make_Fabric() |
59 | 286 | vlan = fabric.get_default_vlan() | ||
60 | 283 | uri = get_interfaces_uri(node) | 287 | uri = get_interfaces_uri(node) |
61 | 284 | response = self.client.post(uri, { | 288 | response = self.client.post(uri, { |
62 | 285 | "op": "create_physical", | 289 | "op": "create_physical", |
63 | @@ -299,7 +303,8 @@ | |||
64 | 299 | self.become_admin() | 303 | self.become_admin() |
65 | 300 | for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): | 304 | for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): |
66 | 301 | node = factory.make_Node(status=status) | 305 | node = factory.make_Node(status=status) |
68 | 302 | vlan = factory.make_VLAN() | 306 | fabric = factory.make_Fabric() |
69 | 307 | vlan = fabric.get_default_vlan() | ||
70 | 303 | parent_1_iface = factory.make_Interface( | 308 | parent_1_iface = factory.make_Interface( |
71 | 304 | INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node) | 309 | INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node) |
72 | 305 | parent_2_iface = factory.make_Interface( | 310 | parent_2_iface = factory.make_Interface( |
73 | @@ -402,7 +407,7 @@ | |||
74 | 402 | self.assertEqual( | 407 | self.assertEqual( |
75 | 403 | httplib.CONFLICT, response.status_code, response.content) | 408 | httplib.CONFLICT, response.status_code, response.content) |
76 | 404 | 409 | ||
78 | 405 | def test_create_bond_requires_name_vlan_and_parents(self): | 410 | def test_create_bond_requires_name_and_parents(self): |
79 | 406 | self.become_admin() | 411 | self.become_admin() |
80 | 407 | node = factory.make_Node(status=NODE_STATUS.READY) | 412 | node = factory.make_Node(status=NODE_STATUS.READY) |
81 | 408 | uri = get_interfaces_uri(node) | 413 | uri = get_interfaces_uri(node) |
82 | @@ -415,7 +420,6 @@ | |||
83 | 415 | self.assertEquals({ | 420 | self.assertEquals({ |
84 | 416 | "mac_address": ["This field cannot be blank."], | 421 | "mac_address": ["This field cannot be blank."], |
85 | 417 | "name": ["This field is required."], | 422 | "name": ["This field is required."], |
86 | 418 | "vlan": ["This field is required."], | ||
87 | 419 | "parents": ["A Bond interface must have one or more parents."], | 423 | "parents": ["A Bond interface must have one or more parents."], |
88 | 420 | }, json.loads(response.content)) | 424 | }, json.loads(response.content)) |
89 | 421 | 425 | ||
90 | @@ -634,7 +638,8 @@ | |||
91 | 634 | interface = factory.make_Interface( | 638 | interface = factory.make_Interface( |
92 | 635 | INTERFACE_TYPE.PHYSICAL, node=node) | 639 | INTERFACE_TYPE.PHYSICAL, node=node) |
93 | 636 | new_name = factory.make_name("name") | 640 | new_name = factory.make_name("name") |
95 | 637 | new_vlan = factory.make_VLAN() | 641 | new_fabric = factory.make_Fabric() |
96 | 642 | new_vlan = new_fabric.get_default_vlan() | ||
97 | 638 | uri = get_interface_uri(interface) | 643 | uri = get_interface_uri(interface) |
98 | 639 | response = self.client.put(uri, { | 644 | response = self.client.put(uri, { |
99 | 640 | "name": new_name, | 645 | "name": new_name, |
100 | @@ -653,7 +658,8 @@ | |||
101 | 653 | interface = factory.make_Interface( | 658 | interface = factory.make_Interface( |
102 | 654 | INTERFACE_TYPE.PHYSICAL, node=device) | 659 | INTERFACE_TYPE.PHYSICAL, node=device) |
103 | 655 | new_name = factory.make_name("name") | 660 | new_name = factory.make_name("name") |
105 | 656 | new_vlan = factory.make_VLAN() | 661 | new_fabric = factory.make_Fabric() |
106 | 662 | new_vlan = new_fabric.get_default_vlan() | ||
107 | 657 | uri = get_interface_uri(interface) | 663 | uri = get_interface_uri(interface) |
108 | 658 | response = self.client.put(uri, { | 664 | response = self.client.put(uri, { |
109 | 659 | "name": new_name, | 665 | "name": new_name, |
110 | @@ -688,9 +694,11 @@ | |||
111 | 688 | node) | 694 | node) |
112 | 689 | physical_interface = factory.make_Interface( | 695 | physical_interface = factory.make_Interface( |
113 | 690 | INTERFACE_TYPE.PHYSICAL, node=node) | 696 | INTERFACE_TYPE.PHYSICAL, node=node) |
114 | 697 | new_vlan = factory.make_VLAN(fabric=physical_interface.vlan.fabric) | ||
115 | 691 | uri = get_interface_uri(vlan_10) | 698 | uri = get_interface_uri(vlan_10) |
116 | 692 | response = self.client.put(uri, { | 699 | response = self.client.put(uri, { |
117 | 693 | "parent": physical_interface.id, | 700 | "parent": physical_interface.id, |
118 | 701 | "vlan": new_vlan.id, | ||
119 | 694 | }) | 702 | }) |
120 | 695 | self.assertEqual( | 703 | self.assertEqual( |
121 | 696 | httplib.OK, response.status_code, response.content) | 704 | httplib.OK, response.status_code, response.content) |
122 | 697 | 705 | ||
123 | === modified file 'src/maasserver/forms_interface.py' | |||
124 | --- src/maasserver/forms_interface.py 2015-10-31 23:55:19 +0000 | |||
125 | +++ src/maasserver/forms_interface.py 2016-04-05 16:02:24 +0000 | |||
126 | @@ -196,6 +196,13 @@ | |||
127 | 196 | msg = "A physical interface cannot have parents." | 196 | msg = "A physical interface cannot have parents." |
128 | 197 | raise ValidationError({'parents': [msg]}) | 197 | raise ValidationError({'parents': [msg]}) |
129 | 198 | 198 | ||
130 | 199 | def clean_vlan(self): | ||
131 | 200 | new_vlan = self.cleaned_data.get('vlan') | ||
132 | 201 | if new_vlan and new_vlan.fabric.get_default_vlan() != new_vlan: | ||
133 | 202 | raise ValidationError( | ||
134 | 203 | "A physical interface can only belong to an untagged VLAN.") | ||
135 | 204 | return new_vlan | ||
136 | 205 | |||
137 | 199 | def clean(self): | 206 | def clean(self): |
138 | 200 | cleaned_data = super(PhysicalInterfaceForm, self).clean() | 207 | cleaned_data = super(PhysicalInterfaceForm, self).clean() |
139 | 201 | new_name = cleaned_data.get('name') | 208 | new_name = cleaned_data.get('name') |
140 | @@ -233,11 +240,25 @@ | |||
141 | 233 | "in a bond.") | 240 | "in a bond.") |
142 | 234 | return parents | 241 | return parents |
143 | 235 | 242 | ||
144 | 243 | def clean_vlan(self): | ||
145 | 244 | new_vlan = self.cleaned_data.get('vlan') | ||
146 | 245 | if new_vlan and new_vlan.fabric.get_default_vlan() == new_vlan: | ||
147 | 246 | raise ValidationError( | ||
148 | 247 | "A VLAN interface can only belong to a tagged VLAN.") | ||
149 | 248 | return new_vlan | ||
150 | 249 | |||
151 | 236 | def clean(self): | 250 | def clean(self): |
152 | 237 | cleaned_data = super(VLANInterfaceForm, self).clean() | 251 | cleaned_data = super(VLANInterfaceForm, self).clean() |
153 | 238 | if self.fields_ok(['vlan', 'parents']): | 252 | if self.fields_ok(['vlan', 'parents']): |
154 | 239 | new_vlan = self.cleaned_data.get('vlan') | 253 | new_vlan = self.cleaned_data.get('vlan') |
155 | 240 | if new_vlan: | 254 | if new_vlan: |
156 | 255 | # VLAN needs to be the in the same fabric as the parent. | ||
157 | 256 | parent = self.cleaned_data.get('parents')[0] | ||
158 | 257 | if parent.vlan.fabric_id != new_vlan.fabric_id: | ||
159 | 258 | set_form_error( | ||
160 | 259 | self, "vlan", | ||
161 | 260 | "A VLAN interface can only belong to a tagged VLAN on " | ||
162 | 261 | "the same fabric as its parent interface.") | ||
163 | 241 | name = build_vlan_interface_name( | 262 | name = build_vlan_interface_name( |
164 | 242 | self.cleaned_data.get('parents').first(), new_vlan) | 263 | self.cleaned_data.get('parents').first(), new_vlan) |
165 | 243 | self.clean_interface_name_uniqueness(name) | 264 | self.clean_interface_name_uniqueness(name) |
166 | @@ -281,6 +302,15 @@ | |||
167 | 281 | 'name', | 302 | 'name', |
168 | 282 | ) | 303 | ) |
169 | 283 | 304 | ||
170 | 305 | def __init__(self, *args, **kwargs): | ||
171 | 306 | super(BondInterfaceForm, self).__init__(*args, **kwargs) | ||
172 | 307 | # Allow VLAN to be blank when creating. | ||
173 | 308 | instance = kwargs.get("instance", None) | ||
174 | 309 | if instance is not None and instance.id is not None: | ||
175 | 310 | self.fields['vlan'].required = True | ||
176 | 311 | else: | ||
177 | 312 | self.fields['vlan'].required = False | ||
178 | 313 | |||
179 | 284 | def clean_parents(self): | 314 | def clean_parents(self): |
180 | 285 | parents = self.get_clean_parents() | 315 | parents = self.get_clean_parents() |
181 | 286 | if parents is None: | 316 | if parents is None: |
182 | @@ -290,9 +320,16 @@ | |||
183 | 290 | raise ValidationError({'parents': [msg]}) | 320 | raise ValidationError({'parents': [msg]}) |
184 | 291 | return parents | 321 | return parents |
185 | 292 | 322 | ||
186 | 323 | def clean_vlan(self): | ||
187 | 324 | new_vlan = self.cleaned_data.get('vlan') | ||
188 | 325 | if new_vlan and new_vlan.fabric.get_default_vlan() != new_vlan: | ||
189 | 326 | raise ValidationError( | ||
190 | 327 | "A bond interface can only belong to an untagged VLAN.") | ||
191 | 328 | return new_vlan | ||
192 | 329 | |||
193 | 293 | def clean(self): | 330 | def clean(self): |
194 | 294 | cleaned_data = super(BondInterfaceForm, self).clean() | 331 | cleaned_data = super(BondInterfaceForm, self).clean() |
196 | 295 | if self.fields_ok(['parents']): | 332 | if self.fields_ok(['vlan', 'parents']): |
197 | 296 | parents = self.cleaned_data.get('parents') | 333 | parents = self.cleaned_data.get('parents') |
198 | 297 | # Set the mac_address if its missing and the interface is being | 334 | # Set the mac_address if its missing and the interface is being |
199 | 298 | # created. | 335 | # created. |
200 | @@ -333,6 +370,23 @@ | |||
201 | 333 | self, 'parents', | 370 | self, 'parents', |
202 | 334 | "%s is already in-use by another interface." % ( | 371 | "%s is already in-use by another interface." % ( |
203 | 335 | ', '.join(sorted(parents_with_other_children)))) | 372 | ', '.join(sorted(parents_with_other_children)))) |
204 | 373 | |||
205 | 374 | # When creating the bond set VLAN to the same as the parents | ||
206 | 375 | # and check that the parents all belong to the same VLAN. | ||
207 | 376 | if self.instance.id is None: | ||
208 | 377 | vlan = self.cleaned_data.get('vlan') | ||
209 | 378 | if vlan is None: | ||
210 | 379 | vlan = parents[0].vlan | ||
211 | 380 | self.cleaned_data['vlan'] = vlan | ||
212 | 381 | parent_vlans = { | ||
213 | 382 | parent.vlan | ||
214 | 383 | for parent in parents | ||
215 | 384 | } | ||
216 | 385 | if parent_vlans != set([vlan]): | ||
217 | 386 | set_form_error( | ||
218 | 387 | self, 'parents', | ||
219 | 388 | "All parents must belong to the same VLAN.") | ||
220 | 389 | |||
221 | 336 | return cleaned_data | 390 | return cleaned_data |
222 | 337 | 391 | ||
223 | 338 | def set_extra_parameters(self, interface, created): | 392 | def set_extra_parameters(self, interface, created): |
224 | @@ -356,17 +410,6 @@ | |||
225 | 356 | elif created: | 410 | elif created: |
226 | 357 | interface.params[bond_field] = self.fields[bond_field].initial | 411 | interface.params[bond_field] = self.fields[bond_field].initial |
227 | 358 | 412 | ||
228 | 359 | def save(self, *args, **kwargs): | ||
229 | 360 | """Persist the interface into the database.""" | ||
230 | 361 | created = self.instance.id is None | ||
231 | 362 | interface = super(BondInterfaceForm, self).save() | ||
232 | 363 | if created: | ||
233 | 364 | # Bond was created we remove all the links on the parent interfaces | ||
234 | 365 | # and ensure that the bond has atleast a LINK_UP. | ||
235 | 366 | for parent in interface.parents.all(): | ||
236 | 367 | parent.clear_all_links(clearing_config=True) | ||
237 | 368 | return interface | ||
238 | 369 | |||
239 | 370 | 413 | ||
240 | 371 | INTERFACE_FORM_MAPPING = { | 414 | INTERFACE_FORM_MAPPING = { |
241 | 372 | INTERFACE_TYPE.PHYSICAL: PhysicalInterfaceForm, | 415 | INTERFACE_TYPE.PHYSICAL: PhysicalInterfaceForm, |
242 | 373 | 416 | ||
243 | === modified file 'src/maasserver/models/interface.py' | |||
244 | --- src/maasserver/models/interface.py 2015-12-11 00:24:18 +0000 | |||
245 | +++ src/maasserver/models/interface.py 2016-04-05 16:02:24 +0000 | |||
246 | @@ -922,11 +922,12 @@ | |||
247 | 922 | # Nothing to do, already has links. | 922 | # Nothing to do, already has links. |
248 | 923 | return | 923 | return |
249 | 924 | else: | 924 | else: |
251 | 925 | # Use an associated subnet if it exists, else it will just be a | 925 | # Use an associated subnet if it exists and its on the same VLAN |
252 | 926 | # the interface is currently connected, else it will just be a | ||
253 | 926 | # LINK_UP without a subnet. | 927 | # LINK_UP without a subnet. |
254 | 927 | discovered_address = self.ip_addresses.filter( | 928 | discovered_address = self.ip_addresses.filter( |
255 | 928 | alloc_type=IPADDRESS_TYPE.DISCOVERED, | 929 | alloc_type=IPADDRESS_TYPE.DISCOVERED, |
257 | 929 | subnet__isnull=False).first() | 930 | subnet__vlan=self.vlan).first() |
258 | 930 | if discovered_address is not None: | 931 | if discovered_address is not None: |
259 | 931 | subnet = discovered_address.subnet | 932 | subnet = discovered_address.subnet |
260 | 932 | else: | 933 | else: |
261 | 933 | 934 | ||
262 | === modified file 'src/maasserver/models/signals/interfaces.py' | |||
263 | --- src/maasserver/models/signals/interfaces.py 2015-11-03 12:38:17 +0000 | |||
264 | +++ src/maasserver/models/signals/interfaces.py 2016-04-05 16:02:24 +0000 | |||
265 | @@ -14,6 +14,7 @@ | |||
266 | 14 | __metaclass__ = type | 14 | __metaclass__ = type |
267 | 15 | __all__ = [] | 15 | __all__ = [] |
268 | 16 | 16 | ||
269 | 17 | from django.db.models.signals import post_save | ||
270 | 17 | from maasserver.enum import ( | 18 | from maasserver.enum import ( |
271 | 18 | INTERFACE_TYPE, | 19 | INTERFACE_TYPE, |
272 | 19 | IPADDRESS_TYPE, | 20 | IPADDRESS_TYPE, |
273 | @@ -27,6 +28,14 @@ | |||
274 | 27 | from maasserver.utils.signals import connect_to_field_change | 28 | from maasserver.utils.signals import connect_to_field_change |
275 | 28 | 29 | ||
276 | 29 | 30 | ||
277 | 31 | INTERFACE_CLASSES = [ | ||
278 | 32 | Interface, | ||
279 | 33 | PhysicalInterface, | ||
280 | 34 | BondInterface, | ||
281 | 35 | VLANInterface, | ||
282 | 36 | ] | ||
283 | 37 | |||
284 | 38 | |||
285 | 30 | def interface_enabled_or_disabled(instance, old_values, **kwargs): | 39 | def interface_enabled_or_disabled(instance, old_values, **kwargs): |
286 | 31 | """When an interface is enabled be sure at minimum a LINK_UP is created. | 40 | """When an interface is enabled be sure at minimum a LINK_UP is created. |
287 | 32 | When an interface is disabled make sure that all its links are removed, | 41 | When an interface is disabled make sure that all its links are removed, |
288 | @@ -52,12 +61,10 @@ | |||
289 | 52 | ip_address, clearing_config=True) | 61 | ip_address, clearing_config=True) |
290 | 53 | 62 | ||
291 | 54 | 63 | ||
298 | 55 | connect_to_field_change( | 64 | for klass in INTERFACE_CLASSES: |
299 | 56 | interface_enabled_or_disabled, | 65 | connect_to_field_change( |
300 | 57 | Interface, ['enabled'], delete=False) | 66 | interface_enabled_or_disabled, |
301 | 58 | connect_to_field_change( | 67 | klass, ['enabled'], delete=False) |
296 | 59 | interface_enabled_or_disabled, | ||
297 | 60 | PhysicalInterface, ['enabled'], delete=False) | ||
302 | 61 | 68 | ||
303 | 62 | 69 | ||
304 | 63 | def interface_mtu_params_update(instance, old_values, **kwargs): | 70 | def interface_mtu_params_update(instance, old_values, **kwargs): |
305 | @@ -118,15 +125,48 @@ | |||
306 | 118 | parent.save() | 125 | parent.save() |
307 | 119 | 126 | ||
308 | 120 | 127 | ||
321 | 121 | connect_to_field_change( | 128 | for klass in INTERFACE_CLASSES: |
322 | 122 | interface_mtu_params_update, | 129 | connect_to_field_change( |
323 | 123 | Interface, ['params'], delete=False) | 130 | interface_mtu_params_update, |
324 | 124 | connect_to_field_change( | 131 | klass, ['params'], delete=False) |
325 | 125 | interface_mtu_params_update, | 132 | |
326 | 126 | PhysicalInterface, ['params'], delete=False) | 133 | |
327 | 127 | connect_to_field_change( | 134 | def update_bond_parents(sender, instance, created, **kwargs): |
328 | 128 | interface_mtu_params_update, | 135 | """Update bond parents when interface created.""" |
329 | 129 | BondInterface, ['params'], delete=False) | 136 | if instance.type == INTERFACE_TYPE.BOND: |
330 | 130 | connect_to_field_change( | 137 | for parent in instance.parents.all(): |
331 | 131 | interface_mtu_params_update, | 138 | # Make sure the parent has not links as well, just to be sure. |
332 | 132 | VLANInterface, ['params'], delete=False) | 139 | parent.clear_all_links(clearing_config=True) |
333 | 140 | if parent.vlan != instance.vlan: | ||
334 | 141 | parent.vlan = instance.vlan | ||
335 | 142 | parent.save() | ||
336 | 143 | |||
337 | 144 | |||
338 | 145 | for klass in INTERFACE_CLASSES: | ||
339 | 146 | post_save.connect( | ||
340 | 147 | update_bond_parents, sender=klass) | ||
341 | 148 | |||
342 | 149 | |||
343 | 150 | def interface_vlan_update(instance, old_values, **kwargs): | ||
344 | 151 | """When an interfaces VLAN is changed we need to remove all | ||
345 | 152 | links if the VLAN is different. | ||
346 | 153 | """ | ||
347 | 154 | new_vlan_id = instance.vlan_id | ||
348 | 155 | [old_vlan_id] = old_values | ||
349 | 156 | if new_vlan_id == old_vlan_id: | ||
350 | 157 | # Nothing changed do nothing. | ||
351 | 158 | return | ||
352 | 159 | if instance.node is None: | ||
353 | 160 | # Not assigned to a node. Nothing to do. | ||
354 | 161 | return | ||
355 | 162 | |||
356 | 163 | # Interface VLAN was changed on a machine or device. Remove all its | ||
357 | 164 | # links except the DISCOVERED ones. | ||
358 | 165 | instance.ip_addresses.exclude( | ||
359 | 166 | alloc_type=IPADDRESS_TYPE.DISCOVERED).delete() | ||
360 | 167 | |||
361 | 168 | |||
362 | 169 | for klass in INTERFACE_CLASSES: | ||
363 | 170 | connect_to_field_change( | ||
364 | 171 | interface_vlan_update, | ||
365 | 172 | klass, ['vlan_id'], delete=False) | ||
366 | 133 | 173 | ||
367 | === modified file 'src/maasserver/models/signals/tests/test_interfaces.py' | |||
368 | --- src/maasserver/models/signals/tests/test_interfaces.py 2015-11-03 12:38:17 +0000 | |||
369 | +++ src/maasserver/models/signals/tests/test_interfaces.py 2016-04-05 16:02:24 +0000 | |||
370 | @@ -132,3 +132,49 @@ | |||
371 | 132 | self.assertEquals({ | 132 | self.assertEquals({ |
372 | 133 | 'mtu': bond_mtu, | 133 | 'mtu': bond_mtu, |
373 | 134 | }, reload_object(physical3_interface).params) | 134 | }, reload_object(physical3_interface).params) |
374 | 135 | |||
375 | 136 | |||
376 | 137 | class TestUpdateBondParents(MAASServerTestCase): | ||
377 | 138 | |||
378 | 139 | def test__updates_bond_parents(self): | ||
379 | 140 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
380 | 141 | parent2 = factory.make_Interface( | ||
381 | 142 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | ||
382 | 143 | bond = factory.make_Interface( | ||
383 | 144 | INTERFACE_TYPE.BOND, parents=[parent1, parent2]) | ||
384 | 145 | self.assertEqual(bond.vlan, reload_object(parent1).vlan) | ||
385 | 146 | self.assertEqual(bond.vlan, reload_object(parent2).vlan) | ||
386 | 147 | |||
387 | 148 | def test__update_bond_clears_parent_links(self): | ||
388 | 149 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
389 | 150 | parent2 = factory.make_Interface( | ||
390 | 151 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | ||
391 | 152 | static_ip = factory.make_StaticIPAddress(interface=parent1) | ||
392 | 153 | factory.make_Interface( | ||
393 | 154 | INTERFACE_TYPE.BOND, parents=[parent1, parent2]) | ||
394 | 155 | self.assertIsNone(reload_object(static_ip)) | ||
395 | 156 | |||
396 | 157 | |||
397 | 158 | class TestInterfaceVLANUpdate(MAASServerTestCase): | ||
398 | 159 | |||
399 | 160 | scenarios = ( | ||
400 | 161 | ("node", { | ||
401 | 162 | "maker": factory.make_Node, | ||
402 | 163 | }), | ||
403 | 164 | ("device", { | ||
404 | 165 | "maker": factory.make_Device, | ||
405 | 166 | }), | ||
406 | 167 | ) | ||
407 | 168 | |||
408 | 169 | def test__removes_links(self): | ||
409 | 170 | node = self.maker() | ||
410 | 171 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | ||
411 | 172 | static_ip = factory.make_StaticIPAddress(interface=interface) | ||
412 | 173 | discovered_ip = factory.make_StaticIPAddress( | ||
413 | 174 | alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=interface) | ||
414 | 175 | new_fabric = factory.make_Fabric() | ||
415 | 176 | new_vlan = new_fabric.get_default_vlan() | ||
416 | 177 | interface.vlan = new_vlan | ||
417 | 178 | interface.save() | ||
418 | 179 | self.assertIsNone(reload_object(static_ip)) | ||
419 | 180 | self.assertIsNotNone(reload_object(discovered_ip)) | ||
420 | 135 | 181 | ||
421 | === modified file 'src/maasserver/models/tests/test_interface.py' | |||
422 | --- src/maasserver/models/tests/test_interface.py 2015-12-11 00:24:23 +0000 | |||
423 | +++ src/maasserver/models/tests/test_interface.py 2016-04-05 16:02:24 +0000 | |||
424 | @@ -230,31 +230,51 @@ | |||
425 | 230 | ["id:%s" % iface1.id, "id:%s" % iface2.id]), [iface1, iface2]) | 230 | ["id:%s" % iface1.id, "id:%s" % iface2.id]), [iface1, iface2]) |
426 | 231 | 231 | ||
427 | 232 | def test__filter_by_specifiers_matches_vid(self): | 232 | def test__filter_by_specifiers_matches_vid(self): |
439 | 233 | iface1 = factory.make_Interface() | 233 | fabric1 = factory.make_Fabric() |
440 | 234 | iface2 = factory.make_Interface() | 234 | parent1 = factory.make_Interface( |
441 | 235 | self.assertItemsEqual( | 235 | INTERFACE_TYPE.PHYSICAL, vlan=fabric1.get_default_vlan()) |
442 | 236 | Interface.objects.filter_by_specifiers( | 236 | vlan1 = factory.make_VLAN(fabric=fabric1) |
443 | 237 | "vid:%s" % iface1.vlan.vid), [iface1]) | 237 | iface1 = factory.make_Interface( |
444 | 238 | self.assertItemsEqual( | 238 | INTERFACE_TYPE.VLAN, vlan=vlan1, parents=[parent1]) |
445 | 239 | Interface.objects.filter_by_specifiers( | 239 | fabric2 = factory.make_Fabric() |
446 | 240 | "vid:%s" % iface2.vlan.vid), [iface2]) | 240 | parent2 = factory.make_Interface( |
447 | 241 | self.assertItemsEqual( | 241 | INTERFACE_TYPE.PHYSICAL, vlan=fabric2.get_default_vlan()) |
448 | 242 | Interface.objects.filter_by_specifiers( | 242 | vlan2 = factory.make_VLAN(fabric=fabric2) |
449 | 243 | ["vid:%s" % iface1.vlan.vid, "vid:%s" % iface2.vlan.vid]), | 243 | iface2 = factory.make_Interface( |
450 | 244 | INTERFACE_TYPE.VLAN, vlan=vlan2, parents=[parent2]) | ||
451 | 245 | self.assertItemsEqual( | ||
452 | 246 | Interface.objects.filter_by_specifiers( | ||
453 | 247 | "vid:%s" % vlan1.vid), [iface1]) | ||
454 | 248 | self.assertItemsEqual( | ||
455 | 249 | Interface.objects.filter_by_specifiers( | ||
456 | 250 | "vid:%s" % vlan2.vid), [iface2]) | ||
457 | 251 | self.assertItemsEqual( | ||
458 | 252 | Interface.objects.filter_by_specifiers( | ||
459 | 253 | ["vid:%s" % vlan1.vid, "vid:%s" % vlan2.vid]), | ||
460 | 244 | [iface1, iface2]) | 254 | [iface1, iface2]) |
461 | 245 | 255 | ||
462 | 246 | def test__filter_by_specifiers_matches_vlan(self): | 256 | def test__filter_by_specifiers_matches_vlan(self): |
474 | 247 | iface1 = factory.make_Interface() | 257 | fabric1 = factory.make_Fabric() |
475 | 248 | iface2 = factory.make_Interface() | 258 | parent1 = factory.make_Interface( |
476 | 249 | self.assertItemsEqual( | 259 | INTERFACE_TYPE.PHYSICAL, vlan=fabric1.get_default_vlan()) |
477 | 250 | Interface.objects.filter_by_specifiers( | 260 | vlan1 = factory.make_VLAN(fabric=fabric1) |
478 | 251 | "vlan:%s" % iface1.vlan.vid), [iface1]) | 261 | iface1 = factory.make_Interface( |
479 | 252 | self.assertItemsEqual( | 262 | INTERFACE_TYPE.VLAN, vlan=vlan1, parents=[parent1]) |
480 | 253 | Interface.objects.filter_by_specifiers( | 263 | fabric2 = factory.make_Fabric() |
481 | 254 | "vlan:%s" % iface2.vlan.vid), [iface2]) | 264 | parent2 = factory.make_Interface( |
482 | 255 | self.assertItemsEqual( | 265 | INTERFACE_TYPE.PHYSICAL, vlan=fabric2.get_default_vlan()) |
483 | 256 | Interface.objects.filter_by_specifiers( | 266 | vlan2 = factory.make_VLAN(fabric=fabric2) |
484 | 257 | ["vlan:%s" % iface1.vlan.vid, "vlan:%s" % iface2.vlan.vid]), | 267 | iface2 = factory.make_Interface( |
485 | 268 | INTERFACE_TYPE.VLAN, vlan=vlan2, parents=[parent2]) | ||
486 | 269 | self.assertItemsEqual( | ||
487 | 270 | Interface.objects.filter_by_specifiers( | ||
488 | 271 | "vlan:%s" % vlan1.vid), [iface1]) | ||
489 | 272 | self.assertItemsEqual( | ||
490 | 273 | Interface.objects.filter_by_specifiers( | ||
491 | 274 | "vlan:%s" % vlan2.vid), [iface2]) | ||
492 | 275 | self.assertItemsEqual( | ||
493 | 276 | Interface.objects.filter_by_specifiers( | ||
494 | 277 | ["vlan:%s" % vlan1.vid, "vlan:%s" % vlan2.vid]), | ||
495 | 258 | [iface1, iface2]) | 278 | [iface1, iface2]) |
496 | 259 | 279 | ||
497 | 260 | def test__filter_by_specifiers_matches_subnet_specifier(self): | 280 | def test__filter_by_specifiers_matches_subnet_specifier(self): |
498 | @@ -1515,7 +1535,7 @@ | |||
499 | 1515 | 1, interface.ip_addresses.count(), | 1535 | 1, interface.ip_addresses.count(), |
500 | 1516 | "Should only have one IP address assigned.") | 1536 | "Should only have one IP address assigned.") |
501 | 1517 | 1537 | ||
503 | 1518 | def test__creates_link_up_to_discovered_subnet(self): | 1538 | def test__creates_link_up_to_discovered_subnet_on_same_vlan(self): |
504 | 1519 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 1539 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
505 | 1520 | subnet = factory.make_Subnet(vlan=interface.vlan) | 1540 | subnet = factory.make_Subnet(vlan=interface.vlan) |
506 | 1521 | factory.make_StaticIPAddress( | 1541 | factory.make_StaticIPAddress( |
507 | @@ -1527,6 +1547,18 @@ | |||
508 | 1527 | self.assertIsNone(link_ip.ip) | 1547 | self.assertIsNone(link_ip.ip) |
509 | 1528 | self.assertEquals(subnet, link_ip.subnet) | 1548 | self.assertEquals(subnet, link_ip.subnet) |
510 | 1529 | 1549 | ||
511 | 1550 | def test__creates_link_up_to_no_subnet_when_on_different_vlan(self): | ||
512 | 1551 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
513 | 1552 | subnet = factory.make_Subnet() | ||
514 | 1553 | factory.make_StaticIPAddress( | ||
515 | 1554 | alloc_type=IPADDRESS_TYPE.DISCOVERED, ip="", | ||
516 | 1555 | subnet=subnet, interface=interface) | ||
517 | 1556 | interface.ensure_link_up() | ||
518 | 1557 | link_ip = interface.ip_addresses.filter( | ||
519 | 1558 | alloc_type=IPADDRESS_TYPE.STICKY).first() | ||
520 | 1559 | self.assertIsNone(link_ip.ip) | ||
521 | 1560 | self.assertIsNone(link_ip.subnet) | ||
522 | 1561 | |||
523 | 1530 | def test__creates_link_up_to_no_subnet(self): | 1562 | def test__creates_link_up_to_no_subnet(self): |
524 | 1531 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 1563 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
525 | 1532 | interface.ensure_link_up() | 1564 | interface.ensure_link_up() |
526 | 1533 | 1565 | ||
527 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' | |||
528 | --- src/maasserver/models/tests/test_staticipaddress.py 2015-11-17 00:33:35 +0000 | |||
529 | +++ src/maasserver/models/tests/test_staticipaddress.py 2016-04-05 16:02:24 +0000 | |||
530 | @@ -805,6 +805,7 @@ | |||
531 | 805 | 805 | ||
532 | 806 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): | 806 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): |
533 | 807 | self.patch_autospec(interface_module, "update_host_maps") | 807 | self.patch_autospec(interface_module, "update_host_maps") |
534 | 808 | self.patch_autospec(interface_module, "remove_host_maps") | ||
535 | 808 | subnet = factory.make_Subnet( | 809 | subnet = factory.make_Subnet( |
536 | 809 | cidr=unicode(factory.make_ipv4_network().cidr)) | 810 | cidr=unicode(factory.make_ipv4_network().cidr)) |
537 | 810 | node = factory.make_Node_with_Interface_on_Subnet( | 811 | node = factory.make_Node_with_Interface_on_Subnet( |
538 | @@ -835,6 +836,7 @@ | |||
539 | 835 | 836 | ||
540 | 836 | def test_get_hostname_ip_mapping_prefers_bond_with_boot_interface(self): | 837 | def test_get_hostname_ip_mapping_prefers_bond_with_boot_interface(self): |
541 | 837 | self.patch_autospec(interface_module, "update_host_maps") | 838 | self.patch_autospec(interface_module, "update_host_maps") |
542 | 839 | self.patch_autospec(interface_module, "remove_host_maps") | ||
543 | 838 | subnet = factory.make_Subnet( | 840 | subnet = factory.make_Subnet( |
544 | 839 | cidr=unicode(factory.make_ipv4_network().cidr)) | 841 | cidr=unicode(factory.make_ipv4_network().cidr)) |
545 | 840 | node = factory.make_Node_with_Interface_on_Subnet( | 842 | node = factory.make_Node_with_Interface_on_Subnet( |
546 | @@ -859,6 +861,7 @@ | |||
547 | 859 | 861 | ||
548 | 860 | def test_get_hostname_ip_mapping_ignores_bond_without_boot_interface(self): | 862 | def test_get_hostname_ip_mapping_ignores_bond_without_boot_interface(self): |
549 | 861 | self.patch_autospec(interface_module, "update_host_maps") | 863 | self.patch_autospec(interface_module, "update_host_maps") |
550 | 864 | self.patch_autospec(interface_module, "remove_host_maps") | ||
551 | 862 | subnet = factory.make_Subnet( | 865 | subnet = factory.make_Subnet( |
552 | 863 | cidr=unicode(factory.make_ipv4_network().cidr)) | 866 | cidr=unicode(factory.make_ipv4_network().cidr)) |
553 | 864 | node = factory.make_Node_with_Interface_on_Subnet( | 867 | node = factory.make_Node_with_Interface_on_Subnet( |
554 | 865 | 868 | ||
555 | === modified file 'src/maasserver/testing/factory.py' | |||
556 | --- src/maasserver/testing/factory.py 2015-12-11 00:24:23 +0000 | |||
557 | +++ src/maasserver/testing/factory.py 2016-04-05 16:02:24 +0000 | |||
558 | @@ -335,7 +335,6 @@ | |||
559 | 335 | acquired = node.status in ALLOCATED_NODE_STATUSES | 335 | acquired = node.status in ALLOCATED_NODE_STATUSES |
560 | 336 | self.make_Filesystem( | 336 | self.make_Filesystem( |
561 | 337 | partition=root_partition, mount_point='/', acquired=acquired) | 337 | partition=root_partition, mount_point='/', acquired=acquired) |
562 | 338 | |||
563 | 339 | # Update the 'updated'/'created' fields with a call to 'update' | 338 | # Update the 'updated'/'created' fields with a call to 'update' |
564 | 340 | # preventing a call to save() from overriding the values. | 339 | # preventing a call to save() from overriding the values. |
565 | 341 | if updated is not None: | 340 | if updated is not None: |
566 | @@ -591,7 +590,9 @@ | |||
567 | 591 | node = self.make_Node( | 590 | node = self.make_Node( |
568 | 592 | nodegroup=nodegroup, fabric=fabric, **kwargs) | 591 | nodegroup=nodegroup, fabric=fabric, **kwargs) |
569 | 593 | if vlan is None: | 592 | if vlan is None: |
571 | 594 | vlan = self.make_VLAN(fabric=fabric) | 593 | if fabric is None: |
572 | 594 | fabric = factory.make_Fabric() | ||
573 | 595 | vlan = fabric.get_default_vlan() | ||
574 | 595 | if subnet is None: | 596 | if subnet is None: |
575 | 596 | subnet = self.make_Subnet(vlan=vlan, cidr=cidr) | 597 | subnet = self.make_Subnet(vlan=vlan, cidr=cidr) |
576 | 597 | # Check if the subnet already has a managed interface. | 598 | # Check if the subnet already has a managed interface. |
577 | @@ -630,7 +631,7 @@ | |||
578 | 630 | self.make_StaticIPAddress( | 631 | self.make_StaticIPAddress( |
579 | 631 | alloc_type=IPADDRESS_TYPE.STICKY, ip="", | 632 | alloc_type=IPADDRESS_TYPE.STICKY, ip="", |
580 | 632 | subnet=subnet, interface=interface) | 633 | subnet=subnet, interface=interface) |
582 | 633 | return node | 634 | return reload_object(node) |
583 | 634 | 635 | ||
584 | 635 | UNDEFINED = float('NaN') | 636 | UNDEFINED = float('NaN') |
585 | 636 | 637 | ||
586 | @@ -786,7 +787,19 @@ | |||
587 | 786 | if iftype is None: | 787 | if iftype is None: |
588 | 787 | iftype = INTERFACE_TYPE.PHYSICAL | 788 | iftype = INTERFACE_TYPE.PHYSICAL |
589 | 788 | if vlan is None: | 789 | if vlan is None: |
591 | 789 | vlan = self.make_VLAN(fabric=fabric) | 790 | if fabric is not None: |
592 | 791 | if iftype == INTERFACE_TYPE.VLAN: | ||
593 | 792 | vlan = self.make_VLAN(fabric=fabric) | ||
594 | 793 | else: | ||
595 | 794 | vlan = fabric.get_default_vlan() | ||
596 | 795 | else: | ||
597 | 796 | if iftype == INTERFACE_TYPE.VLAN and parents: | ||
598 | 797 | vlan = self.make_VLAN(fabric=parents[0].vlan.fabric) | ||
599 | 798 | elif iftype == INTERFACE_TYPE.BOND and parents: | ||
600 | 799 | vlan = parents[0].vlan | ||
601 | 800 | else: | ||
602 | 801 | fabric = self.make_Fabric() | ||
603 | 802 | vlan = fabric.get_default_vlan() | ||
604 | 790 | if (mac_address is None and | 803 | if (mac_address is None and |
605 | 791 | iftype in [ | 804 | iftype in [ |
606 | 792 | INTERFACE_TYPE.PHYSICAL, | 805 | INTERFACE_TYPE.PHYSICAL, |
607 | 793 | 806 | ||
608 | === modified file 'src/maasserver/tests/test_forms_interface.py' | |||
609 | --- src/maasserver/tests/test_forms_interface.py 2015-10-31 23:55:19 +0000 | |||
610 | +++ src/maasserver/tests/test_forms_interface.py 2016-04-05 16:02:24 +0000 | |||
611 | @@ -32,6 +32,7 @@ | |||
612 | 32 | ) | 32 | ) |
613 | 33 | from maasserver.models.interface import build_vlan_interface_name | 33 | from maasserver.models.interface import build_vlan_interface_name |
614 | 34 | from maasserver.testing.factory import factory | 34 | from maasserver.testing.factory import factory |
615 | 35 | from maasserver.testing.orm import reload_object | ||
616 | 35 | from maasserver.testing.testcase import MAASServerTestCase | 36 | from maasserver.testing.testcase import MAASServerTestCase |
617 | 36 | from maasserver.utils.forms import compose_invalid_choice_text | 37 | from maasserver.utils.forms import compose_invalid_choice_text |
618 | 37 | from testtools import ExpectedException | 38 | from testtools import ExpectedException |
619 | @@ -67,7 +68,8 @@ | |||
620 | 67 | node = factory.make_Node() | 68 | node = factory.make_Node() |
621 | 68 | mac_address = factory.make_mac_address() | 69 | mac_address = factory.make_mac_address() |
622 | 69 | interface_name = 'eth0' | 70 | interface_name = 'eth0' |
624 | 70 | vlan = factory.make_VLAN() | 71 | fabric = factory.make_Fabric() |
625 | 72 | vlan = fabric.get_default_vlan() | ||
626 | 71 | tags = [ | 73 | tags = [ |
627 | 72 | factory.make_name("tag") | 74 | factory.make_name("tag") |
628 | 73 | for _ in range(3) | 75 | for _ in range(3) |
629 | @@ -93,7 +95,8 @@ | |||
630 | 93 | node = factory.make_Node() | 95 | node = factory.make_Node() |
631 | 94 | mac_address = factory.make_mac_address() | 96 | mac_address = factory.make_mac_address() |
632 | 95 | interface_name = 'eth0' | 97 | interface_name = 'eth0' |
634 | 96 | vlan = factory.make_VLAN() | 98 | fabric = factory.make_Fabric() |
635 | 99 | vlan = fabric.get_default_vlan() | ||
636 | 97 | tags = [ | 100 | tags = [ |
637 | 98 | factory.make_name("tag") | 101 | factory.make_name("tag") |
638 | 99 | for _ in range(3) | 102 | for _ in range(3) |
639 | @@ -113,7 +116,8 @@ | |||
640 | 113 | 116 | ||
641 | 114 | def test__requires_mac_address(self): | 117 | def test__requires_mac_address(self): |
642 | 115 | interface_name = 'eth0' | 118 | interface_name = 'eth0' |
644 | 116 | vlan = factory.make_VLAN() | 119 | fabric = factory.make_Fabric() |
645 | 120 | vlan = fabric.get_default_vlan() | ||
646 | 117 | form = PhysicalInterfaceForm( | 121 | form = PhysicalInterfaceForm( |
647 | 118 | node=factory.make_Node(), | 122 | node=factory.make_Node(), |
648 | 119 | data={ | 123 | data={ |
649 | @@ -128,7 +132,9 @@ | |||
650 | 128 | form.errors['mac_address'][0]) | 132 | form.errors['mac_address'][0]) |
651 | 129 | 133 | ||
652 | 130 | def test_rejects_interface_with_duplicate_name(self): | 134 | def test_rejects_interface_with_duplicate_name(self): |
654 | 131 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 135 | fabric = factory.make_Fabric() |
655 | 136 | vlan = fabric.get_default_vlan() | ||
656 | 137 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, vlan=vlan) | ||
657 | 132 | mac_address = factory.make_mac_address() | 138 | mac_address = factory.make_mac_address() |
658 | 133 | form = PhysicalInterfaceForm( | 139 | form = PhysicalInterfaceForm( |
659 | 134 | node=interface.node, | 140 | node=interface.node, |
660 | @@ -144,9 +150,30 @@ | |||
661 | 144 | "already has an interface named '%s'." % interface.name, | 150 | "already has an interface named '%s'." % interface.name, |
662 | 145 | form.errors['name'][0]) | 151 | form.errors['name'][0]) |
663 | 146 | 152 | ||
664 | 153 | def test_rejects_interface_on_tagged_vlan(self): | ||
665 | 154 | fabric = factory.make_Fabric() | ||
666 | 155 | interface = factory.make_Interface( | ||
667 | 156 | INTERFACE_TYPE.PHYSICAL, vlan=fabric.get_default_vlan()) | ||
668 | 157 | vlan = factory.make_VLAN(fabric=fabric) | ||
669 | 158 | mac_address = factory.make_mac_address() | ||
670 | 159 | form = PhysicalInterfaceForm( | ||
671 | 160 | node=interface.node, | ||
672 | 161 | data={ | ||
673 | 162 | 'name': factory.make_name("eth"), | ||
674 | 163 | 'mac_address': mac_address, | ||
675 | 164 | 'vlan': vlan.id, | ||
676 | 165 | }) | ||
677 | 166 | self.assertFalse(form.is_valid(), form.errors) | ||
678 | 167 | self.assertItemsEqual( | ||
679 | 168 | ['vlan'], form.errors.keys(), form.errors) | ||
680 | 169 | self.assertIn( | ||
681 | 170 | "A physical interface can only belong to an untagged VLAN.", | ||
682 | 171 | form.errors['vlan'][0]) | ||
683 | 172 | |||
684 | 147 | def test__rejects_parents(self): | 173 | def test__rejects_parents(self): |
685 | 148 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 174 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
687 | 149 | vlan = factory.make_VLAN() | 175 | fabric = factory.make_Fabric() |
688 | 176 | vlan = fabric.get_default_vlan() | ||
689 | 150 | form = PhysicalInterfaceForm( | 177 | form = PhysicalInterfaceForm( |
690 | 151 | node=parent.node, | 178 | node=parent.node, |
691 | 152 | data={ | 179 | data={ |
692 | @@ -166,7 +193,8 @@ | |||
693 | 166 | interface = factory.make_Interface( | 193 | interface = factory.make_Interface( |
694 | 167 | INTERFACE_TYPE.PHYSICAL, name='eth0') | 194 | INTERFACE_TYPE.PHYSICAL, name='eth0') |
695 | 168 | new_name = 'eth1' | 195 | new_name = 'eth1' |
697 | 169 | new_vlan = factory.make_VLAN(vid=33) | 196 | new_fabric = factory.make_Fabric() |
698 | 197 | new_vlan = new_fabric.get_default_vlan() | ||
699 | 170 | form = PhysicalInterfaceForm( | 198 | form = PhysicalInterfaceForm( |
700 | 171 | instance=interface, | 199 | instance=interface, |
701 | 172 | data={ | 200 | data={ |
702 | @@ -187,7 +215,8 @@ | |||
703 | 187 | node = factory.make_Node() | 215 | node = factory.make_Node() |
704 | 188 | mac_address = factory.make_mac_address() | 216 | mac_address = factory.make_mac_address() |
705 | 189 | interface_name = 'eth0' | 217 | interface_name = 'eth0' |
707 | 190 | vlan = factory.make_VLAN() | 218 | fabric = factory.make_Fabric() |
708 | 219 | vlan = fabric.get_default_vlan() | ||
709 | 191 | tags = [ | 220 | tags = [ |
710 | 192 | factory.make_name("tag") | 221 | factory.make_name("tag") |
711 | 193 | for _ in range(3) | 222 | for _ in range(3) |
712 | @@ -226,7 +255,8 @@ | |||
713 | 226 | "autoconf": autoconf, | 255 | "autoconf": autoconf, |
714 | 227 | } | 256 | } |
715 | 228 | new_name = 'eth1' | 257 | new_name = 'eth1' |
717 | 229 | new_vlan = factory.make_VLAN(vid=33) | 258 | new_fabric = factory.make_Fabric() |
718 | 259 | new_vlan = new_fabric.get_default_vlan() | ||
719 | 230 | form = PhysicalInterfaceForm( | 260 | form = PhysicalInterfaceForm( |
720 | 231 | instance=interface, | 261 | instance=interface, |
721 | 232 | data={ | 262 | data={ |
722 | @@ -299,7 +329,7 @@ | |||
723 | 299 | 329 | ||
724 | 300 | def test__creates_vlan_interface(self): | 330 | def test__creates_vlan_interface(self): |
725 | 301 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 331 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
727 | 302 | vlan = factory.make_VLAN(vid=10) | 332 | vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
728 | 303 | form = VLANInterfaceForm( | 333 | form = VLANInterfaceForm( |
729 | 304 | node=parent.node, | 334 | node=parent.node, |
730 | 305 | data={ | 335 | data={ |
731 | @@ -317,7 +347,7 @@ | |||
732 | 317 | 347 | ||
733 | 318 | def test__create_ensures_link_up(self): | 348 | def test__create_ensures_link_up(self): |
734 | 319 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 349 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
736 | 320 | vlan = factory.make_VLAN(vid=10) | 350 | vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
737 | 321 | form = VLANInterfaceForm( | 351 | form = VLANInterfaceForm( |
738 | 322 | node=parent.node, | 352 | node=parent.node, |
739 | 323 | data={ | 353 | data={ |
740 | @@ -330,13 +360,10 @@ | |||
741 | 330 | interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY)) | 360 | interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY)) |
742 | 331 | 361 | ||
743 | 332 | def test_rejects_interface_with_duplicate_name(self): | 362 | def test_rejects_interface_with_duplicate_name(self): |
748 | 333 | vlan = factory.make_VLAN(vid=10) | 363 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
749 | 334 | parent = factory.make_Interface( | 364 | vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
746 | 335 | INTERFACE_TYPE.PHYSICAL, | ||
747 | 336 | vlan=vlan) | ||
750 | 337 | interface = factory.make_Interface( | 365 | interface = factory.make_Interface( |
753 | 338 | INTERFACE_TYPE.VLAN, | 366 | INTERFACE_TYPE.VLAN, vlan=vlan, parents=[parent]) |
752 | 339 | vlan=vlan, parents=[parent]) | ||
754 | 340 | form = VLANInterfaceForm( | 367 | form = VLANInterfaceForm( |
755 | 341 | node=parent.node, | 368 | node=parent.node, |
756 | 342 | data={ | 369 | data={ |
757 | @@ -350,6 +377,22 @@ | |||
758 | 350 | "already has an interface named '%s'." % interface.name, | 377 | "already has an interface named '%s'." % interface.name, |
759 | 351 | form.errors['name'][0]) | 378 | form.errors['name'][0]) |
760 | 352 | 379 | ||
761 | 380 | def test_rejects_interface_on_default_fabric(self): | ||
762 | 381 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
763 | 382 | vlan = parent.vlan.fabric.get_default_vlan() | ||
764 | 383 | form = VLANInterfaceForm( | ||
765 | 384 | node=parent.node, | ||
766 | 385 | data={ | ||
767 | 386 | 'vlan': vlan.id, | ||
768 | 387 | 'parents': [parent.id], | ||
769 | 388 | }) | ||
770 | 389 | self.assertFalse(form.is_valid(), form.errors) | ||
771 | 390 | self.assertItemsEqual( | ||
772 | 391 | ['vlan'], form.errors.keys(), form.errors) | ||
773 | 392 | self.assertIn( | ||
774 | 393 | "A VLAN interface can only belong to a tagged VLAN.", | ||
775 | 394 | form.errors['vlan'][0]) | ||
776 | 395 | |||
777 | 353 | def test__rejects_no_parents(self): | 396 | def test__rejects_no_parents(self): |
778 | 354 | vlan = factory.make_VLAN(vid=10) | 397 | vlan = factory.make_VLAN(vid=10) |
779 | 355 | form = VLANInterfaceForm( | 398 | form = VLANInterfaceForm( |
780 | @@ -365,13 +408,14 @@ | |||
781 | 365 | 408 | ||
782 | 366 | def test__rejects_vlan_parent(self): | 409 | def test__rejects_vlan_parent(self): |
783 | 367 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 410 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
784 | 411 | vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) | ||
785 | 368 | vlan_parent = factory.make_Interface( | 412 | vlan_parent = factory.make_Interface( |
788 | 369 | INTERFACE_TYPE.VLAN, parents=[parent]) | 413 | INTERFACE_TYPE.VLAN, vlan=vlan, parents=[parent]) |
789 | 370 | vlan = factory.make_VLAN(vid=10) | 414 | other_vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=11) |
790 | 371 | form = VLANInterfaceForm( | 415 | form = VLANInterfaceForm( |
791 | 372 | node=parent.node, | 416 | node=parent.node, |
792 | 373 | data={ | 417 | data={ |
794 | 374 | 'vlan': vlan.id, | 418 | 'vlan': other_vlan.id, |
795 | 375 | 'parents': [vlan_parent.id], | 419 | 'parents': [vlan_parent.id], |
796 | 376 | }) | 420 | }) |
797 | 377 | self.assertFalse(form.is_valid(), form.errors) | 421 | self.assertFalse(form.is_valid(), form.errors) |
798 | @@ -380,10 +424,27 @@ | |||
799 | 380 | "VLAN interface can't have another VLAN interface as parent.", | 424 | "VLAN interface can't have another VLAN interface as parent.", |
800 | 381 | form.errors['parents'][0]) | 425 | form.errors['parents'][0]) |
801 | 382 | 426 | ||
802 | 427 | def test__rejects_vlan_not_on_same_fabric(self): | ||
803 | 428 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
804 | 429 | factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) | ||
805 | 430 | other_vlan = factory.make_VLAN() | ||
806 | 431 | form = VLANInterfaceForm( | ||
807 | 432 | node=parent.node, | ||
808 | 433 | data={ | ||
809 | 434 | 'vlan': other_vlan.id, | ||
810 | 435 | 'parents': [parent.id], | ||
811 | 436 | }) | ||
812 | 437 | self.assertFalse(form.is_valid(), form.errors) | ||
813 | 438 | self.assertItemsEqual(['vlan'], form.errors.keys()) | ||
814 | 439 | self.assertIn( | ||
815 | 440 | "A VLAN interface can only belong to a tagged VLAN on " | ||
816 | 441 | "the same fabric as its parent interface.", | ||
817 | 442 | form.errors['vlan'][0]) | ||
818 | 443 | |||
819 | 383 | def test__rejects_parent_on_bond(self): | 444 | def test__rejects_parent_on_bond(self): |
820 | 384 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 445 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
823 | 385 | factory.make_Interface(INTERFACE_TYPE.BOND, parents=[parent]) | 446 | bond = factory.make_Interface(INTERFACE_TYPE.BOND, parents=[parent]) |
824 | 386 | vlan = factory.make_VLAN(vid=10) | 447 | vlan = factory.make_VLAN(fabric=bond.vlan.fabric, vid=10) |
825 | 387 | form = VLANInterfaceForm( | 448 | form = VLANInterfaceForm( |
826 | 388 | node=parent.node, | 449 | node=parent.node, |
827 | 389 | data={ | 450 | data={ |
828 | @@ -417,7 +478,7 @@ | |||
829 | 417 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 478 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
830 | 418 | interface = factory.make_Interface( | 479 | interface = factory.make_Interface( |
831 | 419 | INTERFACE_TYPE.VLAN, parents=[parent]) | 480 | INTERFACE_TYPE.VLAN, parents=[parent]) |
833 | 420 | new_vlan = factory.make_VLAN(vid=33) | 481 | new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric, vid=33) |
834 | 421 | form = VLANInterfaceForm( | 482 | form = VLANInterfaceForm( |
835 | 422 | instance=interface, | 483 | instance=interface, |
836 | 423 | data={ | 484 | data={ |
837 | @@ -438,15 +499,13 @@ | |||
838 | 438 | def test__error_with_invalid_bond_mode(self): | 499 | def test__error_with_invalid_bond_mode(self): |
839 | 439 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 500 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
840 | 440 | parent2 = factory.make_Interface( | 501 | parent2 = factory.make_Interface( |
842 | 441 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 502 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
843 | 442 | interface_name = factory.make_name() | 503 | interface_name = factory.make_name() |
844 | 443 | vlan = factory.make_VLAN(vid=10) | ||
845 | 444 | bond_mode = factory.make_name("bond_mode") | 504 | bond_mode = factory.make_name("bond_mode") |
846 | 445 | form = BondInterfaceForm( | 505 | form = BondInterfaceForm( |
847 | 446 | node=parent1.node, | 506 | node=parent1.node, |
848 | 447 | data={ | 507 | data={ |
849 | 448 | 'name': interface_name, | 508 | 'name': interface_name, |
850 | 449 | 'vlan': vlan.id, | ||
851 | 450 | 'parents': [parent1.id, parent2.id], | 509 | 'parents': [parent1.id, parent2.id], |
852 | 451 | 'bond_mode': bond_mode, | 510 | 'bond_mode': bond_mode, |
853 | 452 | }) | 511 | }) |
854 | @@ -460,14 +519,12 @@ | |||
855 | 460 | def test__creates_bond_interface(self): | 519 | def test__creates_bond_interface(self): |
856 | 461 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 520 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
857 | 462 | parent2 = factory.make_Interface( | 521 | parent2 = factory.make_Interface( |
859 | 463 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 522 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
860 | 464 | interface_name = factory.make_name() | 523 | interface_name = factory.make_name() |
861 | 465 | vlan = factory.make_VLAN(vid=10) | ||
862 | 466 | form = BondInterfaceForm( | 524 | form = BondInterfaceForm( |
863 | 467 | node=parent1.node, | 525 | node=parent1.node, |
864 | 468 | data={ | 526 | data={ |
865 | 469 | 'name': interface_name, | 527 | 'name': interface_name, |
866 | 470 | 'vlan': vlan.id, | ||
867 | 471 | 'parents': [parent1.id, parent2.id], | 528 | 'parents': [parent1.id, parent2.id], |
868 | 472 | }) | 529 | }) |
869 | 473 | self.assertTrue(form.is_valid(), form.errors) | 530 | self.assertTrue(form.is_valid(), form.errors) |
870 | @@ -484,15 +541,13 @@ | |||
871 | 484 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 541 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
872 | 485 | parent1.ensure_link_up() | 542 | parent1.ensure_link_up() |
873 | 486 | parent2 = factory.make_Interface( | 543 | parent2 = factory.make_Interface( |
875 | 487 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 544 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
876 | 488 | parent2.ensure_link_up() | 545 | parent2.ensure_link_up() |
877 | 489 | interface_name = factory.make_name() | 546 | interface_name = factory.make_name() |
878 | 490 | vlan = factory.make_VLAN(vid=10) | ||
879 | 491 | form = BondInterfaceForm( | 547 | form = BondInterfaceForm( |
880 | 492 | node=parent1.node, | 548 | node=parent1.node, |
881 | 493 | data={ | 549 | data={ |
882 | 494 | 'name': interface_name, | 550 | 'name': interface_name, |
883 | 495 | 'vlan': vlan.id, | ||
884 | 496 | 'parents': [parent1.id, parent2.id], | 551 | 'parents': [parent1.id, parent2.id], |
885 | 497 | }) | 552 | }) |
886 | 498 | self.assertTrue(form.is_valid(), form.errors) | 553 | self.assertTrue(form.is_valid(), form.errors) |
887 | @@ -511,14 +566,12 @@ | |||
888 | 511 | def test__creates_bond_interface_with_parent_mac_address(self): | 566 | def test__creates_bond_interface_with_parent_mac_address(self): |
889 | 512 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 567 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
890 | 513 | parent2 = factory.make_Interface( | 568 | parent2 = factory.make_Interface( |
892 | 514 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 569 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
893 | 515 | interface_name = factory.make_name() | 570 | interface_name = factory.make_name() |
894 | 516 | vlan = factory.make_VLAN(vid=10) | ||
895 | 517 | form = BondInterfaceForm( | 571 | form = BondInterfaceForm( |
896 | 518 | node=parent1.node, | 572 | node=parent1.node, |
897 | 519 | data={ | 573 | data={ |
898 | 520 | 'name': interface_name, | 574 | 'name': interface_name, |
899 | 521 | 'vlan': vlan.id, | ||
900 | 522 | 'parents': [parent1.id, parent2.id], | 575 | 'parents': [parent1.id, parent2.id], |
901 | 523 | 'mac_address': parent1.mac_address, | 576 | 'mac_address': parent1.mac_address, |
902 | 524 | }) | 577 | }) |
903 | @@ -534,14 +587,12 @@ | |||
904 | 534 | def test__creates_bond_interface_with_default_bond_params(self): | 587 | def test__creates_bond_interface_with_default_bond_params(self): |
905 | 535 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 588 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
906 | 536 | parent2 = factory.make_Interface( | 589 | parent2 = factory.make_Interface( |
908 | 537 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 590 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
909 | 538 | interface_name = factory.make_name() | 591 | interface_name = factory.make_name() |
910 | 539 | vlan = factory.make_VLAN(vid=10) | ||
911 | 540 | form = BondInterfaceForm( | 592 | form = BondInterfaceForm( |
912 | 541 | node=parent1.node, | 593 | node=parent1.node, |
913 | 542 | data={ | 594 | data={ |
914 | 543 | 'name': interface_name, | 595 | 'name': interface_name, |
915 | 544 | 'vlan': vlan.id, | ||
916 | 545 | 'parents': [parent1.id, parent2.id], | 596 | 'parents': [parent1.id, parent2.id], |
917 | 546 | }) | 597 | }) |
918 | 547 | self.assertTrue(form.is_valid(), form.errors) | 598 | self.assertTrue(form.is_valid(), form.errors) |
919 | @@ -558,9 +609,8 @@ | |||
920 | 558 | def test__creates_bond_interface_with_bond_params(self): | 609 | def test__creates_bond_interface_with_bond_params(self): |
921 | 559 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 610 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
922 | 560 | parent2 = factory.make_Interface( | 611 | parent2 = factory.make_Interface( |
924 | 561 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 612 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
925 | 562 | interface_name = factory.make_name() | 613 | interface_name = factory.make_name() |
926 | 563 | vlan = factory.make_VLAN(vid=10) | ||
927 | 564 | bond_mode = factory.pick_choice(BOND_MODE_CHOICES) | 614 | bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
928 | 565 | bond_miimon = random.randint(0, 1000) | 615 | bond_miimon = random.randint(0, 1000) |
929 | 566 | bond_downdelay = random.randint(0, 1000) | 616 | bond_downdelay = random.randint(0, 1000) |
930 | @@ -572,7 +622,6 @@ | |||
931 | 572 | node=parent1.node, | 622 | node=parent1.node, |
932 | 573 | data={ | 623 | data={ |
933 | 574 | 'name': interface_name, | 624 | 'name': interface_name, |
934 | 575 | 'vlan': vlan.id, | ||
935 | 576 | 'parents': [parent1.id, parent2.id], | 625 | 'parents': [parent1.id, parent2.id], |
936 | 577 | 'bond_mode': bond_mode, | 626 | 'bond_mode': bond_mode, |
937 | 578 | 'bond_miimon': bond_miimon, | 627 | 'bond_miimon': bond_miimon, |
938 | @@ -593,13 +642,11 @@ | |||
939 | 593 | }, interface.params) | 642 | }, interface.params) |
940 | 594 | 643 | ||
941 | 595 | def test__rejects_no_parents(self): | 644 | def test__rejects_no_parents(self): |
942 | 596 | vlan = factory.make_VLAN(vid=10) | ||
943 | 597 | interface_name = factory.make_name() | 645 | interface_name = factory.make_name() |
944 | 598 | form = BondInterfaceForm( | 646 | form = BondInterfaceForm( |
945 | 599 | node=factory.make_Node(), | 647 | node=factory.make_Node(), |
946 | 600 | data={ | 648 | data={ |
947 | 601 | 'name': interface_name, | 649 | 'name': interface_name, |
948 | 602 | 'vlan': vlan.id, | ||
949 | 603 | }) | 650 | }) |
950 | 604 | self.assertFalse(form.is_valid(), form.errors) | 651 | self.assertFalse(form.is_valid(), form.errors) |
951 | 605 | self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys()) | 652 | self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys()) |
952 | @@ -607,21 +654,37 @@ | |||
953 | 607 | "A Bond interface must have one or more parents.", | 654 | "A Bond interface must have one or more parents.", |
954 | 608 | form.errors['parents'][0]) | 655 | form.errors['parents'][0]) |
955 | 609 | 656 | ||
956 | 657 | def test__rejects_when_vlan_not_untagged(self): | ||
957 | 658 | interface_name = factory.make_name() | ||
958 | 659 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | ||
959 | 660 | vlan = factory.make_VLAN(fabric=parent.vlan.fabric) | ||
960 | 661 | form = BondInterfaceForm( | ||
961 | 662 | node=parent.node, | ||
962 | 663 | data={ | ||
963 | 664 | 'name': interface_name, | ||
964 | 665 | 'parents': [parent.id], | ||
965 | 666 | 'mac_address': parent.mac_address, | ||
966 | 667 | 'vlan': vlan.id, | ||
967 | 668 | }) | ||
968 | 669 | self.assertFalse(form.is_valid(), form.errors) | ||
969 | 670 | self.assertItemsEqual(['vlan'], form.errors.keys()) | ||
970 | 671 | self.assertIn( | ||
971 | 672 | "A bond interface can only belong to an untagged VLAN.", | ||
972 | 673 | form.errors['vlan'][0]) | ||
973 | 674 | |||
974 | 610 | def test__rejects_when_parents_already_have_children(self): | 675 | def test__rejects_when_parents_already_have_children(self): |
975 | 611 | node = factory.make_Node() | 676 | node = factory.make_Node() |
976 | 612 | parent1 = factory.make_Interface( | 677 | parent1 = factory.make_Interface( |
977 | 613 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth0") | 678 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth0") |
978 | 614 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent1]) | 679 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent1]) |
979 | 615 | parent2 = factory.make_Interface( | 680 | parent2 = factory.make_Interface( |
981 | 616 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth1") | 681 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth1", vlan=parent1.vlan) |
982 | 617 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent2]) | 682 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent2]) |
983 | 618 | vlan = factory.make_VLAN(vid=10) | ||
984 | 619 | interface_name = factory.make_name() | 683 | interface_name = factory.make_name() |
985 | 620 | form = BondInterfaceForm( | 684 | form = BondInterfaceForm( |
986 | 621 | node=node, | 685 | node=node, |
987 | 622 | data={ | 686 | data={ |
988 | 623 | 'name': interface_name, | 687 | 'name': interface_name, |
989 | 624 | 'vlan': vlan.id, | ||
990 | 625 | 'parents': [parent1.id, parent2.id] | 688 | 'parents': [parent1.id, parent2.id] |
991 | 626 | }) | 689 | }) |
992 | 627 | self.assertFalse(form.is_valid(), form.errors) | 690 | self.assertFalse(form.is_valid(), form.errors) |
993 | @@ -629,17 +692,36 @@ | |||
994 | 629 | "eth0, eth1 is already in-use by another interface.", | 692 | "eth0, eth1 is already in-use by another interface.", |
995 | 630 | form.errors['parents'][0]) | 693 | form.errors['parents'][0]) |
996 | 631 | 694 | ||
997 | 695 | def test__rejects_when_parents_not_in_same_vlan(self): | ||
998 | 696 | node = factory.make_Node() | ||
999 | 697 | parent1 = factory.make_Interface( | ||
1000 | 698 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth0") | ||
1001 | 699 | parent2 = factory.make_Interface( | ||
1002 | 700 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth1") | ||
1003 | 701 | interface_name = factory.make_name() | ||
1004 | 702 | form = BondInterfaceForm( | ||
1005 | 703 | node=node, | ||
1006 | 704 | data={ | ||
1007 | 705 | 'name': interface_name, | ||
1008 | 706 | 'parents': [parent1.id, parent2.id] | ||
1009 | 707 | }) | ||
1010 | 708 | self.assertFalse(form.is_valid(), form.errors) | ||
1011 | 709 | self.assertEquals( | ||
1012 | 710 | "All parents must belong to the same VLAN.", | ||
1013 | 711 | form.errors['parents'][0]) | ||
1014 | 712 | |||
1015 | 632 | def test__edits_interface(self): | 713 | def test__edits_interface(self): |
1016 | 633 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 714 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1017 | 634 | parent2 = factory.make_Interface( | 715 | parent2 = factory.make_Interface( |
1019 | 635 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 716 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1020 | 636 | interface = factory.make_Interface( | 717 | interface = factory.make_Interface( |
1021 | 637 | INTERFACE_TYPE.BOND, | 718 | INTERFACE_TYPE.BOND, |
1022 | 638 | parents=[parent1, parent2]) | 719 | parents=[parent1, parent2]) |
1024 | 639 | new_vlan = factory.make_VLAN(vid=33) | 720 | new_fabric = factory.make_Fabric() |
1025 | 721 | new_vlan = new_fabric.get_default_vlan() | ||
1026 | 640 | new_name = factory.make_name() | 722 | new_name = factory.make_name() |
1027 | 641 | new_parent = factory.make_Interface( | 723 | new_parent = factory.make_Interface( |
1029 | 642 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 724 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1030 | 643 | form = BondInterfaceForm( | 725 | form = BondInterfaceForm( |
1031 | 644 | instance=interface, | 726 | instance=interface, |
1032 | 645 | data={ | 727 | data={ |
1033 | @@ -656,6 +738,10 @@ | |||
1034 | 656 | vlan=new_vlan, type=INTERFACE_TYPE.BOND)) | 738 | vlan=new_vlan, type=INTERFACE_TYPE.BOND)) |
1035 | 657 | self.assertItemsEqual( | 739 | self.assertItemsEqual( |
1036 | 658 | [parent1, parent2, new_parent], interface.parents.all()) | 740 | [parent1, parent2, new_parent], interface.parents.all()) |
1037 | 741 | self.assertItemsEqual([new_vlan], set( | ||
1038 | 742 | reload_object(parent).vlan | ||
1039 | 743 | for parent in [parent1, parent2, new_parent] | ||
1040 | 744 | )) | ||
1041 | 659 | 745 | ||
1042 | 660 | def test__edits_interface_removes_parents(self): | 746 | def test__edits_interface_removes_parents(self): |
1043 | 661 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 747 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1044 | @@ -713,7 +799,7 @@ | |||
1045 | 713 | def test__edit_doesnt_overwrite_params(self): | 799 | def test__edit_doesnt_overwrite_params(self): |
1046 | 714 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 800 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1047 | 715 | parent2 = factory.make_Interface( | 801 | parent2 = factory.make_Interface( |
1049 | 716 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 802 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1050 | 717 | interface = factory.make_Interface( | 803 | interface = factory.make_Interface( |
1051 | 718 | INTERFACE_TYPE.BOND, | 804 | INTERFACE_TYPE.BOND, |
1052 | 719 | parents=[parent1, parent2]) | 805 | parents=[parent1, parent2]) |
1053 | @@ -733,12 +819,10 @@ | |||
1054 | 733 | "bond_xmit_hash_policy": bond_xmit_hash_policy, | 819 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1055 | 734 | } | 820 | } |
1056 | 735 | interface.save() | 821 | interface.save() |
1057 | 736 | new_vlan = factory.make_VLAN(vid=33) | ||
1058 | 737 | new_name = factory.make_name() | 822 | new_name = factory.make_name() |
1059 | 738 | form = BondInterfaceForm( | 823 | form = BondInterfaceForm( |
1060 | 739 | instance=interface, | 824 | instance=interface, |
1061 | 740 | data={ | 825 | data={ |
1062 | 741 | 'vlan': new_vlan.id, | ||
1063 | 742 | 'name': new_name, | 826 | 'name': new_name, |
1064 | 743 | }) | 827 | }) |
1065 | 744 | self.assertTrue(form.is_valid(), form.errors) | 828 | self.assertTrue(form.is_valid(), form.errors) |
1066 | @@ -755,7 +839,7 @@ | |||
1067 | 755 | def test__edit_does_overwrite_params(self): | 839 | def test__edit_does_overwrite_params(self): |
1068 | 756 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 840 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1069 | 757 | parent2 = factory.make_Interface( | 841 | parent2 = factory.make_Interface( |
1071 | 758 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 842 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1072 | 759 | interface = factory.make_Interface( | 843 | interface = factory.make_Interface( |
1073 | 760 | INTERFACE_TYPE.BOND, | 844 | INTERFACE_TYPE.BOND, |
1074 | 761 | parents=[parent1, parent2]) | 845 | parents=[parent1, parent2]) |
1075 | @@ -775,7 +859,6 @@ | |||
1076 | 775 | "bond_xmit_hash_policy": bond_xmit_hash_policy, | 859 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1077 | 776 | } | 860 | } |
1078 | 777 | interface.save() | 861 | interface.save() |
1079 | 778 | new_vlan = factory.make_VLAN(vid=33) | ||
1080 | 779 | new_name = factory.make_name() | 862 | new_name = factory.make_name() |
1081 | 780 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) | 863 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
1082 | 781 | new_bond_miimon = random.randint(0, 1000) | 864 | new_bond_miimon = random.randint(0, 1000) |
1083 | @@ -787,7 +870,6 @@ | |||
1084 | 787 | form = BondInterfaceForm( | 870 | form = BondInterfaceForm( |
1085 | 788 | instance=interface, | 871 | instance=interface, |
1086 | 789 | data={ | 872 | data={ |
1087 | 790 | 'vlan': new_vlan.id, | ||
1088 | 791 | 'name': new_name, | 873 | 'name': new_name, |
1089 | 792 | 'bond_mode': new_bond_mode, | 874 | 'bond_mode': new_bond_mode, |
1090 | 793 | 'bond_miimon': new_bond_miimon, | 875 | 'bond_miimon': new_bond_miimon, |
1091 | @@ -810,7 +892,7 @@ | |||
1092 | 810 | def test__edit_allows_zero_params(self): | 892 | def test__edit_allows_zero_params(self): |
1093 | 811 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) | 893 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1094 | 812 | parent2 = factory.make_Interface( | 894 | parent2 = factory.make_Interface( |
1096 | 813 | INTERFACE_TYPE.PHYSICAL, node=parent1.node) | 895 | INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1097 | 814 | interface = factory.make_Interface( | 896 | interface = factory.make_Interface( |
1098 | 815 | INTERFACE_TYPE.BOND, | 897 | INTERFACE_TYPE.BOND, |
1099 | 816 | parents=[parent1, parent2]) | 898 | parents=[parent1, parent2]) |
1100 | @@ -830,7 +912,6 @@ | |||
1101 | 830 | "bond_xmit_hash_policy": bond_xmit_hash_policy, | 912 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1102 | 831 | } | 913 | } |
1103 | 832 | interface.save() | 914 | interface.save() |
1104 | 833 | new_vlan = factory.make_VLAN(vid=33) | ||
1105 | 834 | new_name = factory.make_name() | 915 | new_name = factory.make_name() |
1106 | 835 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) | 916 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
1107 | 836 | new_bond_miimon = 0 | 917 | new_bond_miimon = 0 |
1108 | @@ -842,7 +923,6 @@ | |||
1109 | 842 | form = BondInterfaceForm( | 923 | form = BondInterfaceForm( |
1110 | 843 | instance=interface, | 924 | instance=interface, |
1111 | 844 | data={ | 925 | data={ |
1112 | 845 | 'vlan': new_vlan.id, | ||
1113 | 846 | 'name': new_name, | 926 | 'name': new_name, |
1114 | 847 | 'bond_mode': new_bond_mode, | 927 | 'bond_mode': new_bond_mode, |
1115 | 848 | 'bond_miimon': new_bond_miimon, | 928 | 'bond_miimon': new_bond_miimon, |
1116 | 849 | 929 | ||
1117 | === modified file 'src/maasserver/websockets/handlers/tests/test_node.py' | |||
1118 | --- src/maasserver/websockets/handlers/tests/test_node.py 2015-11-18 18:14:30 +0000 | |||
1119 | +++ src/maasserver/websockets/handlers/tests/test_node.py 2016-04-05 16:02:24 +0000 | |||
1120 | @@ -1912,7 +1912,8 @@ | |||
1121 | 1912 | handler = NodeHandler(user, {}) | 1912 | handler = NodeHandler(user, {}) |
1122 | 1913 | name = factory.make_name("eth") | 1913 | name = factory.make_name("eth") |
1123 | 1914 | mac_address = factory.make_mac_address() | 1914 | mac_address = factory.make_mac_address() |
1125 | 1915 | vlan = factory.make_VLAN() | 1915 | fabric = factory.make_Fabric() |
1126 | 1916 | vlan = fabric.get_default_vlan() | ||
1127 | 1916 | handler.create_physical({ | 1917 | handler.create_physical({ |
1128 | 1917 | "system_id": node.system_id, | 1918 | "system_id": node.system_id, |
1129 | 1918 | "name": name, | 1919 | "name": name, |
1130 | @@ -1929,7 +1930,8 @@ | |||
1131 | 1929 | handler = NodeHandler(user, {}) | 1930 | handler = NodeHandler(user, {}) |
1132 | 1930 | name = factory.make_name("eth") | 1931 | name = factory.make_name("eth") |
1133 | 1931 | mac_address = factory.make_mac_address() | 1932 | mac_address = factory.make_mac_address() |
1135 | 1932 | vlan = factory.make_VLAN() | 1933 | fabric = factory.make_Fabric() |
1136 | 1934 | vlan = fabric.get_default_vlan() | ||
1137 | 1933 | subnet = factory.make_Subnet(vlan=vlan) | 1935 | subnet = factory.make_Subnet(vlan=vlan) |
1138 | 1934 | handler.create_physical({ | 1936 | handler.create_physical({ |
1139 | 1935 | "system_id": node.system_id, | 1937 | "system_id": node.system_id, |
1140 | @@ -1951,7 +1953,8 @@ | |||
1141 | 1951 | handler = NodeHandler(user, {}) | 1953 | handler = NodeHandler(user, {}) |
1142 | 1952 | name = factory.make_name("eth") | 1954 | name = factory.make_name("eth") |
1143 | 1953 | mac_address = factory.make_mac_address() | 1955 | mac_address = factory.make_mac_address() |
1145 | 1954 | vlan = factory.make_VLAN() | 1956 | fabric = factory.make_Fabric() |
1146 | 1957 | vlan = fabric.get_default_vlan() | ||
1147 | 1955 | handler.create_physical({ | 1958 | handler.create_physical({ |
1148 | 1956 | "system_id": node.system_id, | 1959 | "system_id": node.system_id, |
1149 | 1957 | "name": name, | 1960 | "name": name, |
1150 | @@ -1971,7 +1974,8 @@ | |||
1151 | 1971 | handler = NodeHandler(user, {}) | 1974 | handler = NodeHandler(user, {}) |
1152 | 1972 | name = factory.make_name("eth") | 1975 | name = factory.make_name("eth") |
1153 | 1973 | mac_address = factory.make_mac_address() | 1976 | mac_address = factory.make_mac_address() |
1155 | 1974 | vlan = factory.make_VLAN() | 1977 | fabric = factory.make_Fabric() |
1156 | 1978 | vlan = fabric.get_default_vlan() | ||
1157 | 1975 | subnet = factory.make_Subnet(vlan=vlan) | 1979 | subnet = factory.make_Subnet(vlan=vlan) |
1158 | 1976 | handler.create_physical({ | 1980 | handler.create_physical({ |
1159 | 1977 | "system_id": node.system_id, | 1981 | "system_id": node.system_id, |
1160 | @@ -1992,7 +1996,7 @@ | |||
1161 | 1992 | node = factory.make_Node() | 1996 | node = factory.make_Node() |
1162 | 1993 | handler = NodeHandler(user, {}) | 1997 | handler = NodeHandler(user, {}) |
1163 | 1994 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | 1998 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1165 | 1995 | new_vlan = factory.make_VLAN() | 1999 | new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1166 | 1996 | handler.create_vlan({ | 2000 | handler.create_vlan({ |
1167 | 1997 | "system_id": node.system_id, | 2001 | "system_id": node.system_id, |
1168 | 1998 | "parent": interface.id, | 2002 | "parent": interface.id, |
1169 | @@ -2008,7 +2012,7 @@ | |||
1170 | 2008 | node = factory.make_Node() | 2012 | node = factory.make_Node() |
1171 | 2009 | handler = NodeHandler(user, {}) | 2013 | handler = NodeHandler(user, {}) |
1172 | 2010 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | 2014 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1174 | 2011 | new_vlan = factory.make_VLAN() | 2015 | new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1175 | 2012 | new_subnet = factory.make_Subnet(vlan=new_vlan) | 2016 | new_subnet = factory.make_Subnet(vlan=new_vlan) |
1176 | 2013 | handler.create_vlan({ | 2017 | handler.create_vlan({ |
1177 | 2014 | "system_id": node.system_id, | 2018 | "system_id": node.system_id, |
1178 | @@ -2030,7 +2034,7 @@ | |||
1179 | 2030 | node = factory.make_Node() | 2034 | node = factory.make_Node() |
1180 | 2031 | handler = NodeHandler(user, {}) | 2035 | handler = NodeHandler(user, {}) |
1181 | 2032 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | 2036 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1183 | 2033 | new_vlan = factory.make_VLAN() | 2037 | new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1184 | 2034 | handler.create_vlan({ | 2038 | handler.create_vlan({ |
1185 | 2035 | "system_id": node.system_id, | 2039 | "system_id": node.system_id, |
1186 | 2036 | "parent": interface.id, | 2040 | "parent": interface.id, |
1187 | @@ -2050,7 +2054,7 @@ | |||
1188 | 2050 | node = factory.make_Node() | 2054 | node = factory.make_Node() |
1189 | 2051 | handler = NodeHandler(user, {}) | 2055 | handler = NodeHandler(user, {}) |
1190 | 2052 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | 2056 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1192 | 2053 | new_vlan = factory.make_VLAN() | 2057 | new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1193 | 2054 | new_subnet = factory.make_Subnet(vlan=new_vlan) | 2058 | new_subnet = factory.make_Subnet(vlan=new_vlan) |
1194 | 2055 | handler.create_vlan({ | 2059 | handler.create_vlan({ |
1195 | 2056 | "system_id": node.system_id, | 2060 | "system_id": node.system_id, |
1196 | @@ -2110,7 +2114,8 @@ | |||
1197 | 2110 | handler = NodeHandler(user, {}) | 2114 | handler = NodeHandler(user, {}) |
1198 | 2111 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | 2115 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1199 | 2112 | new_name = factory.make_name("name") | 2116 | new_name = factory.make_name("name") |
1201 | 2113 | new_vlan = factory.make_VLAN() | 2117 | new_fabric = factory.make_Fabric() |
1202 | 2118 | new_vlan = new_fabric.get_default_vlan() | ||
1203 | 2114 | handler.update_interface({ | 2119 | handler.update_interface({ |
1204 | 2115 | "system_id": node.system_id, | 2120 | "system_id": node.system_id, |
1205 | 2116 | "interface_id": interface.id, | 2121 | "interface_id": interface.id, |
Self-approving backport.