Merge lp:~julian-edwards/maas/auto-link-mac-to-network into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Superseded
Proposed branch: lp:~julian-edwards/maas/auto-link-mac-to-network
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 534 lines (+439/-22)
4 files modified
src/maasserver/api.py (+10/-0)
src/maasserver/forms.py (+34/-20)
src/maasserver/migrations/0099_convert_cluster_interfaces_to_networks.py (+371/-0)
src/maasserver/tests/test_api_nodegroup.py (+24/-2)
To merge this branch: bzr merge lp:~julian-edwards/maas/auto-link-mac-to-network
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+230421@code.launchpad.net

This proposal has been superseded by a proposal from 2014-08-12.

Commit message

Auto-populate the connected Network for a MAC at the same time the MAC's cluster_interface is set (at lease upload time).

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.py'
2--- src/maasserver/api.py 2014-08-11 21:49:55 +0000
3+++ src/maasserver/api.py 2014-08-12 03:07:38 +0000
4@@ -1728,6 +1728,16 @@
5 if netaddr.IPAddress(ip) in ip_range:
6 mac_address.cluster_interface = interface
7 mac_address.save()
8+
9+ # Locate the Network to which this MAC belongs.
10+ ipnetwork = interface.network
11+ if ipnetwork is not None:
12+ try:
13+ network = Network.objects.get(ip=ipnetwork.ip.format())
14+ network.macaddress_set.add(mac_address)
15+ except Network.DoesNotExist:
16+ pass
17+
18 # Cheap optimisation. No other interfaces will match, so
19 # break out of the loop.
20 break
21
22=== modified file 'src/maasserver/forms.py'
23--- src/maasserver/forms.py 2014-08-11 10:28:42 +0000
24+++ src/maasserver/forms.py 2014-08-12 03:07:38 +0000
25@@ -18,6 +18,7 @@
26 "BootSourceForm",
27 "BootSourceSelectionForm",
28 "BulkNodeActionForm",
29+ "create_Network_from_NodeGroupInterface",
30 "CommissioningForm",
31 "CommissioningScriptForm",
32 "DownloadProgressForm",
33@@ -1153,6 +1154,38 @@
34 return True
35
36
37+def create_Network_from_NodeGroupInterface(interface):
38+ """Given a `NodeGroupInterface`, create its Network counterpart."""
39+ # This method cannot use non-orm model properties because it needs
40+ # to be used in a data migration, where they won't work.
41+ if not interface.subnet_mask:
42+ # Can be None or empty string, do nothing if so.
43+ return
44+
45+ name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
46+ ipnetwork = make_network(interface.ip, interface.subnet_mask)
47+ network = Network(
48+ name=name,
49+ ip=unicode(ipnetwork.network),
50+ netmask=unicode(ipnetwork.netmask),
51+ vlan_tag=vlan_tag,
52+ # I bloody hate the damn linter. It actually prefers this.
53+ description="Auto created when creating interface %s on "
54+ "cluster %s" % (
55+ interface.name, interface.nodegroup.name),
56+ )
57+ try:
58+ network.save()
59+ except ValidationError as e:
60+ # It probably already exists, keep calm and carry on.
61+ maaslog.warning(
62+ "Failed to create Network when adding/editing cluster "
63+ "interface %s with error [%s]. This is OK if it already "
64+ "exists, or it could be another error" % (name, unicode(e)))
65+ pass
66+ return network
67+
68+
69 class NodeGroupInterfaceForm(ModelForm):
70
71 management = forms.TypedChoiceField(
72@@ -1187,26 +1220,7 @@
73 interface = super(NodeGroupInterfaceForm, self).save(*args, **kwargs)
74 if interface.network is None:
75 return interface
76- name, vlan_tag = get_name_and_vlan_from_cluster_interface(interface)
77- network = Network(
78- name=name,
79- ip=unicode(interface.network.network),
80- netmask=unicode(interface.network.netmask),
81- vlan_tag=vlan_tag,
82- # I bloody hate the damn linter. It actually prefers this.
83- description="Auto created when creating interface %s on "
84- "cluster %s" % (
85- interface.name, interface.nodegroup.name),
86- )
87- try:
88- network.save()
89- except ValidationError as e:
90- # It probably already exists, keep calm and carry on.
91- maaslog.warning(
92- "Failed to create Network when adding/editing cluster "
93- "interface %s with error [%s]. This is OK if it already "
94- "exists, or it could be another error" % (name, unicode(e)))
95- pass
96+ create_Network_from_NodeGroupInterface(interface)
97 return interface
98
99 def compute_name(self):
100
101=== added file 'src/maasserver/migrations/0099_convert_cluster_interfaces_to_networks.py'
102--- src/maasserver/migrations/0099_convert_cluster_interfaces_to_networks.py 1970-01-01 00:00:00 +0000
103+++ src/maasserver/migrations/0099_convert_cluster_interfaces_to_networks.py 2014-08-12 03:07:38 +0000
104@@ -0,0 +1,371 @@
105+# -*- coding: utf-8 -*-
106+from django.db import models
107+from maasserver.forms import create_Network_from_NodeGroupInterface
108+from south.db import db
109+from south.utils import datetime_utils as datetime
110+from south.v2 import DataMigration
111+
112+
113+class Migration(DataMigration):
114+
115+ def forwards(self, orm):
116+ "Write your forwards methods here."
117+ # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..."
118+ interfaces = orm['maasserver.NodeGroupInterface'].objects.all()
119+ for interface in interfaces:
120+ create_Network_from_NodeGroupInterface(interface)
121+
122+ def backwards(self, orm):
123+ "Write your backwards methods here."
124+ # No point going backwards and it's probably dangerous to delete
125+ # Networks. The forwards code is idempotent anyway.
126+ pass
127+
128+ models = {
129+ u'auth.group': {
130+ 'Meta': {'object_name': 'Group'},
131+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
133+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
134+ },
135+ u'auth.permission': {
136+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
137+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
138+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
139+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
141+ },
142+ u'auth.user': {
143+ 'Meta': {'object_name': 'User'},
144+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
145+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
146+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
147+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
148+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
149+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
150+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
151+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
152+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
153+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
154+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
155+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
156+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
157+ },
158+ u'contenttypes.contenttype': {
159+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
160+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
161+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
162+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
163+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
164+ },
165+ u'maasserver.bootimage': {
166+ 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
167+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
168+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
169+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
170+ 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
171+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
172+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
173+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
174+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
175+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
176+ 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
177+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
178+ 'xinstall_path': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
179+ 'xinstall_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '30', 'null': 'True', 'blank': 'True'})
180+ },
181+ u'maasserver.bootresource': {
182+ 'Meta': {'unique_together': "((u'rtype', u'name', u'architecture'),)", 'object_name': 'BootResource'},
183+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
184+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
185+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
186+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
187+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
188+ 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
189+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
190+ },
191+ u'maasserver.bootresourcefile': {
192+ 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
193+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
194+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
195+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
196+ 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'tgz'", 'max_length': '20'}),
197+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
198+ 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
199+ 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
200+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
201+ },
202+ u'maasserver.bootresourceset': {
203+ 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
204+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
205+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
206+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
207+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
208+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
209+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
210+ },
211+ u'maasserver.bootsource': {
212+ 'Meta': {'object_name': 'BootSource'},
213+ 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
214+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
215+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
216+ 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
217+ 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
218+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
219+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
220+ },
221+ u'maasserver.bootsourceselection': {
222+ 'Meta': {'object_name': 'BootSourceSelection'},
223+ 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
224+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
225+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
226+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
227+ 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
228+ 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
229+ 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
230+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
231+ },
232+ u'maasserver.componenterror': {
233+ 'Meta': {'object_name': 'ComponentError'},
234+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
235+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
236+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
237+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
238+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
239+ },
240+ u'maasserver.config': {
241+ 'Meta': {'object_name': 'Config'},
242+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
244+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
245+ },
246+ u'maasserver.dhcplease': {
247+ 'Meta': {'object_name': 'DHCPLease'},
248+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
250+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
251+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
252+ },
253+ u'maasserver.downloadprogress': {
254+ 'Meta': {'object_name': 'DownloadProgress'},
255+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
256+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
257+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
258+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
259+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
260+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
261+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
262+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
263+ },
264+ u'maasserver.event': {
265+ 'Meta': {'object_name': 'Event'},
266+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
267+ 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
268+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
270+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
271+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
272+ },
273+ u'maasserver.eventtype': {
274+ 'Meta': {'object_name': 'EventType'},
275+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
276+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
277+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
278+ 'level': ('django.db.models.fields.IntegerField', [], {}),
279+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
280+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
281+ },
282+ u'maasserver.filestorage': {
283+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
284+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
285+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
286+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
287+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'bf5ca3cc-21b2-11e4-9913-0026c71eea0e'", 'unique': 'True', 'max_length': '36'}),
288+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
289+ },
290+ u'maasserver.largefile': {
291+ 'Meta': {'object_name': 'LargeFile'},
292+ 'content': ('maasserver.fields.LargeObjectField', [], {}),
293+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
294+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295+ 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
296+ 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
297+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
298+ },
299+ u'maasserver.licensekey': {
300+ 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
301+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
302+ 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
303+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
304+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
305+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
306+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
307+ },
308+ u'maasserver.macaddress': {
309+ 'Meta': {'ordering': "(u'created',)", 'object_name': 'MACAddress'},
310+ 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'blank': 'True'}),
311+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
312+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
313+ 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}),
314+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
315+ 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
316+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
317+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
318+ },
319+ u'maasserver.macstaticipaddresslink': {
320+ 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'},
321+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
322+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
323+ 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}),
324+ 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}),
325+ 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
326+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
327+ },
328+ u'maasserver.network': {
329+ 'Meta': {'object_name': 'Network'},
330+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
331+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
332+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
333+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
334+ 'netmask': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
335+ 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
336+ },
337+ u'maasserver.node': {
338+ 'Meta': {'object_name': 'Node'},
339+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
340+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
341+ 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
342+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
343+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
344+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
345+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
346+ 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
347+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
348+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
349+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
350+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
351+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
352+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
353+ 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
354+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
355+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
356+ 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
357+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
358+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
359+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
360+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
361+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-bf56dba4-21b2-11e4-9913-0026c71eea0e'", 'unique': 'True', 'max_length': '41'}),
362+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
363+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
364+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
365+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
366+ },
367+ u'maasserver.nodegroup': {
368+ 'Meta': {'object_name': 'NodeGroup'},
369+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
370+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
371+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
372+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
373+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
374+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
375+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
376+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
377+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
378+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
379+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
380+ },
381+ u'maasserver.nodegroupinterface': {
382+ 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
383+ 'broadcast_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
384+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
385+ 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
386+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
387+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
388+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
389+ 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
390+ 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
391+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
392+ 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
393+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
394+ 'router_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
395+ 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
396+ 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
397+ 'subnet_mask': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
398+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
399+ },
400+ u'maasserver.sshkey': {
401+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
402+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
403+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
404+ 'key': ('django.db.models.fields.TextField', [], {}),
405+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
406+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
407+ },
408+ u'maasserver.sslkey': {
409+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
410+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
411+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
412+ 'key': ('django.db.models.fields.TextField', [], {}),
413+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
414+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
415+ },
416+ u'maasserver.staticipaddress': {
417+ 'Meta': {'object_name': 'StaticIPAddress'},
418+ 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
419+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
420+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
421+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
422+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
423+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
424+ },
425+ u'maasserver.tag': {
426+ 'Meta': {'object_name': 'Tag'},
427+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
428+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
429+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
430+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
431+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
432+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
433+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
434+ },
435+ u'maasserver.userprofile': {
436+ 'Meta': {'object_name': 'UserProfile'},
437+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
438+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
439+ },
440+ u'maasserver.zone': {
441+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
442+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
443+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
444+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
445+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
446+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
447+ },
448+ u'piston.consumer': {
449+ 'Meta': {'object_name': 'Consumer'},
450+ 'description': ('django.db.models.fields.TextField', [], {}),
451+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
452+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
453+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
454+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
455+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
456+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
457+ },
458+ u'piston.token': {
459+ 'Meta': {'object_name': 'Token'},
460+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
461+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
462+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
463+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
464+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
465+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
466+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
467+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1407801231L'}),
468+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
469+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
470+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
471+ }
472+ }
473+
474+ complete_apps = ['maasserver']
475+ symmetrical = True
476
477=== modified file 'src/maasserver/tests/test_api_nodegroup.py'
478--- src/maasserver/tests/test_api_nodegroup.py 2014-08-11 21:49:55 +0000
479+++ src/maasserver/tests/test_api_nodegroup.py 2014-08-12 03:07:38 +0000
480@@ -32,6 +32,7 @@
481 NODEGROUP_STATUS_CHOICES,
482 NODEGROUPINTERFACE_MANAGEMENT,
483 )
484+from maasserver.forms import create_Network_from_NodeGroupInterface
485 from maasserver.models import (
486 Config,
487 DHCPLease,
488@@ -833,7 +834,7 @@
489 class TestUpdateMacClusterInterfaces(MAASServerTestCase):
490 """Tests for `update_mac_cluster_interfaces`()."""
491
492- def test_updates_mac_cluster_interfaces(self):
493+ def make_cluster_with_macs_and_leases(self):
494 cluster = factory.make_node_group()
495 mac_addresses = {
496 factory.make_mac_address(): factory.make_node_group_interface(
497@@ -843,8 +844,13 @@
498 leases = {
499 get_random_ip_from_interface_range(interface): (
500 mac_address.mac_address)
501- for mac_address, interface in mac_addresses.items()
502+ for mac_address, interface in mac_addresses.viewitems()
503 }
504+ return cluster, mac_addresses, leases
505+
506+ def test_updates_mac_cluster_interfaces(self):
507+ cluster, mac_addresses, leases = (
508+ self.make_cluster_with_macs_and_leases())
509 update_mac_cluster_interfaces(leases, cluster)
510 results = {
511 mac_address: mac_address.cluster_interface
512@@ -853,6 +859,22 @@
513 }
514 self.assertEqual(mac_addresses, results)
515
516+ def test_updates_network_relations(self):
517+ # update_mac_cluster_interfaces should also associate the mac
518+ # with the network on which it resides.
519+ cluster, mac_addresses, leases = (
520+ self.make_cluster_with_macs_and_leases())
521+ expected_relations = []
522+ for mac, interface in mac_addresses.viewitems():
523+ net = create_Network_from_NodeGroupInterface(interface)
524+ expected_relations.append((net, mac))
525+ update_mac_cluster_interfaces(leases, cluster)
526+ # Doing a single giant comparison here would be unintuitive and
527+ # fugly, so I'm iterating.
528+ for net, mac in expected_relations:
529+ [observed_macddress] = net.macaddress_set.all()
530+ self.assertEqual(mac, observed_macddress)
531+
532 def test_ignores_mac_not_attached_to_cluster(self):
533 cluster = factory.make_node_group()
534 mac_address = factory.make_mac_address()