Merge lp:~julian-edwards/maas/backport-network-mac-linkage into lp:maas/1.6

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2547
Proposed branch: lp:~julian-edwards/maas/backport-network-mac-linkage
Merge into: lp:maas/1.6
Diff against target: 431 lines (+249/-19)
6 files modified
src/maasserver/api.py (+19/-3)
src/maasserver/forms.py (+38/-0)
src/maasserver/models/network.py (+16/-0)
src/maasserver/models/tests/test_network.py (+39/-0)
src/maasserver/tests/test_api_nodegroup.py (+50/-13)
src/maasserver/tests/test_forms.py (+87/-3)
To merge this branch: bzr merge lp:~julian-edwards/maas/backport-network-mac-linkage
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+231484@code.launchpad.net

Commit message

Backport changes from trunk that fix bug 1341619, which creates Networks from cluster interfaces and auto-links MAC addresses from nodes to them.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Selfing straight backport

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.5 KiB)

The attempt to merge lp:~julian-edwards/maas/backport-network-mac-linkage into lp:maas/1.6 failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-netaddr python-netifaces python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-seamicroclient python-simplestreams python-sphinx python-tempita python-twisted python-txamqp python-txlongpoll python-txtftp python-virtualenv python-yaml rab...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.5 KiB)

The attempt to merge lp:~julian-edwards/maas/backport-network-mac-linkage into lp:maas/1.6 failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-netaddr python-netifaces python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-seamicroclient python-simplestreams python-sphinx python-tempita python-twisted python-txamqp python-txlongpoll python-txtftp python-virtualenv python-yaml rab...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.5 KiB)

The attempt to merge lp:~julian-edwards/maas/backport-network-mac-linkage into lp:maas/1.6 failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://security.ubuntu.com trusty-security Release
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-netaddr python-netifaces python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-seamicroclient python-simplestreams python-sphinx python-tempita python-twisted python-txamqp python-txlongpoll python-txtftp python-virtualenv python-yaml rab...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

FML

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2014-08-08 20:09:49 +0000
3+++ src/maasserver/api.py 2014-08-20 02:35:32 +0000
4@@ -1589,17 +1589,33 @@
5 for interface in interfaces:
6 ip_range = netaddr.IPRange(
7 interface.ip_range_low, interface.ip_range_high)
8- interface_ranges[interface] = ip_range
9+ if interface.static_ip_range_low and interface.static_ip_range_high:
10+ static_range = netaddr.IPRange(
11+ interface.static_ip_range_low, interface.static_ip_range_high)
12+ else:
13+ static_range = []
14+ interface_ranges[interface] = (ip_range, static_range)
15 for ip, mac in leases.items():
16 try:
17 mac_address = MACAddress.objects.get(mac_address=mac)
18 except MACAddress.DoesNotExist:
19 # Silently ignore MAC addresses that we don't know about.
20 continue
21- for interface, ip_range in interface_ranges.items():
22- if netaddr.IPAddress(ip) in ip_range:
23+ for interface, (ip_range, static_range) in interface_ranges.items():
24+ ipaddress = netaddr.IPAddress(ip)
25+ if ipaddress in ip_range or ipaddress in static_range:
26 mac_address.cluster_interface = interface
27 mac_address.save()
28+
29+ # Locate the Network to which this MAC belongs.
30+ ipnetwork = interface.network
31+ if ipnetwork is not None:
32+ try:
33+ network = Network.objects.get(ip=ipnetwork.ip.format())
34+ network.macaddress_set.add(mac_address)
35+ except Network.DoesNotExist:
36+ pass
37+
38 # Cheap optimisation. No other interfaces will match, so
39 # break out of the loop.
40 break
41
42=== modified file 'src/maasserver/forms.py'
43--- src/maasserver/forms.py 2014-07-09 08:45:22 +0000
44+++ src/maasserver/forms.py 2014-08-20 02:35:32 +0000
45@@ -18,6 +18,7 @@
46 "BootSourceForm",
47 "BootSourceSelectionForm",
48 "BulkNodeActionForm",
49+ "create_Network_from_NodeGroupInterface",
50 "CommissioningForm",
51 "CommissioningScriptForm",
52 "DownloadProgressForm",
53@@ -110,6 +111,7 @@
54 Tag,
55 Zone,
56 )
57+from maasserver.models.network import get_name_and_vlan_from_cluster_interface
58 from maasserver.models.nodegroup import NODEGROUP_CLUSTER_NAME_TEMPLATE
59 from maasserver.node_action import (
60 ACTION_CLASSES,
61@@ -1127,6 +1129,33 @@
62 return True
63
64
65+def create_Network_from_NodeGroupInterface(interface):
66+ """Given a `NodeGroupInterface`, create its Network counterpart."""
67+ # This method cannot use non-orm model properties because it needs
68+ # to be used in a data migration, where they won't work.
69+ if not interface.subnet_mask:
70+ # Can be None or empty string, do nothing if so.
71+ return
72+
73+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
74+ ipnetwork = make_network(interface.ip, interface.subnet_mask)
75+ network = Network(
76+ name=name,
77+ ip=unicode(ipnetwork.network),
78+ netmask=unicode(ipnetwork.netmask),
79+ vlan_tag=vlan_tag,
80+ description=(
81+ "Auto created when creating interface %s on cluster "
82+ "%s" % (interface.interface, interface.nodegroup.name)),
83+ )
84+ try:
85+ network.save()
86+ except ValidationError:
87+ # It probably already exists, keep calm and carry on.
88+ return
89+ return network
90+
91+
92 class NodeGroupInterfaceForm(ModelForm):
93
94 management = forms.TypedChoiceField(
95@@ -1154,6 +1183,15 @@
96 'static_ip_range_high',
97 )
98
99+ def save(self, *args, **kwargs):
100+ """Override `ModelForm`.save() so that the network data is copied
101+ to a `Network` instance."""
102+ interface = super(NodeGroupInterfaceForm, self).save(*args, **kwargs)
103+ if interface.network is None:
104+ return interface
105+ create_Network_from_NodeGroupInterface(interface)
106+ return interface
107+
108 def clean(self):
109 cleaned_data = super(NodeGroupInterfaceForm, self).clean()
110 static_ip_range_low = cleaned_data.get('static_ip_range_low')
111
112=== modified file 'src/maasserver/models/network.py'
113--- src/maasserver/models/network.py 2014-07-08 07:33:43 +0000
114+++ src/maasserver/models/network.py 2014-08-20 02:35:32 +0000
115@@ -186,6 +186,22 @@
116 return specifier_class(spec)
117
118
119+def get_name_and_vlan_from_cluster_interface(cluster_interface):
120+ """Given a `NodeGroupInterface`, return a name suitable for a `Network`.
121+
122+ :return: a tuple of the new name and vlan tag. vlan tag may be None
123+ """
124+ name = cluster_interface.interface
125+ nodegroup_name = cluster_interface.nodegroup.name
126+ vlan_tag = None
127+ if '.' in name:
128+ _, vlan_tag = name.split('.', 1)
129+ name = name.replace('.', '-')
130+ name = name.replace(':', '-')
131+ network_name = "-".join((nodegroup_name, name))
132+ return network_name, vlan_tag
133+
134+
135 class NetworkManager(Manager):
136 """Manager for :class:`Network` model class.
137
138
139=== modified file 'src/maasserver/models/tests/test_network.py'
140--- src/maasserver/models/tests/test_network.py 2014-06-30 14:46:10 +0000
141+++ src/maasserver/models/tests/test_network.py 2014-08-20 02:35:32 +0000
142@@ -22,12 +22,14 @@
143 from maasserver.models.network import (
144 get_specifier_type,
145 IPSpecifier,
146+ get_name_and_vlan_from_cluster_interface,
147 NameSpecifier,
148 parse_network_spec,
149 VLANSpecifier,
150 )
151 from maasserver.testing.factory import factory
152 from maasserver.testing.testcase import MAASServerTestCase
153+from mock import sentinel
154 from netaddr import (
155 IPAddress,
156 IPNetwork,
157@@ -129,6 +131,43 @@
158 self.assertRaises(ValidationError, parse_network_spec, '10.4.4.4')
159
160
161+class TestGetNameAndVlanFromClusterInterface(MAASServerTestCase):
162+ """Tests for `get_name_and_vlan_from_cluster_interface`."""
163+
164+ def make_interface_name(self, basename):
165+ interface = sentinel.interface
166+ interface.nodegroup = sentinel.nodegroup
167+ interface.nodegroup.name = factory.make_name('name')
168+ interface.interface = basename
169+ return interface
170+
171+ def test_returns_simple_name_unaltered(self):
172+ interface = self.make_interface_name(factory.make_name('iface'))
173+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
174+ expected_name = '%s-%s' % (
175+ interface.nodegroup.name, interface.interface)
176+ self.assertEqual((expected_name, None), (name, vlan_tag))
177+
178+ def test_substitutes_colon(self):
179+ interface = self.make_interface_name('eth0:0')
180+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
181+ expected_name = '%s-eth0-0' % interface.nodegroup.name
182+ self.assertEqual((expected_name, None), (name, vlan_tag))
183+
184+ def test_returns_with_vlan_tag(self):
185+ interface = self.make_interface_name('eth0.5')
186+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
187+ expected_name = '%s-eth0-5' % interface.nodegroup.name
188+ self.assertEqual((expected_name, '5'), (name, vlan_tag))
189+
190+ def test_returns_name_with_crazy_colon_and_vlan(self):
191+ # For truly twisted network admins.
192+ interface = self.make_interface_name('eth0:2.3')
193+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
194+ expected_name = '%s-eth0-2-3' % interface.nodegroup.name
195+ self.assertEqual((expected_name, '3'), (name, vlan_tag))
196+
197+
198 class TestNetworkManager(MAASServerTestCase):
199
200 def test_get_from_spec_validates_first(self):
201
202=== modified file 'src/maasserver/tests/test_api_nodegroup.py'
203--- src/maasserver/tests/test_api_nodegroup.py 2014-07-03 09:19:18 +0000
204+++ src/maasserver/tests/test_api_nodegroup.py 2014-08-20 02:35:32 +0000
205@@ -32,6 +32,7 @@
206 NODEGROUP_STATUS_CHOICES,
207 NODEGROUPINTERFACE_MANAGEMENT,
208 )
209+from maasserver.forms import create_Network_from_NodeGroupInterface
210 from maasserver.models import (
211 Config,
212 DHCPLease,
213@@ -77,12 +78,16 @@
214 )
215
216
217-def get_random_ip_from_interface_range(interface):
218+def get_random_ip_from_interface_range(interface, use_static_range=None):
219 """Return a random IP from the pool available to an interface.
220
221 :return: An IP address as a string."""
222- ip_range = netaddr.IPRange(
223- interface.ip_range_low, interface.ip_range_high)
224+ if use_static_range:
225+ ip_range = netaddr.IPRange(
226+ interface.static_ip_range_low, interface.static_ip_range_high)
227+ else:
228+ ip_range = netaddr.IPRange(
229+ interface.ip_range_low, interface.ip_range_high)
230 chosen_ip = random.choice(ip_range)
231 return unicode(chosen_ip)
232
233@@ -830,7 +835,7 @@
234 class TestUpdateMacClusterInterfaces(MAASServerTestCase):
235 """Tests for `update_mac_cluster_interfaces`()."""
236
237- def test_updates_mac_cluster_interfaces(self):
238+ def make_cluster_with_macs_and_leases(self, use_static_range=False):
239 cluster = factory.make_node_group()
240 mac_addresses = {
241 factory.make_mac_address(): factory.make_node_group_interface(
242@@ -838,17 +843,49 @@
243 for i in range(4)
244 }
245 leases = {
246- get_random_ip_from_interface_range(interface): (
247+ get_random_ip_from_interface_range(interface, use_static_range): (
248 mac_address.mac_address)
249- for mac_address, interface in mac_addresses.items()
250+ for mac_address, interface in mac_addresses.viewitems()
251 }
252- update_mac_cluster_interfaces(leases, cluster)
253- results = {
254- mac_address: mac_address.cluster_interface
255- for mac_address in MACAddress.objects.filter(
256- mac_address__in=leases.values())
257- }
258- self.assertEqual(mac_addresses, results)
259+ return cluster, mac_addresses, leases
260+
261+ def test_updates_mac_cluster_interfaces(self):
262+ cluster, mac_addresses, leases = (
263+ self.make_cluster_with_macs_and_leases())
264+ update_mac_cluster_interfaces(leases, cluster)
265+ results = {
266+ mac_address: mac_address.cluster_interface
267+ for mac_address in MACAddress.objects.filter(
268+ mac_address__in=leases.values())
269+ }
270+ self.assertEqual(mac_addresses, results)
271+
272+ def test_considers_static_range_when_updating_interfaces(self):
273+ cluster, mac_addresses, leases = (
274+ self.make_cluster_with_macs_and_leases(use_static_range=True))
275+ update_mac_cluster_interfaces(leases, cluster)
276+ results = {
277+ mac_address: mac_address.cluster_interface
278+ for mac_address in MACAddress.objects.filter(
279+ mac_address__in=leases.values())
280+ }
281+ self.assertEqual(mac_addresses, results)
282+
283+ def test_updates_network_relations(self):
284+ # update_mac_cluster_interfaces should also associate the mac
285+ # with the network on which it resides.
286+ cluster, mac_addresses, leases = (
287+ self.make_cluster_with_macs_and_leases())
288+ expected_relations = []
289+ for mac, interface in mac_addresses.viewitems():
290+ net = create_Network_from_NodeGroupInterface(interface)
291+ expected_relations.append((net, mac))
292+ update_mac_cluster_interfaces(leases, cluster)
293+ # Doing a single giant comparison here would be unintuitive and
294+ # fugly, so I'm iterating.
295+ for net, mac in expected_relations:
296+ [observed_macddress] = net.macaddress_set.all()
297+ self.assertThat(mac, Equals(observed_macddress))
298
299 def test_ignores_mac_not_attached_to_cluster(self):
300 cluster = factory.make_node_group()
301
302=== modified file 'src/maasserver/tests/test_forms.py'
303--- src/maasserver/tests/test_forms.py 2014-07-09 11:18:41 +0000
304+++ src/maasserver/tests/test_forms.py 2014-08-20 02:35:32 +0000
305@@ -16,6 +16,7 @@
306
307 from cStringIO import StringIO
308 import json
309+import random
310
311 from django import forms
312 from django.conf import settings
313@@ -92,6 +93,7 @@
314 Zone,
315 )
316 from maasserver.models.config import DEFAULT_CONFIG
317+from maasserver.models.network import get_name_and_vlan_from_cluster_interface
318 from maasserver.models.staticipaddress import StaticIPAddress
319 from maasserver.node_action import (
320 Commission,
321@@ -1054,10 +1056,24 @@
322 ]
323
324
325+def make_ngi_instance(nodegroup=None):
326+ """Create a `NodeGroupInterface` with nothing set but `nodegroup`.
327+
328+ This is used by tests to instantiate the cluster interface form for
329+ a given cluster. We create an initial cluster interface object just
330+ to tell it which cluster that is.
331+ """
332+ if nodegroup is None:
333+ nodegroup = factory.make_node_group()
334+ return NodeGroupInterface(nodegroup=nodegroup)
335+
336+
337 class TestNodeGroupInterfaceForm(MAASServerTestCase):
338
339 def test_NodeGroupInterfaceForm_validates_parameters(self):
340- form = NodeGroupInterfaceForm(data={'ip': factory.getRandomString()})
341+ form = NodeGroupInterfaceForm(
342+ data={'ip': factory.getRandomString()},
343+ instance=make_ngi_instance())
344 self.assertFalse(form.is_valid())
345 self.assertEquals(
346 {'ip': ['Enter a valid IPv4 or IPv6 address.']}, form._errors)
347@@ -1069,8 +1085,7 @@
348 del int_settings[field_name]
349 nodegroup = factory.make_node_group()
350 form = NodeGroupInterfaceForm(
351- data=int_settings,
352- instance=NodeGroupInterface(nodegroup=nodegroup))
353+ data=int_settings, instance=make_ngi_instance(nodegroup))
354 interface = form.save()
355 field_values = [
356 getattr(interface, field_name) for field_name in nullable_fields]
357@@ -1097,6 +1112,75 @@
358 form._errors['static_ip_range_high'])
359
360
361+class TestNodeGroupInterfaceFormNetworkCreation(MAASServerTestCase):
362+ """Tests for when NodeGroupInterfaceForm creates a Network."""
363+
364+ def test_creates_network_name(self):
365+ int_settings = factory.get_interface_fields()
366+ int_settings['interface'] = 'eth0:1'
367+ interface = make_ngi_instance()
368+ form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
369+ form.save()
370+ [network] = Network.objects.all()
371+ expected, _ = get_name_and_vlan_from_cluster_interface(interface)
372+ self.assertEqual(expected, network.name)
373+
374+ def test_sets_vlan_tag(self):
375+ int_settings = factory.get_interface_fields()
376+ vlan_tag = random.randint(1, 10)
377+ int_settings['interface'] = 'eth0.%s' % vlan_tag
378+ interface = make_ngi_instance()
379+ form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
380+ form.save()
381+ [network] = Network.objects.all()
382+ self.assertEqual(vlan_tag, network.vlan_tag)
383+
384+ def test_vlan_tag_is_None_if_no_vlan(self):
385+ int_settings = factory.get_interface_fields()
386+ int_settings['interface'] = 'eth0:1'
387+ interface = make_ngi_instance()
388+ form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
389+ form.save()
390+ [network] = Network.objects.all()
391+ self.assertIs(None, network.vlan_tag)
392+
393+ def test_sets_network_values(self):
394+ int_settings = factory.get_interface_fields()
395+ interface = make_ngi_instance()
396+ form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
397+ form.save()
398+ [network] = Network.objects.all()
399+ expected_net_address = unicode(interface.network.network)
400+ expected_netmask = unicode(interface.network.netmask)
401+ self.assertThat(
402+ network, MatchesStructure.byEquality(
403+ ip=expected_net_address,
404+ netmask=expected_netmask))
405+
406+ def test_does_not_create_new_network_if_already_exists(self):
407+ int_settings = factory.get_interface_fields()
408+ interface = make_ngi_instance()
409+ form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
410+ # The easiest way to pre-create the same network is just to save
411+ # the form twice.
412+ form.save()
413+ [existing_network] = Network.objects.all()
414+ form.save()
415+ self.assertItemsEqual([existing_network], Network.objects.all())
416+
417+ def test_creates_many_unique_networks(self):
418+ names = ('eth0', 'eth0:1', 'eth0.1', 'eth0:1.2')
419+ for name in names:
420+ int_settings = factory.get_interface_fields()
421+ int_settings['interface'] = name
422+ interface = make_ngi_instance()
423+ form = NodeGroupInterfaceForm(
424+ data=int_settings, instance=interface)
425+ form.save()
426+
427+ self.assertEqual(len(names), len(Network.objects.all()))
428+
429+
430 class TestValidateNewStaticIPRanges(MAASServerTestCase):
431 """Tests for `validate_new_static_ip_ranges`()."""
432

Subscribers

People subscribed via source and target branches

to all changes: