Merge lp:~blake-rouse/maas/node-gateway-subnet into lp:~maas-maintainers/maas/packaging

Proposed by Blake Rouse
Status: Superseded
Proposed branch: lp:~blake-rouse/maas/node-gateway-subnet
Merge into: lp:~maas-maintainers/maas/packaging
Prerequisite: lp:~blake-rouse/maas/pick-best-gateway-ip
Diff against target: 816 lines (+754/-0)
5 files modified
src/maasserver/migrations/0174_add_gateway_links_to_node.py (+493/-0)
src/maasserver/models/interface.py (+23/-0)
src/maasserver/models/node.py (+73/-0)
src/maasserver/models/tests/test_interface.py (+30/-0)
src/maasserver/models/tests/test_node.py (+135/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/node-gateway-subnet
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Needs Information
Mike Pontillo (community) Approve
Review via email: mp+270863@code.launchpad.net

This proposal has been superseded by a proposal from 2015-09-14.

Commit message

Add to node model IPv4 and IPv6 fields to set the subnet links which should be used as the default gateway on the node.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good.

I like that this change gives us the flexibility to link to an implicit gateway (via a StaticIPAddress's specified Subnet), or - perhaps one day - a specific IP address.

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Why is this Boeing merced against packaging?

review: Needs Information

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'src/maasserver/migrations/0174_add_gateway_links_to_node.py'
2--- src/maasserver/migrations/0174_add_gateway_links_to_node.py 1970-01-01 00:00:00 +0000
3+++ src/maasserver/migrations/0174_add_gateway_links_to_node.py 2015-09-11 19:32:41 +0000
4@@ -0,0 +1,493 @@
5+from django.db import models
6+from south.db import db
7+# -*- coding: utf-8 -*-
8+from south.utils import datetime_utils as datetime
9+from south.v2 import SchemaMigration
10+
11+
12+class Migration(SchemaMigration):
13+
14+ def forwards(self, orm):
15+ # Adding field 'Node.gateway_link_ipv4'
16+ db.add_column(u'maasserver_node', 'gateway_link_ipv4',
17+ self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'+', on_delete=models.SET_NULL, default=None, to=orm['maasserver.StaticIPAddress'], blank=True, null=True),
18+ keep_default=False)
19+
20+ # Adding field 'Node.gateway_link_ipv6'
21+ db.add_column(u'maasserver_node', 'gateway_link_ipv6',
22+ self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'+', on_delete=models.SET_NULL, default=None, to=orm['maasserver.StaticIPAddress'], blank=True, null=True),
23+ keep_default=False)
24+
25+
26+ def backwards(self, orm):
27+ # Deleting field 'Node.gateway_link_ipv4'
28+ db.delete_column(u'maasserver_node', 'gateway_link_ipv4_id')
29+
30+ # Deleting field 'Node.gateway_link_ipv6'
31+ db.delete_column(u'maasserver_node', 'gateway_link_ipv6_id')
32+
33+
34+ models = {
35+ u'auth.group': {
36+ 'Meta': {'object_name': 'Group'},
37+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
39+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
40+ },
41+ u'auth.permission': {
42+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
43+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
44+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
45+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
47+ },
48+ u'auth.user': {
49+ 'Meta': {'object_name': 'User'},
50+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
52+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
53+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
54+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
56+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
57+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
58+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
59+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
60+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
61+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
62+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
63+ },
64+ u'contenttypes.contenttype': {
65+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
66+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
67+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
68+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
69+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
70+ },
71+ u'maasserver.blockdevice': {
72+ 'Meta': {'ordering': "[u'id']", 'unique_together': "((u'node', u'name'),)", 'object_name': 'BlockDevice'},
73+ 'block_size': ('django.db.models.fields.IntegerField', [], {}),
74+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
75+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76+ 'id_path': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
77+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
78+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
79+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
80+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
81+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
82+ },
83+ u'maasserver.bootresource': {
84+ 'Meta': {'unique_together': "((u'name', u'architecture'),)", 'object_name': 'BootResource'},
85+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
86+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
87+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
88+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
89+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
90+ 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
91+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
92+ },
93+ u'maasserver.bootresourcefile': {
94+ 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
95+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
96+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
97+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
98+ 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'root-tgz'", 'max_length': '20'}),
99+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100+ 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
101+ 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
102+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
103+ },
104+ u'maasserver.bootresourceset': {
105+ 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
106+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
107+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
109+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
110+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
111+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
112+ },
113+ u'maasserver.bootsource': {
114+ 'Meta': {'object_name': 'BootSource'},
115+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
116+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
117+ 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
118+ 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
119+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
120+ 'url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'})
121+ },
122+ u'maasserver.bootsourcecache': {
123+ 'Meta': {'object_name': 'BootSourceCache'},
124+ 'arch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
125+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
126+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
127+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
128+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
129+ 'os': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
130+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
131+ 'subarch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
132+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
133+ },
134+ u'maasserver.bootsourceselection': {
135+ 'Meta': {'unique_together': "((u'boot_source', u'os', u'release'),)", 'object_name': 'BootSourceSelection'},
136+ 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
137+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
138+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
139+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
141+ 'os': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
142+ 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
143+ 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
144+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
145+ },
146+ u'maasserver.cacheset': {
147+ 'Meta': {'object_name': 'CacheSet'},
148+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
149+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
151+ },
152+ u'maasserver.candidatename': {
153+ 'Meta': {'unique_together': "((u'name', u'position'),)", 'object_name': 'CandidateName'},
154+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
155+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
156+ 'position': ('django.db.models.fields.IntegerField', [], {})
157+ },
158+ u'maasserver.componenterror': {
159+ 'Meta': {'object_name': 'ComponentError'},
160+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
161+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
162+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
163+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
164+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
165+ },
166+ u'maasserver.config': {
167+ 'Meta': {'object_name': 'Config'},
168+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
170+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
171+ },
172+ u'maasserver.downloadprogress': {
173+ 'Meta': {'object_name': 'DownloadProgress'},
174+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
175+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
176+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
177+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
178+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
179+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
180+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
181+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
182+ },
183+ u'maasserver.event': {
184+ 'Meta': {'object_name': 'Event'},
185+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
186+ 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
187+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
188+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
189+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
190+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
191+ },
192+ u'maasserver.eventtype': {
193+ 'Meta': {'object_name': 'EventType'},
194+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
195+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
196+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197+ 'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
198+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
199+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
200+ },
201+ u'maasserver.fabric': {
202+ 'Meta': {'object_name': 'Fabric'},
203+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
204+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
205+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
206+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
207+ },
208+ u'maasserver.filestorage': {
209+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
210+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
211+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
212+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
213+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'4e4b80c2-58a2-11e5-a0e6-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}),
214+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
215+ },
216+ u'maasserver.filesystem': {
217+ 'Meta': {'object_name': 'Filesystem'},
218+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
219+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.CacheSet']"}),
220+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
221+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
222+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.FilesystemGroup']"}),
223+ 'fstype': ('django.db.models.fields.CharField', [], {'default': "u'ext4'", 'max_length': '20'}),
224+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
225+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
226+ 'mount_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
227+ 'mount_point': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
228+ 'partition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Partition']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
229+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
230+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
231+ },
232+ u'maasserver.filesystemgroup': {
233+ 'Meta': {'object_name': 'FilesystemGroup'},
234+ 'cache_mode': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
235+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.CacheSet']", 'null': 'True', 'blank': 'True'}),
236+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
237+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
238+ 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
239+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
240+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
241+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
242+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
243+ },
244+ u'maasserver.interface': {
245+ 'Meta': {'ordering': "(u'created',)", 'object_name': 'Interface'},
246+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
247+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
248+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249+ 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.StaticIPAddress']", 'null': 'True', 'blank': 'True'}),
250+ 'ipv4_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
251+ 'ipv6_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
252+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'null': 'True', 'blank': 'True'}),
253+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
254+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']", 'null': 'True', 'blank': 'True'}),
255+ 'params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
256+ 'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.Interface']", 'null': 'True', 'through': u"orm['maasserver.InterfaceRelationship']", 'blank': 'True'}),
257+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
258+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
259+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
260+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
261+ },
262+ u'maasserver.interfacerelationship': {
263+ 'Meta': {'object_name': 'InterfaceRelationship'},
264+ 'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'parent_relationships'", 'to': u"orm['maasserver.Interface']"}),
265+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
266+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
267+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'children_relationships'", 'to': u"orm['maasserver.Interface']"}),
268+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
269+ },
270+ u'maasserver.largefile': {
271+ 'Meta': {'object_name': 'LargeFile'},
272+ 'content': ('maasserver.fields.LargeObjectField', [], {}),
273+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
274+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275+ 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
276+ 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
277+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
278+ },
279+ u'maasserver.licensekey': {
280+ 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
281+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
282+ 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
283+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
284+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
285+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
286+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
287+ },
288+ u'maasserver.node': {
289+ 'Meta': {'object_name': 'Node'},
290+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
291+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
292+ 'bios_boot_method': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
293+ 'boot_cluster_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
294+ 'boot_disk': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['maasserver.PhysicalBlockDevice']", 'blank': 'True', 'null': 'True'}),
295+ 'boot_interface': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['maasserver.Interface']", 'blank': 'True', 'null': 'True'}),
296+ 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
297+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
298+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
299+ 'disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
300+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
301+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
302+ 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
303+ 'gateway_link_ipv4': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['maasserver.StaticIPAddress']", 'blank': 'True', 'null': 'True'}),
304+ 'gateway_link_ipv6': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['maasserver.StaticIPAddress']", 'blank': 'True', 'null': 'True'}),
305+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
306+ 'hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
307+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
308+ 'installable': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
309+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
310+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
311+ 'min_hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
312+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
313+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
314+ 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
315+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
316+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "u'children'", 'null': 'True', 'blank': 'True', 'to': u"orm['maasserver.Node']"}),
317+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'max_length': '32768', 'blank': 'True'}),
318+ 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
319+ 'power_state_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
320+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
321+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
322+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
323+ 'swap_size': ('django.db.models.fields.BigIntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
324+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-4e4cd59e-58a2-11e5-a0e6-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}),
325+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
326+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
327+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
328+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
329+ },
330+ u'maasserver.nodegroup': {
331+ 'Meta': {'object_name': 'NodeGroup'},
332+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
333+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
334+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
335+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
336+ 'default_disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
337+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
338+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
339+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
340+ 'name': ('maasserver.models.nodegroup.DomainNameField', [], {'max_length': '80', 'blank': 'True'}),
341+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
342+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
343+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
344+ },
345+ u'maasserver.nodegroupinterface': {
346+ 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
347+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
348+ 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
349+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
350+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
351+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
352+ 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
353+ 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
354+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
355+ 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
356+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
357+ 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
358+ 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
359+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}),
360+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
361+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
362+ },
363+ u'maasserver.partition': {
364+ 'Meta': {'object_name': 'Partition'},
365+ 'bootable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
366+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
367+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
368+ 'partition_table': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'partitions'", 'to': u"orm['maasserver.PartitionTable']"}),
369+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
370+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
371+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'unique': 'True', 'null': 'True', 'blank': 'True'})
372+ },
373+ u'maasserver.partitiontable': {
374+ 'Meta': {'object_name': 'PartitionTable'},
375+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']"}),
376+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
377+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
378+ 'table_type': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '20'}),
379+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
380+ },
381+ u'maasserver.physicalblockdevice': {
382+ 'Meta': {'ordering': "[u'id']", 'object_name': 'PhysicalBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
383+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
384+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
385+ 'serial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
386+ },
387+ u'maasserver.space': {
388+ 'Meta': {'object_name': 'Space'},
389+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
390+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
391+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
392+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
393+ },
394+ u'maasserver.sshkey': {
395+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
396+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
397+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
398+ 'key': ('django.db.models.fields.TextField', [], {}),
399+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
400+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
401+ },
402+ u'maasserver.sslkey': {
403+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
404+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
405+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
406+ 'key': ('django.db.models.fields.TextField', [], {}),
407+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
408+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
409+ },
410+ u'maasserver.staticipaddress': {
411+ 'Meta': {'object_name': 'StaticIPAddress'},
412+ 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
413+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
414+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
415+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
416+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
417+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'blank': 'True'}),
418+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
419+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
420+ },
421+ u'maasserver.subnet': {
422+ 'Meta': {'unique_together': "((u'name', u'space'),)", 'object_name': 'Subnet'},
423+ 'cidr': ('maasserver.fields.CIDRField', [], {'unique': 'True'}),
424+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
425+ 'dns_servers': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
426+ 'gateway_ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
427+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
428+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
429+ 'space': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Space']", 'on_delete': 'models.PROTECT'}),
430+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
431+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
432+ },
433+ u'maasserver.tag': {
434+ 'Meta': {'object_name': 'Tag'},
435+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
436+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
437+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
438+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
439+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
440+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
441+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
442+ },
443+ u'maasserver.userprofile': {
444+ 'Meta': {'object_name': 'UserProfile'},
445+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
446+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
447+ },
448+ u'maasserver.virtualblockdevice': {
449+ 'Meta': {'ordering': "[u'id']", 'object_name': 'VirtualBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
450+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
451+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'virtual_devices'", 'to': u"orm['maasserver.FilesystemGroup']"}),
452+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
453+ },
454+ u'maasserver.vlan': {
455+ 'Meta': {'unique_together': "((u'vid', u'fabric'), (u'name', u'fabric'))", 'object_name': 'VLAN'},
456+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
457+ 'fabric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Fabric']"}),
458+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
459+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
460+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
461+ 'vid': ('django.db.models.fields.IntegerField', [], {})
462+ },
463+ u'maasserver.zone': {
464+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
465+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
466+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
467+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
468+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
469+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
470+ },
471+ u'piston.consumer': {
472+ 'Meta': {'object_name': 'Consumer'},
473+ 'description': ('django.db.models.fields.TextField', [], {}),
474+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
475+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
476+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
477+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
478+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
479+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
480+ },
481+ u'piston.token': {
482+ 'Meta': {'object_name': 'Token'},
483+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
484+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
485+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
486+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
487+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
488+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
489+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
490+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1441988982L'}),
491+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
492+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
493+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
494+ }
495+ }
496+
497+ complete_apps = ['maasserver']
498\ No newline at end of file
499
500=== modified file 'src/maasserver/models/interface.py'
501--- src/maasserver/models/interface.py 2015-09-11 19:32:26 +0000
502+++ src/maasserver/models/interface.py 2015-09-11 19:32:41 +0000
503@@ -1077,6 +1077,29 @@
504 models.signals.post_save.connect(resave_children_interface_handler)
505
506
507+def remove_gateway_link_when_ip_address_removed_from_interface(
508+ sender, instance, action, model, pk_set, **kwargs):
509+ """When an IP address is removed from an interface it is possible that
510+ the IP address was not deleted just moved. In that case we need to removed
511+ the gateway links on the node model."""
512+ if (type(instance) in ALL_INTERFACE_TYPES and
513+ model == StaticIPAddress and
514+ instance.node is not None and
515+ action == "post_remove"):
516+ node = instance.node
517+ for pk in pk_set:
518+ if node.gateway_link_ipv4_id == pk:
519+ node.gateway_link_ipv4_id = None
520+ node.save(update_fields=["gateway_link_ipv4_id"])
521+ if node.gateway_link_ipv6_id == pk:
522+ node.gateway_link_ipv6_id = None
523+ node.save(update_fields=["gateway_link_ipv6_id"])
524+
525+
526+models.signals.m2m_changed.connect(
527+ remove_gateway_link_when_ip_address_removed_from_interface)
528+
529+
530 class PhysicalInterface(Interface):
531
532 class Meta(Interface.Meta):
533
534=== modified file 'src/maasserver/models/node.py'
535--- src/maasserver/models/node.py 2015-09-11 19:32:26 +0000
536+++ src/maasserver/models/node.py 2015-09-11 19:32:41 +0000
537@@ -60,6 +60,7 @@
538 from maasserver.enum import (
539 INTERFACE_LINK_TYPE,
540 INTERFACE_TYPE,
541+ IPADDRESS_FAMILY,
542 IPADDRESS_TYPE,
543 NODE_BOOT,
544 NODE_BOOT_CHOICES,
545@@ -546,6 +547,18 @@
546 PhysicalBlockDevice, default=None, blank=True, null=True,
547 editable=False, related_name='+', on_delete=SET_NULL)
548
549+ # Default IPv4 subnet link on an interface for this node. This is used to
550+ # define the default IPv4 route the node should use.
551+ gateway_link_ipv4 = ForeignKey(
552+ StaticIPAddress, default=None, blank=True, null=True,
553+ editable=False, related_name='+', on_delete=SET_NULL)
554+
555+ # Default IPv6 subnet link on an interface for this node. This is used to
556+ # define the default IPv6 route the node should use.
557+ gateway_link_ipv6 = ForeignKey(
558+ StaticIPAddress, default=None, blank=True, null=True,
559+ editable=False, related_name='+', on_delete=SET_NULL)
560+
561 # Note that the ordering of the managers is meaningul. More precisely, the
562 # first manager defined is important: see
563 # https://docs.djangoproject.com/en/1.7/topics/db/managers/ ("Default
564@@ -1923,6 +1936,66 @@
565 for found in cursor.fetchall()
566 ]
567
568+ def _get_best_interface_from_gateway_link(self, gateway_link):
569+ """Return the best interface for the `gateway_link` and this node."""
570+ return gateway_link.interface_set.filter(
571+ node=self).order_by('type', 'id').first().id
572+
573+ def _get_gateway_tuple(self, gateway_link):
574+ """Return a tuple for the interface id, subnet id, and gateway IP for
575+ the `gateway_link`."""
576+ return (
577+ self._get_best_interface_from_gateway_link(
578+ gateway_link),
579+ gateway_link.subnet.id,
580+ gateway_link.subnet.gateway_ip,
581+ )
582+
583+ def _get_gateway_tuple_by_family(self, gateways, ip_family):
584+ """Return the gateway tuple from `gateways` that is in the IP address
585+ family."""
586+ for gateway in gateways:
587+ if IPAddress(gateway[2]).version == ip_family:
588+ return gateway
589+ return None
590+
591+ def get_default_gateway_ips(self):
592+ """Return the default gateway IP addresses.
593+
594+ :return: Return a tuple or tuples with IPv4 and IPv6 gateway
595+ information.
596+ :rtype: tuple
597+ """
598+ # Get the set gateways on the node.
599+ gateway_ipv4 = None
600+ gateway_ipv6 = None
601+ if self.gateway_link_ipv4 is not None:
602+ subnet = self.gateway_link_ipv4.subnet
603+ if subnet is not None:
604+ if subnet.gateway_ip:
605+ gateway_ipv4 = self._get_gateway_tuple(
606+ self.gateway_link_ipv4)
607+ if self.gateway_link_ipv6 is not None:
608+ subnet = self.gateway_link_ipv6.subnet
609+ if subnet is not None:
610+ if subnet.gateway_ip:
611+ gateway_ipv6 = self._get_gateway_tuple(
612+ self.gateway_link_ipv6)
613+
614+ # Early out if we already have both gateways.
615+ if gateway_ipv4 and gateway_ipv6:
616+ return (gateway_ipv4, gateway_ipv6)
617+
618+ # Get the best guesses for the missing IP families.
619+ found_gateways = self.get_best_guess_for_default_gateway_ips()
620+ if not gateway_ipv4:
621+ gateway_ipv4 = self._get_gateway_tuple_by_family(
622+ found_gateways, IPADDRESS_FAMILY.IPv4)
623+ if not gateway_ipv6:
624+ gateway_ipv6 = self._get_gateway_tuple_by_family(
625+ found_gateways, IPADDRESS_FAMILY.IPv6)
626+ return (gateway_ipv4, gateway_ipv6)
627+
628 def get_boot_purpose(self):
629 """
630 Return a suitable "purpose" for this boot, e.g. "install".
631
632=== modified file 'src/maasserver/models/tests/test_interface.py'
633--- src/maasserver/models/tests/test_interface.py 2015-09-11 19:32:26 +0000
634+++ src/maasserver/models/tests/test_interface.py 2015-09-11 19:32:41 +0000
635@@ -260,6 +260,36 @@
636 nodegroup: {ip}
637 }))
638
639+ def test_remove_gateway_link_on_node_ipv4(self):
640+ node = factory.make_Node()
641+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
642+ network = factory.make_ipv4_network()
643+ subnet = factory.make_Subnet(cidr=unicode(network.cidr))
644+ ip = factory.make_StaticIPAddress(
645+ alloc_type=IPADDRESS_TYPE.STICKY,
646+ ip=factory.pick_ip_in_network(network),
647+ subnet=subnet, interface=interface)
648+ node.gateway_link_ipv4 = ip
649+ node.save()
650+ reload_object(interface).ip_addresses.remove(ip)
651+ node = reload_object(node)
652+ self.assertIsNone(node.gateway_link_ipv4)
653+
654+ def test_remove_gateway_link_on_node_ipv6(self):
655+ node = factory.make_Node()
656+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
657+ network = factory.make_ipv6_network()
658+ subnet = factory.make_Subnet(cidr=unicode(network.cidr))
659+ ip = factory.make_StaticIPAddress(
660+ alloc_type=IPADDRESS_TYPE.STICKY,
661+ ip=factory.pick_ip_in_network(network),
662+ subnet=subnet, interface=interface)
663+ node.gateway_link_ipv6 = ip
664+ node.save()
665+ reload_object(interface).ip_addresses.remove(ip)
666+ node = reload_object(node)
667+ self.assertIsNone(node.gateway_link_ipv6)
668+
669
670 class PhysicalInterfaceTest(MAASServerTestCase):
671
672
673=== modified file 'src/maasserver/models/tests/test_node.py'
674--- src/maasserver/models/tests/test_node.py 2015-09-11 19:32:26 +0000
675+++ src/maasserver/models/tests/test_node.py 2015-09-11 19:32:41 +0000
676@@ -2836,6 +2836,141 @@
677 node.get_best_guess_for_default_gateway_ips())
678
679
680+class TestGetDefaultGatewayIP(MAASServerTestCase):
681+ """Tests for `Node.get_default_gateway_ips`."""
682+
683+ def test__return_set_ipv4_and_ipv6(self):
684+ nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
685+ node = factory.make_Node(
686+ status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False)
687+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
688+ network_v4 = factory.make_ipv4_network()
689+ subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr))
690+ network_v4_2 = factory.make_ipv4_network()
691+ subnet_v4_2 = factory.make_Subnet(cidr=unicode(network_v4_2.cidr))
692+ factory.make_NodeGroupInterface(
693+ nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
694+ subnet=subnet_v4_2)
695+ network_v6 = factory.make_ipv6_network()
696+ subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr))
697+ network_v6_2 = factory.make_ipv6_network()
698+ subnet_v6_2 = factory.make_Subnet(cidr=unicode(network_v6_2.cidr))
699+ factory.make_NodeGroupInterface(
700+ nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
701+ subnet=subnet_v6_2)
702+ factory.make_StaticIPAddress(
703+ alloc_type=IPADDRESS_TYPE.STICKY,
704+ ip=factory.pick_ip_in_network(network_v4),
705+ subnet=subnet_v4, interface=interface)
706+ link_v4 = factory.make_StaticIPAddress(
707+ alloc_type=IPADDRESS_TYPE.STICKY,
708+ ip=factory.pick_ip_in_network(network_v4_2),
709+ subnet=subnet_v4_2, interface=interface)
710+ factory.make_StaticIPAddress(
711+ alloc_type=IPADDRESS_TYPE.STICKY,
712+ ip=factory.pick_ip_in_network(network_v6),
713+ subnet=subnet_v6, interface=interface)
714+ link_v6 = factory.make_StaticIPAddress(
715+ alloc_type=IPADDRESS_TYPE.STICKY,
716+ ip=factory.pick_ip_in_network(network_v6_2),
717+ subnet=subnet_v6_2, interface=interface)
718+ node.gateway_link_ipv4 = link_v4
719+ node.gateway_link_ipv6 = link_v6
720+ node.save()
721+ self.assertEquals((
722+ (interface.id, subnet_v4_2.id, subnet_v4_2.gateway_ip),
723+ (interface.id, subnet_v6_2.id, subnet_v6_2.gateway_ip),
724+ ), node.get_default_gateway_ips())
725+
726+ def test__return_set_ipv4_and_guess_ipv6(self):
727+ nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
728+ node = factory.make_Node(
729+ status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False)
730+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
731+ network_v4 = factory.make_ipv4_network()
732+ subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr))
733+ network_v4_2 = factory.make_ipv4_network()
734+ subnet_v4_2 = factory.make_Subnet(cidr=unicode(network_v4_2.cidr))
735+ factory.make_NodeGroupInterface(
736+ nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
737+ subnet=subnet_v4_2)
738+ network_v6 = factory.make_ipv6_network()
739+ subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr))
740+ factory.make_StaticIPAddress(
741+ alloc_type=IPADDRESS_TYPE.STICKY,
742+ ip=factory.pick_ip_in_network(network_v4),
743+ subnet=subnet_v4, interface=interface)
744+ link_v4 = factory.make_StaticIPAddress(
745+ alloc_type=IPADDRESS_TYPE.STICKY,
746+ ip=factory.pick_ip_in_network(network_v4_2),
747+ subnet=subnet_v4_2, interface=interface)
748+ factory.make_StaticIPAddress(
749+ alloc_type=IPADDRESS_TYPE.STICKY,
750+ ip=factory.pick_ip_in_network(network_v6),
751+ subnet=subnet_v6, interface=interface)
752+ node.gateway_link_ipv4 = link_v4
753+ node.save()
754+ self.assertEquals((
755+ (interface.id, subnet_v4_2.id, subnet_v4_2.gateway_ip),
756+ (interface.id, subnet_v6.id, subnet_v6.gateway_ip),
757+ ), node.get_default_gateway_ips())
758+
759+ def test__return_set_ipv6_and_guess_ipv4(self):
760+ nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
761+ node = factory.make_Node(
762+ status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False)
763+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
764+ network_v4 = factory.make_ipv4_network()
765+ subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr))
766+ network_v6 = factory.make_ipv6_network()
767+ subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr))
768+ network_v6_2 = factory.make_ipv6_network()
769+ subnet_v6_2 = factory.make_Subnet(cidr=unicode(network_v6_2.cidr))
770+ factory.make_NodeGroupInterface(
771+ nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
772+ subnet=subnet_v6_2)
773+ factory.make_StaticIPAddress(
774+ alloc_type=IPADDRESS_TYPE.STICKY,
775+ ip=factory.pick_ip_in_network(network_v4),
776+ subnet=subnet_v4, interface=interface)
777+ factory.make_StaticIPAddress(
778+ alloc_type=IPADDRESS_TYPE.STICKY,
779+ ip=factory.pick_ip_in_network(network_v6),
780+ subnet=subnet_v6, interface=interface)
781+ link_v6 = factory.make_StaticIPAddress(
782+ alloc_type=IPADDRESS_TYPE.STICKY,
783+ ip=factory.pick_ip_in_network(network_v6_2),
784+ subnet=subnet_v6_2, interface=interface)
785+ node.gateway_link_ipv6 = link_v6
786+ node.save()
787+ self.assertEquals((
788+ (interface.id, subnet_v4.id, subnet_v4.gateway_ip),
789+ (interface.id, subnet_v6_2.id, subnet_v6_2.gateway_ip),
790+ ), node.get_default_gateway_ips())
791+
792+ def test__return_guess_ipv4_and_ipv6(self):
793+ nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
794+ node = factory.make_Node(
795+ status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False)
796+ interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
797+ network_v4 = factory.make_ipv4_network()
798+ subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr))
799+ network_v6 = factory.make_ipv6_network()
800+ subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr))
801+ factory.make_StaticIPAddress(
802+ alloc_type=IPADDRESS_TYPE.STICKY,
803+ ip=factory.pick_ip_in_network(network_v4),
804+ subnet=subnet_v4, interface=interface)
805+ factory.make_StaticIPAddress(
806+ alloc_type=IPADDRESS_TYPE.STICKY,
807+ ip=factory.pick_ip_in_network(network_v6),
808+ subnet=subnet_v6, interface=interface)
809+ self.assertEquals((
810+ (interface.id, subnet_v4.id, subnet_v4.gateway_ip),
811+ (interface.id, subnet_v6.id, subnet_v6.gateway_ip),
812+ ), node.get_default_gateway_ips())
813+
814+
815 class TestDeploymentStatus(MAASServerTestCase):
816 """Tests for node.get_deployment_status."""
817

Subscribers

People subscribed via source and target branches