Merge ~ltrager/maas:network_testing_models into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: e8075505cb458a525fe871f4ff7e5f93780f1b15
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:network_testing_models
Merge into: maas:master
Diff against target: 487 lines (+172/-26)
12 files modified
src/maasserver/forms/tests/test_pods.py (+2/-2)
src/maasserver/migrations/maasserver/0188_network_testing.py (+33/-0)
src/maasserver/models/interface.py (+19/-1)
src/maasserver/models/tests/test_interface.py (+27/-6)
src/maasserver/testing/factory.py (+17/-4)
src/maasserver/tests/test_routablepairs.py (+9/-9)
src/maasserver/websockets/handlers/tests/test_script.py (+2/-1)
src/metadataserver/enum.py (+3/-1)
src/metadataserver/migrations/0020_network_testing.py (+35/-0)
src/metadataserver/models/script.py (+7/-1)
src/metadataserver/models/scriptresult.py (+5/-0)
src/metadataserver/models/tests/test_script.py (+13/-1)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Newell Jensen (community) Approve
Review via email: mp+368567@code.launchpad.net

Commit message

Add database columns for network testing

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

Follows the spec. Looks good.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good to me.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
~ltrager/maas:network_testing_models updated
e807550... by Lee Trager

Fix failing test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py
2index 948d4ce..03d332a 100644
3--- a/src/maasserver/forms/tests/test_pods.py
4+++ b/src/maasserver/forms/tests/test_pods.py
5@@ -1869,9 +1869,9 @@ class TestGetKnownHostInterfaces(MAASServerTestCase):
6 node = factory.make_Machine_with_Interface_on_Subnet()
7 node.interface_set.all().delete()
8 bridge = factory.make_Interface(
9- iftype=INTERFACE_TYPE.BRIDGE, node=node, disconnected=True)
10+ iftype=INTERFACE_TYPE.BRIDGE, node=node, link_connected=False)
11 physical = factory.make_Interface(
12- iftype=INTERFACE_TYPE.PHYSICAL, node=node, disconnected=True)
13+ iftype=INTERFACE_TYPE.PHYSICAL, node=node, link_connected=False)
14 interfaces = get_known_host_interfaces(node)
15 self.assertItemsEqual(
16 interfaces, [
17diff --git a/src/maasserver/migrations/maasserver/0188_network_testing.py b/src/maasserver/migrations/maasserver/0188_network_testing.py
18new file mode 100644
19index 0000000..bb9e267
20--- /dev/null
21+++ b/src/maasserver/migrations/maasserver/0188_network_testing.py
22@@ -0,0 +1,33 @@
23+# -*- coding: utf-8 -*-
24+# Generated by Django 1.11.11 on 2019-06-07 19:40
25+from __future__ import unicode_literals
26+
27+from django.db import (
28+ migrations,
29+ models,
30+)
31+
32+
33+class Migration(migrations.Migration):
34+
35+ dependencies = [
36+ ('maasserver', '0187_status_messages_change_event_logging_levels'),
37+ ]
38+
39+ operations = [
40+ migrations.AddField(
41+ model_name='interface',
42+ name='interface_speed',
43+ field=models.PositiveIntegerField(default=0),
44+ ),
45+ migrations.AddField(
46+ model_name='interface',
47+ name='link_connected',
48+ field=models.BooleanField(default=True),
49+ ),
50+ migrations.AddField(
51+ model_name='interface',
52+ name='link_speed',
53+ field=models.PositiveIntegerField(default=0),
54+ ),
55+ ]
56diff --git a/src/maasserver/models/interface.py b/src/maasserver/models/interface.py
57index 842e0e8..6b5932e 100644
58--- a/src/maasserver/models/interface.py
59+++ b/src/maasserver/models/interface.py
60@@ -1,4 +1,4 @@
61-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
62+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
63 # GNU Affero General Public License version 3 (see the file LICENSE).
64
65 """Model for interfaces."""
66@@ -28,6 +28,7 @@ from django.db.models import (
67 ForeignKey,
68 Manager,
69 ManyToManyField,
70+ PositiveIntegerField,
71 PROTECT,
72 Q,
73 TextField,
74@@ -571,6 +572,15 @@ class Interface(CleanSave, TimestampedModel):
75 max_length=255, blank=True, null=True,
76 help_text="Firmware version of the interface.")
77
78+ # Whether or not the Interface is physically connected to an uplink.
79+ link_connected = BooleanField(default=True)
80+
81+ # The speed of the interface in Mbit/s
82+ interface_speed = PositiveIntegerField(default=0)
83+
84+ # The speed of the link in Mbit/s
85+ link_speed = PositiveIntegerField(default=0)
86+
87 def __init__(self, *args, **kwargs):
88 type = kwargs.get('type', self.get_type())
89 kwargs['type'] = type
90@@ -1475,6 +1485,14 @@ class Interface(CleanSave, TimestampedModel):
91 'mdns': self.mdns_discovery_state,
92 }
93
94+ def save(self, *args, **kwargs):
95+ if (self.link_speed > self.interface_speed and
96+ self.interface_speed != 0):
97+ raise ValidationError('link_speed may not exceed interface_speed')
98+ if not self.link_connected:
99+ self.link_speed = 0
100+ return super().save(*args, **kwargs)
101+
102
103 class InterfaceRelationship(CleanSave, TimestampedModel):
104 child = ForeignKey(
105diff --git a/src/maasserver/models/tests/test_interface.py b/src/maasserver/models/tests/test_interface.py
106index 36cd455..934705d 100644
107--- a/src/maasserver/models/tests/test_interface.py
108+++ b/src/maasserver/models/tests/test_interface.py
109@@ -1,4 +1,4 @@
110-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
111+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
112 # GNU Affero General Public License version 3 (see the file LICENSE).
113
114 """Tests for the Interface model."""
115@@ -873,7 +873,7 @@ class InterfaceTest(MAASServerTestCase):
116 mac = factory.make_MAC()
117 interface = factory.make_Interface(
118 INTERFACE_TYPE.PHYSICAL,
119- name=name, node=node, mac_address=mac, disconnected=True)
120+ name=name, node=node, mac_address=mac, link_connected=False)
121 self.assertThat(interface, MatchesStructure.byEquality(
122 name=name, node=node, mac_address=mac,
123 type=INTERFACE_TYPE.PHYSICAL, vlan=None))
124@@ -884,7 +884,7 @@ class InterfaceTest(MAASServerTestCase):
125 mac = factory.make_MAC()
126 interface = factory.make_Interface(
127 INTERFACE_TYPE.PHYSICAL,
128- name=name, node=node, mac_address=mac, disconnected=True)
129+ name=name, node=node, mac_address=mac, link_connected=False)
130 interface.acquired = True
131 self.assertRaises(ValueError, interface.save)
132
133@@ -1103,6 +1103,27 @@ class InterfaceTest(MAASServerTestCase):
134 #: Test is this doesn't raise an exception
135 interface.remove_tag(tag)
136
137+ def test_save_link_speed_may_not_exceed_interface_speed(self):
138+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
139+ interface.interface_speed = 100
140+ interface.link_speed = 1000
141+ self.assertRaises(ValidationError, interface.save)
142+
143+ def test_save_link_speed_may_exceed_unknown_interface_speed(self):
144+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
145+ interface.interface_speed = 0
146+ interface.link_speed = 1000
147+ interface.save()
148+ interface = reload_object(interface)
149+ self.assertEquals(0, interface.interface_speed)
150+ self.assertEquals(1000, interface.link_speed)
151+
152+ def test_save_if_link_disconnected_set_link_speed_to_zero(self):
153+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
154+ interface.link_connected = False
155+ interface.save()
156+ self.assertEquals(0, interface.link_speed)
157+
158
159 class InterfaceUpdateNeighbourTest(MAASServerTestCase):
160 """Tests for `Interface.update_neighbour`."""
161@@ -1378,7 +1399,7 @@ class InterfaceMTUTest(MAASServerTestCase):
162
163 def test_get_effective_mtu_returns_default_mtu(self):
164 nic1 = factory.make_Interface(
165- INTERFACE_TYPE.PHYSICAL, disconnected=True)
166+ INTERFACE_TYPE.PHYSICAL, link_connected=False)
167 self.assertEqual(DEFAULT_MTU, nic1.get_effective_mtu())
168
169 def test_get_effective_mtu_returns_interface_mtu(self):
170@@ -2380,7 +2401,7 @@ class TestForceAutoOrDHCPLink(MAASServerTestCase):
171
172 def test__does_nothing_when_disconnected(self):
173 interface = factory.make_Interface(
174- INTERFACE_TYPE.PHYSICAL, disconnected=True)
175+ INTERFACE_TYPE.PHYSICAL, link_connected=False)
176 self.assertIsNone(interface.force_auto_or_dhcp_link())
177
178 def test__sets_to_AUTO_on_subnet(self):
179@@ -2412,7 +2433,7 @@ class TestEnsureLinkUp(MAASServerTestCase):
180
181 def test__does_nothing_if_no_vlan(self):
182 interface = factory.make_Interface(
183- INTERFACE_TYPE.PHYSICAL, disconnected=True)
184+ INTERFACE_TYPE.PHYSICAL, link_connected=False)
185 interface.ensure_link_up()
186 interface = reload_object(interface)
187 self.assertEqual(
188diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py
189index ec327a4..7ce4b7a 100644
190--- a/src/maasserver/testing/factory.py
191+++ b/src/maasserver/testing/factory.py
192@@ -1,4 +1,4 @@
193-# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
194+# Copyright 2012-2019 Canonical Ltd. This software is licensed under the
195 # GNU Affero General Public License version 3 (see the file LICENSE).
196
197 """Test object factories."""
198@@ -1323,7 +1323,8 @@ class Factory(maastesting.factory.Factory):
199 self, iftype=INTERFACE_TYPE.PHYSICAL, node=None, mac_address=None,
200 vlan=None, parents=None, name=None, cluster_interface=None,
201 ip=None, subnet=None, enabled=True, fabric=None, tags=None,
202- disconnected=False, params=""):
203+ link_connected=True, interface_speed=None, link_speed=None,
204+ params=""):
205 if subnet is None and cluster_interface is not None:
206 subnet = cluster_interface.subnet
207 if subnet is not None and vlan is None:
208@@ -1344,7 +1345,7 @@ class Factory(maastesting.factory.Factory):
209 elif iftype == INTERFACE_TYPE.VLAN:
210 # Need to calculate this later based on the VID.
211 name = None
212- if not disconnected:
213+ if link_connected:
214 if vlan is None:
215 if fabric is not None:
216 if iftype == INTERFACE_TYPE.VLAN:
217@@ -1375,9 +1376,21 @@ class Factory(maastesting.factory.Factory):
218 node = parents[0].get_node()
219 if tags is None:
220 tags = [self.make_name('tag') for _ in range(3)]
221+ link_speeds = [10, 100, 1000, 10000, 20000, 40000, 50000, 100000]
222+ if interface_speed is None:
223+ interface_speed = random.choice(link_speeds)
224+ if link_speed is None:
225+ if not link_connected:
226+ link_speed = 0
227+ else:
228+ link_speed = random.choice([
229+ speed for speed in link_speeds
230+ if speed <= interface_speed])
231 interface = Interface(
232 node=node, mac_address=mac_address, type=iftype,
233- name=name, vlan=vlan, enabled=enabled, tags=tags, params=params)
234+ name=name, vlan=vlan, enabled=enabled, tags=tags,
235+ link_connected=link_connected, interface_speed=interface_speed,
236+ link_speed=link_speed, params=params)
237 interface.save()
238 if subnet is None and ip is not None:
239 subnet = Subnet.objects.get_best_subnet_for_ip(ip)
240diff --git a/src/maasserver/tests/test_routablepairs.py b/src/maasserver/tests/test_routablepairs.py
241index 7ba6fe8..45ca34c 100644
242--- a/src/maasserver/tests/test_routablepairs.py
243+++ b/src/maasserver/tests/test_routablepairs.py
244@@ -1,4 +1,4 @@
245-# Copyright 2016 Canonical Ltd. This software is licensed under the
246+# Copyright 2016-2019 Canonical Ltd. This software is licensed under the
247 # GNU Affero General Public License version 3 (see the file LICENSE).
248
249 """Tests for `maasserver.routablepairs`."""
250@@ -136,7 +136,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
251 # null space, one in a non-null space.
252 origin = factory.make_Node(hostname="origin")
253 origin_iface = factory.make_Interface(
254- node=origin, disconnected=True)
255+ node=origin, link_connected=False)
256 origin_subnet = factory.make_Subnet(
257 space=space, cidr=next(networks))
258 origin_subnet_null_space = factory.make_Subnet(
259@@ -150,14 +150,14 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
260 node_same_subnet = factory.make_Node(hostname="same-subnet")
261 sip_same_subnet = factory.make_StaticIPAddress(
262 interface=factory.make_Interface(
263- node=node_same_subnet, disconnected=True),
264+ node=node_same_subnet, link_connected=False),
265 subnet=origin_subnet)
266
267 # Same VLAN, different subnet, different node.
268 node_same_vlan = factory.make_Node(hostname="same-vlan")
269 sip_same_vlan = factory.make_StaticIPAddress(
270 interface=factory.make_Interface(
271- node=node_same_vlan, disconnected=True),
272+ node=node_same_vlan, link_connected=False),
273 subnet=factory.make_Subnet(
274 space=space, vlan=origin_subnet.vlan,
275 cidr=next(networks)))
276@@ -166,7 +166,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
277 node_same_space = factory.make_Node(hostname="same-space")
278 sip_same_space = factory.make_StaticIPAddress(
279 interface=factory.make_Interface(
280- node=node_same_space, disconnected=True),
281+ node=node_same_space, link_connected=False),
282 subnet=factory.make_Subnet(
283 space=space, cidr=next(networks)))
284
285@@ -174,7 +174,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
286 node_null_space = factory.make_Node(hostname="null-space")
287 factory.make_StaticIPAddress(
288 interface=factory.make_Interface(
289- node=node_null_space, disconnected=True),
290+ node=node_null_space, link_connected=False),
291 subnet=factory.make_Subnet(
292 space=None, cidr=next(networks)))
293
294@@ -208,7 +208,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
295 # would have obscured the test.
296 origin_sip_2 = factory.make_StaticIPAddress(
297 interface=factory.make_Interface(
298- node=origin, disconnected=True),
299+ node=origin, link_connected=False),
300 subnet=factory.make_Subnet(
301 space=space, cidr=next(networks)))
302
303@@ -239,7 +239,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
304 # null space, one in a non-null space.
305 origin = factory.make_Node(hostname="origin")
306 origin_iface = factory.make_Interface(
307- node=origin, disconnected=True)
308+ node=origin, link_connected=False)
309 origin_subnet_null_space = factory.make_Subnet(
310 space=None, cidr=network1)
311 factory.make_StaticIPAddress(
312@@ -248,7 +248,7 @@ class TestFindAddressesBetweenNodes(MAASServerTestCase):
313 # Same subnet, different node.
314 node_no_match = factory.make_Node(hostname="no-match")
315 no_match_iface = factory.make_Interface(
316- node=node_no_match, disconnected=True)
317+ node=node_no_match, link_connected=False)
318 no_match_subnet_null_space = factory.make_Subnet(
319 space=None, cidr=network2)
320 factory.make_StaticIPAddress(
321diff --git a/src/maasserver/websockets/handlers/tests/test_script.py b/src/maasserver/websockets/handlers/tests/test_script.py
322index 7d498d2..1108d6d 100644
323--- a/src/maasserver/websockets/handlers/tests/test_script.py
324+++ b/src/maasserver/websockets/handlers/tests/test_script.py
325@@ -1,4 +1,4 @@
326-# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
327+# Copyright 2017-2019 Canonical Ltd. This software is licensed under the
328 # GNU Affero General Public License version 3 (see the file LICENSE).
329
330 """Tests for `maasserver.websockets.handlers.script`"""
331@@ -35,6 +35,7 @@ class TestScriptHandler(MAASServerTestCase):
332 'for_hardware': script.for_hardware,
333 'may_reboot': script.may_reboot,
334 'recommission': script.recommission,
335+ 'apply_configured_networking': script.apply_configured_networking,
336 'created': dehydrate_datetime(script.created),
337 'updated': dehydrate_datetime(script.updated),
338 }
339diff --git a/src/metadataserver/enum.py b/src/metadataserver/enum.py
340index 25222bc..983a5b3 100644
341--- a/src/metadataserver/enum.py
342+++ b/src/metadataserver/enum.py
343@@ -1,4 +1,4 @@
344-# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
345+# Copyright 2012-2019 Canonical Ltd. This software is licensed under the
346 # GNU Affero General Public License version 3 (see the file LICENSE).
347
348 """Enumerations meaningful to the metadataserver application."""
349@@ -101,6 +101,7 @@ class HARDWARE_TYPE:
350 CPU = 1
351 MEMORY = 2
352 STORAGE = 3
353+ NETWORK = 4
354
355
356 # Labels are also used for autotagging scripts.
357@@ -109,6 +110,7 @@ HARDWARE_TYPE_CHOICES = (
358 (HARDWARE_TYPE.CPU, "CPU"),
359 (HARDWARE_TYPE.MEMORY, "Memory"),
360 (HARDWARE_TYPE.STORAGE, "Storage"),
361+ (HARDWARE_TYPE.NETWORK, "Network"),
362 )
363
364
365diff --git a/src/metadataserver/migrations/0020_network_testing.py b/src/metadataserver/migrations/0020_network_testing.py
366new file mode 100644
367index 0000000..2ea2ecb
368--- /dev/null
369+++ b/src/metadataserver/migrations/0020_network_testing.py
370@@ -0,0 +1,35 @@
371+# -*- coding: utf-8 -*-
372+# Generated by Django 1.11.11 on 2019-06-07 19:40
373+from __future__ import unicode_literals
374+
375+from django.db import (
376+ migrations,
377+ models,
378+)
379+import django.db.models.deletion
380+
381+
382+class Migration(migrations.Migration):
383+
384+ dependencies = [
385+ ('maasserver', '0188_network_testing'),
386+ ('metadataserver', '0019_add_script_result_suppressed'),
387+ ]
388+
389+ operations = [
390+ migrations.AddField(
391+ model_name='script',
392+ name='apply_configured_networking',
393+ field=models.BooleanField(default=False),
394+ ),
395+ migrations.AddField(
396+ model_name='scriptresult',
397+ name='interface',
398+ field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='maasserver.Interface'),
399+ ),
400+ migrations.AlterField(
401+ model_name='script',
402+ name='hardware_type',
403+ field=models.IntegerField(choices=[(0, 'Node'), (1, 'CPU'), (2, 'Memory'), (3, 'Storage'), (4, 'Network')], default=0),
404+ ),
405+ ]
406diff --git a/src/metadataserver/models/script.py b/src/metadataserver/models/script.py
407index ffa4e69..3bfda14 100644
408--- a/src/metadataserver/models/script.py
409+++ b/src/metadataserver/models/script.py
410@@ -1,4 +1,4 @@
411-# Copyright 2017 Canonical Ltd. This software is licensed under the
412+# Copyright 2017-2019 Canonical Ltd. This software is licensed under the
413 # GNU Affero General Public License version 3 (see the file LICENSE).
414
415 __all__ = [
416@@ -75,6 +75,8 @@ def translate_hardware_type(hardware_type):
417 return HARDWARE_TYPE.MEMORY
418 elif hardware_type in ['storage', 'disk', 'ssd']:
419 return HARDWARE_TYPE.STORAGE
420+ elif hardware_type in ['network', 'net', 'interface']:
421+ return HARDWARE_TYPE.NETWORK
422 else:
423 raise ValidationError(
424 'Hardware type must be node, cpu, memory, or storage')
425@@ -182,6 +184,10 @@ class Script(CleanSave, TimestampedModel):
426 # scripts after receiving the result.
427 recommission = BooleanField(default=False)
428
429+ # Whether or not maas-run-remote-scripts should apply user configured
430+ # network settings before running the Script.
431+ apply_configured_networking = BooleanField(default=False)
432+
433 @property
434 def ForHardware(self):
435 """Parses the for_hardware field and returns a ForHardware tuple."""
436diff --git a/src/metadataserver/models/scriptresult.py b/src/metadataserver/models/scriptresult.py
437index e3b80ff..5ec3c98 100644
438--- a/src/metadataserver/models/scriptresult.py
439+++ b/src/metadataserver/models/scriptresult.py
440@@ -24,6 +24,7 @@ from django.db.models import (
441 from maasserver.fields import JSONObjectField
442 from maasserver.models.cleansave import CleanSave
443 from maasserver.models.event import Event
444+from maasserver.models.interface import Interface
445 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
446 from maasserver.models.timestampedmodel import (
447 now,
448@@ -73,6 +74,10 @@ class ScriptResult(CleanSave, TimestampedModel):
449 PhysicalBlockDevice, editable=False, blank=True, null=True,
450 on_delete=CASCADE)
451
452+ # If the result is in reference to a particular Interface link it.
453+ interface = ForeignKey(
454+ Interface, editable=False, blank=True, null=True, on_delete=CASCADE)
455+
456 script_version = ForeignKey(
457 VersionedTextFile, blank=True, null=True, editable=False,
458 on_delete=SET_NULL)
459diff --git a/src/metadataserver/models/tests/test_script.py b/src/metadataserver/models/tests/test_script.py
460index ebf12d9..9c10e16 100644
461--- a/src/metadataserver/models/tests/test_script.py
462+++ b/src/metadataserver/models/tests/test_script.py
463@@ -1,4 +1,4 @@
464-# Copyright 2017 Canonical Ltd. This software is licensed under the
465+# Copyright 2017-2019 Canonical Ltd. This software is licensed under the
466 # GNU Affero General Public License version 3 (see the file LICENSE).
467
468 __all__ = []
469@@ -162,6 +162,18 @@ class TestTranslateHardwareType(MAASServerTestCase):
470 'value': 'ssd',
471 'return_value': HARDWARE_TYPE.STORAGE,
472 }),
473+ ('network', {
474+ 'value': 'network',
475+ 'return_value': HARDWARE_TYPE.NETWORK,
476+ }),
477+ ('net', {
478+ 'value': 'net',
479+ 'return_value': HARDWARE_TYPE.NETWORK,
480+ }),
481+ ('interface', {
482+ 'value': 'interface',
483+ 'return_value': HARDWARE_TYPE.NETWORK,
484+ }),
485 ('invalid value', {
486 'value': factory.make_name('value'),
487 'exception': 'Hardware type must be node, cpu, memory, or storage',

Subscribers

People subscribed via source and target branches