Merge lp:~julian-edwards/maas/cluster-interfaces-as-networks into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2672
Proposed branch: lp:~julian-edwards/maas/cluster-interfaces-as-networks
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 356 lines (+180/-21)
4 files modified
src/maasserver/forms.py (+32/-0)
src/maasserver/models/network.py (+16/-0)
src/maasserver/models/tests/test_network.py (+39/-0)
src/maasserver/tests/test_forms.py (+93/-21)
To merge this branch: bzr merge lp:~julian-edwards/maas/cluster-interfaces-as-networks
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+230038@code.launchpad.net

Commit message

Copy any network information from cluster interface edits into the Network table itself.

Description of the change

Pre-imped with jtv.

First part of a few branches to make a change that will list on which network each Node's MAC resides.

This simply copies network info from a cluster into the Network model itself, creating a new model as necessary, whose name is <nodegroup name>-<interface name> and the vlan_tag set as appropriate where it was present in the interface name.

At some point, we can reference the network model itself and remove the duplicate fields on the cluster interface. (I've not done it here because it would be a large change involving complex form stuff that I need to get advice on first.)

Subsequent branches will add a data migration to populate networks based on the cluster interfaces, and a web UI and API change to show the networks next to the MACs.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

There are some finicky details here. I left comments inline; some of them I think really need addressing before landing.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (3.5 KiB)

On Friday 08 Aug 2014 05:32:16 you wrote:
> Review: Approve
>
> There are some finicky details here. I left comments inline; some of them I
> think really need addressing before landing.

Finicky is good sometimes, especially with networking! Thank you for the
thoughtful review, as ever.

> > + def save(self, *args, **kwargs):
> Can you document very briefly what unusual thing this method does? Saves
> the reader having to go through the code.

Done, well spotted.

> > + try:
> > + network.save()
> > + except ValidationError:
> > + # It already exists, keep calm and carry on.
>
> It may already exist, or you may have hit a difference between cluster
> interface validation and network validation, or there may be a bug which
> now leaves no trace. If there is no separate exception type for this, and
> you can't make an explicit call to just the uniqueness check, then at least
> log the error.

I've added the logging, thanks for calling this out.

> > + name = cluster_interface.interface
> > + nodegroup_name = cluster_interface.nodegroup.name
> > + vlan_tag = None
> > + if '.' in name:
> > + name, vlan_tag = name.split('.', 1)
>
> Won't this generate duplicate names for e.g. eth0 and eth0.1? The form's
> blanket amnesty for ValidationErrors might hide the problem, but AFAICT one
> of the networks would simply not appear.
>
> ISTM you need to replace the dot, not strip off the vlan suffix.

Well spotted! In fact, with some more stringent testing I added later, this
actually got caught.

> > +class TestMangleNameFromClusterInterface(MAASServerTestCase):
> The test case's name no longer matches that of the function it tests.

Argh. I renamed the damn function with the best of intentions as well!

> > + def test_returns_name_unaltered(self):
> Sometimes it does, yes. Could you make the condition under which this
> happens a bit more explicit, e.g. "straightforward name"?

Done. "Simple name".

> > + interface = sentinel.interface
> > + interface.nodegroup = sentinel.nodegroup
> > + interface.nodegroup.name = factory.make_name('name')
> > + interface.interface = 'eth0'
>
> Consider extracting these 4 lines (which recur in each of these tests) into
> a helper.

Done.

> > + def test_returns_with_colon_substituted(self):
> Maybe just "substitutes colon" instead of "returns with colon substituted"?

Done.

> All these tests use "eth0" as the base network interface. Not that that's
> particularly dangerous here, but it's a bad habit: some maniac could decide
> to take the base interface name from the operating system, or hard-code
> "eth0" for experimental changes, or assume that any colon or dot always
> comes after exactly 4 characters, without breaking the tests. There have
> to be at least some tests to show that you get the right value regardless
> of what that value actually is.

Right. I've changed the "simple" name test to use a random string for the
name.

> > +class TestNodeGroupInterfaceFormNetworkCreation(MAASServerTestCase):
> > + """Tests for when NodeGroupInterfaceForm creates a Network."""
>
> Given that lack of error detail ...

Read more...

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

The attempt to merge lp:~julian-edwards/maas/cluster-interfaces-as-networks into lp:maas 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
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://security.ubuntu.com trusty-security/main Sources
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates Release [59.7 kB]
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
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [106 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [67.1 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [285 kB]
Get:6 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [168 kB]
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en [125 kB]
Get:8 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en [82.7 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 894 kB in 0s (1,468 kB/s)
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 p...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2014-08-04 16:18:21 +0000
+++ src/maasserver/forms.py 2014-08-11 06:31:29 +0000
@@ -111,6 +111,7 @@
111 Tag,111 Tag,
112 Zone,112 Zone,
113 )113 )
114from maasserver.models.network import get_name_and_vlan_from_cluster_interface
114from maasserver.models.node import (115from maasserver.models.node import (
115 fqdn_is_duplicate,116 fqdn_is_duplicate,
116 nodegroup_fqdn,117 nodegroup_fqdn,
@@ -137,8 +138,11 @@
137from metadataserver.fields import Bin138from metadataserver.fields import Bin
138from metadataserver.models import CommissioningScript139from metadataserver.models import CommissioningScript
139from provisioningserver.drivers.osystem import OperatingSystemRegistry140from provisioningserver.drivers.osystem import OperatingSystemRegistry
141from provisioningserver.logger import get_maas_logger
140from provisioningserver.utils.network import make_network142from provisioningserver.utils.network import make_network
141143
144maaslog = get_maas_logger()
145
142# A reusable null-option for choice fields.146# A reusable null-option for choice fields.
143BLANK_CHOICE = ('', '-------')147BLANK_CHOICE = ('', '-------')
144148
@@ -1176,6 +1180,34 @@
1176 'static_ip_range_high',1180 'static_ip_range_high',
1177 )1181 )
11781182
1183 def save(self, *args, **kwargs):
1184 """Override `ModelForm`.save() so that the network data is copied
1185 to a `Network` instance."""
1186 interface = super(NodeGroupInterfaceForm, self).save(*args, **kwargs)
1187 if interface.network is None:
1188 return interface
1189 name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
1190 network = Network(
1191 name=name,
1192 ip=unicode(interface.network.network),
1193 netmask=unicode(interface.network.netmask),
1194 vlan_tag=vlan_tag,
1195 # I bloody hate the damn linter. It actually prefers this.
1196 description="Auto created when creating interface %s on "
1197 "cluster %s" % (
1198 interface.name, interface.nodegroup.name),
1199 )
1200 try:
1201 network.save()
1202 except ValidationError as e:
1203 # It probably already exists, keep calm and carry on.
1204 maaslog.warning(
1205 "Failed to create Network when adding/editing cluster "
1206 "interface %s with error [%s]. This is OK if it already "
1207 "exists, or it could be another error" % (name, unicode(e)))
1208 pass
1209 return interface
1210
1179 def compute_name(self):1211 def compute_name(self):
1180 """Return the value the `name` field should have.1212 """Return the value the `name` field should have.
11811213
11821214
=== modified file 'src/maasserver/models/network.py'
--- src/maasserver/models/network.py 2014-07-08 07:33:43 +0000
+++ src/maasserver/models/network.py 2014-08-11 06:31:29 +0000
@@ -186,6 +186,22 @@
186 return specifier_class(spec)186 return specifier_class(spec)
187187
188188
189def get_name_and_vlan_from_cluster_interface(cluster_interface):
190 """Given a `NodeGroupInterface`, return a name suitable for a `Network`.
191
192 :return: a tuple of the new name and vlan tag. vlan tag may be None
193 """
194 name = cluster_interface.interface
195 nodegroup_name = cluster_interface.nodegroup.name
196 vlan_tag = None
197 if '.' in name:
198 _, vlan_tag = name.split('.', 1)
199 name = name.replace('.', '-')
200 name = name.replace(':', '-')
201 network_name = "-".join((nodegroup_name, name))
202 return network_name, vlan_tag
203
204
189class NetworkManager(Manager):205class NetworkManager(Manager):
190 """Manager for :class:`Network` model class.206 """Manager for :class:`Network` model class.
191207
192208
=== modified file 'src/maasserver/models/tests/test_network.py'
--- src/maasserver/models/tests/test_network.py 2014-07-16 14:12:13 +0000
+++ src/maasserver/models/tests/test_network.py 2014-08-11 06:31:29 +0000
@@ -22,12 +22,14 @@
22from maasserver.models.network import (22from maasserver.models.network import (
23 get_specifier_type,23 get_specifier_type,
24 IPSpecifier,24 IPSpecifier,
25 get_name_and_vlan_from_cluster_interface,
25 NameSpecifier,26 NameSpecifier,
26 parse_network_spec,27 parse_network_spec,
27 VLANSpecifier,28 VLANSpecifier,
28 )29 )
29from maasserver.testing.factory import factory30from maasserver.testing.factory import factory
30from maasserver.testing.testcase import MAASServerTestCase31from maasserver.testing.testcase import MAASServerTestCase
32from mock import sentinel
31from netaddr import (33from netaddr import (
32 IPAddress,34 IPAddress,
33 IPNetwork,35 IPNetwork,
@@ -129,6 +131,43 @@
129 self.assertRaises(ValidationError, parse_network_spec, '10.4.4.4')131 self.assertRaises(ValidationError, parse_network_spec, '10.4.4.4')
130132
131133
134class TestGetNameAndVlanFromClusterInterface(MAASServerTestCase):
135 """Tests for `get_name_and_vlan_from_cluster_interface`."""
136
137 def make_interface_name(self, basename):
138 interface = sentinel.interface
139 interface.nodegroup = sentinel.nodegroup
140 interface.nodegroup.name = factory.make_name('name')
141 interface.interface = basename
142 return interface
143
144 def test_returns_simple_name_unaltered(self):
145 interface = self.make_interface_name(factory.make_name('iface'))
146 name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
147 expected_name = '%s-%s' % (
148 interface.nodegroup.name, interface.interface)
149 self.assertEqual((expected_name, None), (name, vlan_tag))
150
151 def test_substitutes_colon(self):
152 interface = self.make_interface_name('eth0:0')
153 name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
154 expected_name = '%s-eth0-0' % interface.nodegroup.name
155 self.assertEqual((expected_name, None), (name, vlan_tag))
156
157 def test_returns_with_vlan_tag(self):
158 interface = self.make_interface_name('eth0.5')
159 name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
160 expected_name = '%s-eth0-5' % interface.nodegroup.name
161 self.assertEqual((expected_name, '5'), (name, vlan_tag))
162
163 def test_returns_name_with_crazy_colon_and_vlan(self):
164 # For truly twisted network admins.
165 interface = self.make_interface_name('eth0:2.3')
166 name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
167 expected_name = '%s-eth0-2-3' % interface.nodegroup.name
168 self.assertEqual((expected_name, '3'), (name, vlan_tag))
169
170
132class TestNetworkManager(MAASServerTestCase):171class TestNetworkManager(MAASServerTestCase):
133172
134 def test_get_from_spec_validates_first(self):173 def test_get_from_spec_validates_first(self):
135174
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2014-08-08 19:04:18 +0000
+++ src/maasserver/tests/test_forms.py 2014-08-11 06:31:29 +0000
@@ -16,6 +16,7 @@
1616
17from cStringIO import StringIO17from cStringIO import StringIO
18import json18import json
19import random
1920
20from django import forms21from django import forms
21from django.conf import settings22from django.conf import settings
@@ -93,6 +94,7 @@
93 Zone,94 Zone,
94 )95 )
95from maasserver.models.config import DEFAULT_CONFIG96from maasserver.models.config import DEFAULT_CONFIG
97from maasserver.models.network import get_name_and_vlan_from_cluster_interface
96from maasserver.models.staticipaddress import StaticIPAddress98from maasserver.models.staticipaddress import StaticIPAddress
97from maasserver.node_action import (99from maasserver.node_action import (
98 Commission,100 Commission,
@@ -1052,23 +1054,24 @@
1052 ]1054 ]
10531055
10541056
1057def make_ngi_instance(nodegroup=None):
1058 """Create a `NodeGroupInterface` with nothing set but `nodegroup`.
1059
1060 This is used by tests to instantiate the cluster interface form for
1061 a given cluster. We create an initial cluster interface object just
1062 to tell it which cluster that is.
1063 """
1064 if nodegroup is None:
1065 nodegroup = factory.make_node_group()
1066 return NodeGroupInterface(nodegroup=nodegroup)
1067
1068
1055class TestNodeGroupInterfaceForm(MAASServerTestCase):1069class TestNodeGroupInterfaceForm(MAASServerTestCase):
10561070
1057 def make_ngi_instance(self, nodegroup=None):
1058 """Create a `NodeGroupInterface` with nothing set but `nodegroup`.
1059
1060 This is used by tests to instantiate the cluster interface form for
1061 a given cluster. We create an initial cluster interface object just
1062 to tell it which cluster that is.
1063 """
1064 if nodegroup is None:
1065 nodegroup = factory.make_node_group()
1066 return NodeGroupInterface(nodegroup=nodegroup)
1067
1068 def test__validates_parameters(self):1071 def test__validates_parameters(self):
1069 form = NodeGroupInterfaceForm(1072 form = NodeGroupInterfaceForm(
1070 data={'ip': factory.make_string()},1073 data={'ip': factory.make_string()},
1071 instance=self.make_ngi_instance())1074 instance=make_ngi_instance())
1072 self.assertFalse(form.is_valid())1075 self.assertFalse(form.is_valid())
1073 self.assertEquals(1076 self.assertEquals(
1074 {'ip': ['Enter a valid IPv4 or IPv6 address.']}, form._errors)1077 {'ip': ['Enter a valid IPv4 or IPv6 address.']}, form._errors)
@@ -1079,7 +1082,7 @@
1079 for field_name in nullable_fields:1082 for field_name in nullable_fields:
1080 del int_settings[field_name]1083 del int_settings[field_name]
1081 form = NodeGroupInterfaceForm(1084 form = NodeGroupInterfaceForm(
1082 data=int_settings, instance=self.make_ngi_instance())1085 data=int_settings, instance=make_ngi_instance())
1083 interface = form.save()1086 interface = form.save()
1084 field_values = [1087 field_values = [
1085 getattr(interface, field_name) for field_name in nullable_fields]1088 getattr(interface, field_name) for field_name in nullable_fields]
@@ -1090,7 +1093,7 @@
1090 int_settings = factory.get_interface_fields()1093 int_settings = factory.get_interface_fields()
1091 int_settings['name'] = name1094 int_settings['name'] = name
1092 form = NodeGroupInterfaceForm(1095 form = NodeGroupInterfaceForm(
1093 data=int_settings, instance=self.make_ngi_instance())1096 data=int_settings, instance=make_ngi_instance())
1094 interface = form.save()1097 interface = form.save()
1095 self.assertEqual(name, interface.name)1098 self.assertEqual(name, interface.name)
10961099
@@ -1099,7 +1102,7 @@
1099 int_settings['interface'] = factory.make_name('ether')1102 int_settings['interface'] = factory.make_name('ether')
1100 del int_settings['name']1103 del int_settings['name']
1101 form = NodeGroupInterfaceForm(1104 form = NodeGroupInterfaceForm(
1102 data=int_settings, instance=self.make_ngi_instance())1105 data=int_settings, instance=make_ngi_instance())
1103 interface = form.save()1106 interface = form.save()
1104 self.assertEqual(int_settings['interface'], interface.name)1107 self.assertEqual(int_settings['interface'], interface.name)
11051108
@@ -1108,7 +1111,7 @@
1108 int_settings['interface'] = 'eth1+1'1111 int_settings['interface'] = 'eth1+1'
1109 del int_settings['name']1112 del int_settings['name']
1110 form = NodeGroupInterfaceForm(1113 form = NodeGroupInterfaceForm(
1111 data=int_settings, instance=self.make_ngi_instance())1114 data=int_settings, instance=make_ngi_instance())
1112 interface = form.save()1115 interface = form.save()
1113 self.assertEqual('eth1--1', interface.name)1116 self.assertEqual('eth1--1', interface.name)
11141117
@@ -1118,10 +1121,10 @@
1118 del int_settings['name']1121 del int_settings['name']
1119 del int_settings['interface']1122 del int_settings['interface']
1120 form1 = NodeGroupInterfaceForm(1123 form1 = NodeGroupInterfaceForm(
1121 data=int_settings, instance=self.make_ngi_instance())1124 data=int_settings, instance=make_ngi_instance())
1122 interface1 = form1.save()1125 interface1 = form1.save()
1123 form2 = NodeGroupInterfaceForm(1126 form2 = NodeGroupInterfaceForm(
1124 data=int_settings, instance=self.make_ngi_instance())1127 data=int_settings, instance=make_ngi_instance())
1125 interface2 = form2.save()1128 interface2 = form2.save()
1126 self.assertNotIn(interface1.name, [None, ''])1129 self.assertNotIn(interface1.name, [None, ''])
1127 self.assertNotIn(interface2.name, [None, ''])1130 self.assertNotIn(interface2.name, [None, ''])
@@ -1134,7 +1137,7 @@
1134 del int_settings['name']1137 del int_settings['name']
1135 int_settings['interface'] = existing_interface.name1138 int_settings['interface'] = existing_interface.name
1136 form = NodeGroupInterfaceForm(1139 form = NodeGroupInterfaceForm(
1137 data=int_settings, instance=self.make_ngi_instance(cluster))1140 data=int_settings, instance=make_ngi_instance(cluster))
1138 interface = form.save()1141 interface = form.save()
1139 self.assertThat(interface.name, StartsWith(int_settings['interface']))1142 self.assertThat(interface.name, StartsWith(int_settings['interface']))
1140 self.assertNotEqual(int_settings['interface'], interface.name)1143 self.assertNotEqual(int_settings['interface'], interface.name)
@@ -1165,7 +1168,7 @@
1165 int_settings = factory.get_interface_fields(1168 int_settings = factory.get_interface_fields(
1166 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)1169 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
1167 form = NodeGroupInterfaceForm(1170 form = NodeGroupInterfaceForm(
1168 data=int_settings, instance=self.make_ngi_instance())1171 data=int_settings, instance=make_ngi_instance())
1169 mock = self.patch(form, "get_duplicate_fqdns")1172 mock = self.patch(form, "get_duplicate_fqdns")
1170 self.assertTrue(form.is_valid(), form.errors)1173 self.assertTrue(form.is_valid(), form.errors)
1171 self.assertThat(mock, MockCalledOnceWith())1174 self.assertThat(mock, MockCalledOnceWith())
@@ -1174,7 +1177,7 @@
1174 int_settings = factory.get_interface_fields(1177 int_settings = factory.get_interface_fields(
1175 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)1178 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
1176 form = NodeGroupInterfaceForm(1179 form = NodeGroupInterfaceForm(
1177 data=int_settings, instance=self.make_ngi_instance())1180 data=int_settings, instance=make_ngi_instance())
1178 mock = self.patch(form, "get_duplicate_fqdns")1181 mock = self.patch(form, "get_duplicate_fqdns")
1179 hostnames = [1182 hostnames = [
1180 factory.make_hostname("duplicate") for _ in range(0, 3)]1183 factory.make_hostname("duplicate") for _ in range(0, 3)]
@@ -1239,6 +1242,75 @@
1239 self.assertEqual(expected_duplicates, duplicates)1242 self.assertEqual(expected_duplicates, duplicates)
12401243
12411244
1245class TestNodeGroupInterfaceFormNetworkCreation(MAASServerTestCase):
1246 """Tests for when NodeGroupInterfaceForm creates a Network."""
1247
1248 def test_creates_network_name(self):
1249 int_settings = factory.get_interface_fields()
1250 int_settings['interface'] = 'eth0:1'
1251 interface = make_ngi_instance()
1252 form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
1253 form.save()
1254 [network] = Network.objects.all()
1255 expected, _ = get_name_and_vlan_from_cluster_interface(interface)
1256 self.assertEqual(expected, network.name)
1257
1258 def test_sets_vlan_tag(self):
1259 int_settings = factory.get_interface_fields()
1260 vlan_tag = random.randint(1, 10)
1261 int_settings['interface'] = 'eth0.%s' % vlan_tag
1262 interface = make_ngi_instance()
1263 form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
1264 form.save()
1265 [network] = Network.objects.all()
1266 self.assertEqual(vlan_tag, network.vlan_tag)
1267
1268 def test_vlan_tag_is_None_if_no_vlan(self):
1269 int_settings = factory.get_interface_fields()
1270 int_settings['interface'] = 'eth0:1'
1271 interface = make_ngi_instance()
1272 form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
1273 form.save()
1274 [network] = Network.objects.all()
1275 self.assertIs(None, network.vlan_tag)
1276
1277 def test_sets_network_values(self):
1278 int_settings = factory.get_interface_fields()
1279 interface = make_ngi_instance()
1280 form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
1281 form.save()
1282 [network] = Network.objects.all()
1283 expected_net_address = unicode(interface.network.network)
1284 expected_netmask = unicode(interface.network.netmask)
1285 self.assertThat(
1286 network, MatchesStructure.byEquality(
1287 ip=expected_net_address,
1288 netmask=expected_netmask))
1289
1290 def test_does_not_create_new_network_if_already_exists(self):
1291 int_settings = factory.get_interface_fields()
1292 interface = make_ngi_instance()
1293 form = NodeGroupInterfaceForm(data=int_settings, instance=interface)
1294 # The easiest way to pre-create the same network is just to save
1295 # the form twice.
1296 form.save()
1297 [existing_network] = Network.objects.all()
1298 form.save()
1299 self.assertItemsEqual([existing_network], Network.objects.all())
1300
1301 def test_creates_many_unique_networks(self):
1302 names = ('eth0', 'eth0:1', 'eth0.1', 'eth0:1.2')
1303 for name in names:
1304 int_settings = factory.get_interface_fields()
1305 int_settings['interface'] = name
1306 interface = make_ngi_instance()
1307 form = NodeGroupInterfaceForm(
1308 data=int_settings, instance=interface)
1309 form.save()
1310
1311 self.assertEqual(len(names), len(Network.objects.all()))
1312
1313
1242class TestValidateNewStaticIPRanges(MAASServerTestCase):1314class TestValidateNewStaticIPRanges(MAASServerTestCase):
1243 """Tests for `validate_new_static_ip_ranges`()."""1315 """Tests for `validate_new_static_ip_ranges`()."""
12441316