Merge lp:~julian-edwards/maas/models-for-static-dhcp 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: 2390
Proposed branch: lp:~julian-edwards/maas/models-for-static-dhcp
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 527 lines (+454/-2)
7 files modified
src/maasserver/enum.py (+16/-0)
src/maasserver/migrations/0081_ipaddress_table_and_static_dhcp_ranges.py (+326/-0)
src/maasserver/models/__init__.py (+5/-2)
src/maasserver/models/ipaddress.py (+53/-0)
src/maasserver/models/macaddress.py (+6/-0)
src/maasserver/models/macipaddresslink.py (+44/-0)
src/maasserver/models/nodegroupinterface.py (+4/-0)
To merge this branch: bzr merge lp:~julian-edwards/maas/models-for-static-dhcp
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+221155@code.launchpad.net

Commit message

New model "IPAddress" and static IP range fields on nodegroupinterface. This is to prepare the way for a static host dhcpd declaration that does not intersect with the dynamic range.

Description of the change

See https://docs.google.com/a/canonical.com/document/d/1JHbXCgkA0b42uu2t_uKLBnlxCzpaNmaQOPvPpGrDHDA/edit#

Essentially, this is just introducing a new table to store static ip addresses, but is done in such a way to support the future changes described in that doc that we also need.

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

I want to make more changes to this so I've marked it WIP.

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

I've made the IPAddress.mac_address nullable in the case where the IP is not for a Node.

Additionally, we should note that there is no sensible migration you can do to create a static range for DHCP, it requires admin intervention. The simplest thing to do is if there is no static range defined, then continue as before with only dynamic leases. But this will come later in some actual code!

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Please document IPAddress. It's really a static IP address, not just any dynamic-or-static IP address, right?

Why the foreign key from IPAddress to MACAddress in the first place? Wasn't that going to be a many-to-many relationship? Or is this transitional?

I'm not holding this up with my vote — whatever comes out, no harm done at this stage. Better to land and improve than to fork and languish. But to me, the "type" field affects the behaviour of the foreign key in too profound a way.

The biggest difference I see between the four types of relationship is what happens when a node is deallocated. If an IPAddress is "auto" or "extra," it goes down with the ship. If it's "unmanaged" or "sticky," it survives. I think those are different structural relationships, so maybe they should be two alternative foreign keys, with different cascading behaviours.

Of course my "biggest difference" only gets us to 2 types, out of 4. What are the other differences? It seems to me that:

 * "Auto" is like "extra" except it can't be deallocated separately. Could be a separate relationship, or just a boolean attribute.

 * "Unmanaged" is like "sticky" except the machine on the other end is not a node. But does that matter beyond creation and display? If you set an IP address for a machine's MAC address, then registered that machine as a node, ISTM the IP address would implicitly go from "unmanaged" to "sticky" without affecting the world in any way.

There may also be cardinality differences. I can't imagine attaching two MACs to the same "auto" address, but it might make sense for "extra" addresses. Splitting out the relationships may make the rules clearer, and easier to enforce as constraints.

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

Thanks for reviewing jtv.

On 01/06/14 23:14, Jeroen T. Vermeulen wrote:
> Review: Approve
>
> Please document IPAddress. It's really a static IP address, not just any dynamic-or-static IP address, right?

Woops, I had meant to do that and forgot. I've expanded the docstring
now, thanks for spotting!

> Why the foreign key from IPAddress to MACAddress in the first place? Wasn't that going to be a many-to-many relationship? Or is this transitional?

See the maas-devel thread for gory details, but the upshot is that we
potentially need to have many IPs per MAC (VLANs, NIC aliases etc), but
we don't need to model many MACs per IP (VIPs) as we'll do that by
having a per-user IP.

> I'm not holding this up with my vote — whatever comes out, no harm done at this stage. Better to land and improve than to fork and languish. But to me, the "type" field affects the behaviour of the foreign key in too profound a way.
>
> The biggest difference I see between the four types of relationship is what happens when a node is deallocated. If an IPAddress is "auto" or "extra," it goes down with the ship. If it's "unmanaged" or "sticky," it survives. I think those are different structural relationships, so maybe they should be two alternative foreign keys, with different cascading behaviours.

Again please see the maas-devel thread for politics behind this :(

> Of course my "biggest difference" only gets us to 2 types, out of 4. What are the other differences? It seems to me that:
>
> * "Auto" is like "extra" except it can't be deallocated separately. Could be a separate relationship, or just a boolean attribute.

Whenever you have a boolean attribute, an enum is nearly always what you
want instead, and better. Bools as column types are for mugs IMO, they
restrict what you can do, and I've seen many times where the LP schema
had migrations to move bools to enums as the original design had not
been thought through properly.

> * "Unmanaged" is like "sticky" except the machine on the other end is not a node. But does that matter beyond creation and display? If you set an IP address for a machine's MAC address, then registered that machine as a node, ISTM the IP address would implicitly go from "unmanaged" to "sticky" without affecting the world in any way.

You might have a point here. Let's leave it for now since it doesn't
affect the model itself and is a small implementation detail. We can
always change it later very easily and we'll encapsulate all this detail
inside the manager class anyway.

> There may also be cardinality differences. I can't imagine attaching two MACs to the same "auto" address, but it might make sense for "extra" addresses. Splitting out the relationships may make the rules clearer, and easier to enforce as constraints.

There is no way to attach more than one MAC to an IP with this model.

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

I've now changed this a bit to use a link table between MACAddress and
IPAddress and put MACAddress.ip_addresses as a ManyToMany field. It
will still be one-to-many rather than many-to-many because I made
IPAddress on the link table unique.

This brings some benefits:

 A) no more nullable FK on IPAddress
 B) we can put metadata on the link table (which I have done, I've added
nic_alias. See the discussion on maas-devel)
 C) we get a more natural MACAddress.ip_addresses field. Referring to
IPAddress.macaddress_set will always only give one result, though.

I'm going to land this because I don't want to be blocked and it's only
slightly different really. We can revisit if necessary of course.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/enum.py'
--- src/maasserver/enum.py 2014-05-01 20:18:14 +0000
+++ src/maasserver/enum.py 2014-06-02 05:21:22 +0000
@@ -14,6 +14,7 @@
14__metaclass__ = type14__metaclass__ = type
15__all__ = [15__all__ = [
16 'COMPONENT',16 'COMPONENT',
17 'IPADDRESS_TYPE',
17 'NODEGROUP_STATUS',18 'NODEGROUP_STATUS',
18 'NODEGROUP_STATUS_CHOICES',19 'NODEGROUP_STATUS_CHOICES',
19 'NODEGROUPINTERFACE_MANAGEMENT',20 'NODEGROUPINTERFACE_MANAGEMENT',
@@ -150,3 +151,18 @@
150151
151NODEGROUPINTERFACE_MANAGEMENT_CHOICES_DICT = (152NODEGROUPINTERFACE_MANAGEMENT_CHOICES_DICT = (
152 OrderedDict(NODEGROUPINTERFACE_MANAGEMENT_CHOICES))153 OrderedDict(NODEGROUPINTERFACE_MANAGEMENT_CHOICES))
154
155
156class IPADDRESS_TYPE:
157 """The vocabulary of possible types of `IPAddress`."""
158 # Automatically assigned.
159 AUTO = 0
160
161 # Pre-assigned and permanent until removed.
162 STICKY = 1
163
164 # Not associated to hardware managed by MAAS.
165 UNMANAGED = 2
166
167 # Additional IP requested by a user for a node.
168 EXTRA = 3
153169
=== added file 'src/maasserver/migrations/0081_ipaddress_table_and_static_dhcp_ranges.py'
--- src/maasserver/migrations/0081_ipaddress_table_and_static_dhcp_ranges.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0081_ipaddress_table_and_static_dhcp_ranges.py 2014-06-02 05:21:22 +0000
@@ -0,0 +1,326 @@
1from django.db import models
2from south.db import db
3# -*- coding: utf-8 -*-
4from south.utils import datetime_utils as datetime
5from south.v2 import SchemaMigration
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding model 'IPAddress'
12 db.create_table(u'maasserver_ipaddress', (
13 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('created', self.gf('django.db.models.fields.DateTimeField')()),
15 ('updated', self.gf('django.db.models.fields.DateTimeField')()),
16 ('ip', self.gf('django.db.models.fields.GenericIPAddressField')(unique=True, max_length=39)),
17 ('type', self.gf('django.db.models.fields.IntegerField')(default=0)),
18 ))
19 db.send_create_signal(u'maasserver', ['IPAddress'])
20
21 # Adding model 'MACIPAddressLink'
22 db.create_table(u'maasserver_macipaddresslink', (
23 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
24 ('created', self.gf('django.db.models.fields.DateTimeField')()),
25 ('updated', self.gf('django.db.models.fields.DateTimeField')()),
26 ('mac_address', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['maasserver.MACAddress'])),
27 ('ip_address', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['maasserver.IPAddress'], unique=True)),
28 ('nic_alias', self.gf('django.db.models.fields.IntegerField')(default=None, null=True, blank=True)),
29 ))
30 db.send_create_signal(u'maasserver', ['MACIPAddressLink'])
31
32 # Adding unique constraint on 'MACIPAddressLink', fields ['ip_address', 'mac_address']
33 db.create_unique(u'maasserver_macipaddresslink', ['ip_address_id', 'mac_address_id'])
34
35 # Adding field 'NodeGroupInterface.static_ip_range_low'
36 db.add_column(u'maasserver_nodegroupinterface', 'static_ip_range_low',
37 self.gf('django.db.models.fields.GenericIPAddressField')(default=None, max_length=39, null=True, blank=True),
38 keep_default=False)
39
40 # Adding field 'NodeGroupInterface.static_ip_range_high'
41 db.add_column(u'maasserver_nodegroupinterface', 'static_ip_range_high',
42 self.gf('django.db.models.fields.GenericIPAddressField')(default=None, max_length=39, null=True, blank=True),
43 keep_default=False)
44
45
46 def backwards(self, orm):
47 # Removing unique constraint on 'MACIPAddressLink', fields ['ip_address', 'mac_address']
48 db.delete_unique(u'maasserver_macipaddresslink', ['ip_address_id', 'mac_address_id'])
49
50 # Deleting model 'IPAddress'
51 db.delete_table(u'maasserver_ipaddress')
52
53 # Deleting model 'MACIPAddressLink'
54 db.delete_table(u'maasserver_macipaddresslink')
55
56 # Deleting field 'NodeGroupInterface.static_ip_range_low'
57 db.delete_column(u'maasserver_nodegroupinterface', 'static_ip_range_low')
58
59 # Deleting field 'NodeGroupInterface.static_ip_range_high'
60 db.delete_column(u'maasserver_nodegroupinterface', 'static_ip_range_high')
61
62
63 models = {
64 u'auth.group': {
65 'Meta': {'object_name': 'Group'},
66 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
68 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
69 },
70 u'auth.permission': {
71 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
72 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
73 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
74 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
76 },
77 u'auth.user': {
78 'Meta': {'object_name': 'User'},
79 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
80 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
81 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
82 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
84 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
85 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
86 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
87 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
88 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
89 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
90 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
91 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
92 },
93 u'contenttypes.contenttype': {
94 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
95 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
96 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
97 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
98 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
99 },
100 u'maasserver.bootimage': {
101 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
102 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
103 'created': ('django.db.models.fields.DateTimeField', [], {}),
104 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
105 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
106 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
107 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
108 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
109 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
110 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
111 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
112 'updated': ('django.db.models.fields.DateTimeField', [], {})
113 },
114 u'maasserver.bootsource': {
115 'Meta': {'object_name': 'BootSource'},
116 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
117 'created': ('django.db.models.fields.DateTimeField', [], {}),
118 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
120 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
121 'updated': ('django.db.models.fields.DateTimeField', [], {}),
122 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
123 },
124 u'maasserver.bootsourceselection': {
125 'Meta': {'object_name': 'BootSourceSelection'},
126 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
127 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
128 'created': ('django.db.models.fields.DateTimeField', [], {}),
129 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
131 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
132 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
133 'updated': ('django.db.models.fields.DateTimeField', [], {})
134 },
135 u'maasserver.componenterror': {
136 'Meta': {'object_name': 'ComponentError'},
137 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
138 'created': ('django.db.models.fields.DateTimeField', [], {}),
139 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
140 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
141 'updated': ('django.db.models.fields.DateTimeField', [], {})
142 },
143 u'maasserver.config': {
144 'Meta': {'object_name': 'Config'},
145 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
147 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
148 },
149 u'maasserver.dhcplease': {
150 'Meta': {'object_name': 'DHCPLease'},
151 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
152 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
153 'mac': ('maasserver.fields.MACAddressField', [], {}),
154 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
155 },
156 u'maasserver.downloadprogress': {
157 'Meta': {'object_name': 'DownloadProgress'},
158 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
159 'created': ('django.db.models.fields.DateTimeField', [], {}),
160 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
161 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
162 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
163 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
164 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
165 'updated': ('django.db.models.fields.DateTimeField', [], {})
166 },
167 u'maasserver.filestorage': {
168 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
169 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
170 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
171 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172 'key': ('django.db.models.fields.CharField', [], {'default': "u'23d4ee34-ea01-11e3-8dfe-002215205ce8'", 'unique': 'True', 'max_length': '36'}),
173 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
174 },
175 u'maasserver.ipaddress': {
176 'Meta': {'object_name': 'IPAddress'},
177 'created': ('django.db.models.fields.DateTimeField', [], {}),
178 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
179 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
180 'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
181 'updated': ('django.db.models.fields.DateTimeField', [], {})
182 },
183 u'maasserver.macaddress': {
184 'Meta': {'object_name': 'MACAddress'},
185 'created': ('django.db.models.fields.DateTimeField', [], {}),
186 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
187 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.IPAddress']", 'through': u"orm['maasserver.MACIPAddressLink']", 'symmetrical': 'False'}),
188 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
189 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
190 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
191 'updated': ('django.db.models.fields.DateTimeField', [], {})
192 },
193 u'maasserver.macipaddresslink': {
194 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACIPAddressLink'},
195 'created': ('django.db.models.fields.DateTimeField', [], {}),
196 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.IPAddress']", 'unique': 'True'}),
198 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}),
199 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
200 'updated': ('django.db.models.fields.DateTimeField', [], {})
201 },
202 u'maasserver.network': {
203 'Meta': {'object_name': 'Network'},
204 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
205 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
206 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
207 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
208 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
209 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
210 },
211 u'maasserver.node': {
212 'Meta': {'object_name': 'Node'},
213 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
214 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
215 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
216 'created': ('django.db.models.fields.DateTimeField', [], {}),
217 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
218 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
219 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
220 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
221 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
222 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
223 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
224 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
225 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
226 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
227 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
228 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
229 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
230 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
231 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-23d247e2-ea01-11e3-8dfe-002215205ce8'", 'unique': 'True', 'max_length': '41'}),
232 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
233 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
234 'updated': ('django.db.models.fields.DateTimeField', [], {}),
235 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
236 },
237 u'maasserver.nodegroup': {
238 'Meta': {'object_name': 'NodeGroup'},
239 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
240 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
241 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
242 'created': ('django.db.models.fields.DateTimeField', [], {}),
243 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
244 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
245 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
246 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
247 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
248 'updated': ('django.db.models.fields.DateTimeField', [], {}),
249 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
250 },
251 u'maasserver.nodegroupinterface': {
252 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
253 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
254 'created': ('django.db.models.fields.DateTimeField', [], {}),
255 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
256 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
257 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
258 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
259 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
260 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
261 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
262 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
263 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
264 'static_ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
265 'static_ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
266 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
267 'updated': ('django.db.models.fields.DateTimeField', [], {})
268 },
269 u'maasserver.sshkey': {
270 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
271 'created': ('django.db.models.fields.DateTimeField', [], {}),
272 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
273 'key': ('django.db.models.fields.TextField', [], {}),
274 'updated': ('django.db.models.fields.DateTimeField', [], {}),
275 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
276 },
277 u'maasserver.tag': {
278 'Meta': {'object_name': 'Tag'},
279 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
280 'created': ('django.db.models.fields.DateTimeField', [], {}),
281 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
282 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
283 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
284 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
285 'updated': ('django.db.models.fields.DateTimeField', [], {})
286 },
287 u'maasserver.userprofile': {
288 'Meta': {'object_name': 'UserProfile'},
289 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
290 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
291 },
292 u'maasserver.zone': {
293 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
294 'created': ('django.db.models.fields.DateTimeField', [], {}),
295 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
296 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
297 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
298 'updated': ('django.db.models.fields.DateTimeField', [], {})
299 },
300 u'piston.consumer': {
301 'Meta': {'object_name': 'Consumer'},
302 'description': ('django.db.models.fields.TextField', [], {}),
303 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
304 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
305 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
306 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
307 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
308 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
309 },
310 u'piston.token': {
311 'Meta': {'object_name': 'Token'},
312 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
313 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
314 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
315 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
316 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
317 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
318 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
319 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1401677636L'}),
320 'token_type': ('django.db.models.fields.IntegerField', [], {}),
321 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
322 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
323 }
324 }
325
326 complete_apps = ['maasserver']
0\ No newline at end of file327\ No newline at end of file
1328
=== modified file 'src/maasserver/models/__init__.py'
--- src/maasserver/models/__init__.py 2014-05-16 09:00:55 +0000
+++ src/maasserver/models/__init__.py 2014-06-02 05:21:22 +0000
@@ -51,7 +51,9 @@
51from maasserver.models.dhcplease import DHCPLease51from maasserver.models.dhcplease import DHCPLease
52from maasserver.models.downloadprogress import DownloadProgress52from maasserver.models.downloadprogress import DownloadProgress
53from maasserver.models.filestorage import FileStorage53from maasserver.models.filestorage import FileStorage
54from maasserver.models.ipaddress import IPAddress
54from maasserver.models.macaddress import MACAddress55from maasserver.models.macaddress import MACAddress
56from maasserver.models.macipaddresslink import MACIPAddressLink
55from maasserver.models.network import Network57from maasserver.models.network import Network
56from maasserver.models.node import Node58from maasserver.models.node import Node
57from maasserver.models.nodegroup import NodeGroup59from maasserver.models.nodegroup import NodeGroup
@@ -68,8 +70,9 @@
68# export in __all__.70# export in __all__.
69ignore_unused(71ignore_unused(
70 BootImage, ComponentError, Config, DHCPLease, DownloadProgress,72 BootImage, ComponentError, Config, DHCPLease, DownloadProgress,
71 FileStorage, MACAddress, Network, NodeGroup, SSHKey, Tag, UserProfile,73 FileStorage, IPAddress, MACAddress, MACIPAddressLink, Network,
72 NodeGroupInterface, Zone, logger)74 NodeGroup, SSHKey, Tag, UserProfile, NodeGroupInterface, Zone,
75 logger)
7376
7477
75# Connect the 'create_user' method to the post save signal of User.78# Connect the 'create_user' method to the post save signal of User.
7679
=== added file 'src/maasserver/models/ipaddress.py'
--- src/maasserver/models/ipaddress.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/ipaddress.py 2014-06-02 05:21:22 +0000
@@ -0,0 +1,53 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Model definition for IPAddress.
5
6Contains all the in-use static IP addresses that are allocated by MAAS.
7Generally speaking, these are written out to the DHCP server as "host"
8blocks which will tie MACs into a specific IP. The IPs are separate
9from the dynamic range that the DHCP server itself allocates to unknown
10clients.
11"""
12
13from __future__ import (
14 absolute_import,
15 print_function,
16 unicode_literals,
17 )
18
19str = None
20
21__metaclass__ = type
22__all__ = [
23 'IPAddress',
24 ]
25
26
27from django.db.models import (
28 GenericIPAddressField,
29 IntegerField,
30 )
31from maasserver import DefaultMeta
32from maasserver.enum import IPADDRESS_TYPE
33from maasserver.models.cleansave import CleanSave
34from maasserver.models.timestampedmodel import TimestampedModel
35
36
37class IPAddress(CleanSave, TimestampedModel):
38
39 class Meta(DefaultMeta):
40 verbose_name = "IP Address"
41 verbose_name_plural = "IP Addresses"
42
43 ip = GenericIPAddressField(
44 unique=True, null=False, editable=False, blank=False)
45
46 # The MACIPAddressLink table is used to link IPAddress to
47 # MACAddress. See MACAddress.ip_addresses.
48
49 type = IntegerField(
50 editable=False, null=False, blank=False, default=IPADDRESS_TYPE.AUTO)
51
52 def __unicode__(self):
53 return "<IPAddress %s>" % self.ip
054
=== modified file 'src/maasserver/models/macaddress.py'
--- src/maasserver/models/macaddress.py 2014-02-27 23:37:18 +0000
+++ src/maasserver/models/macaddress.py 2014-06-02 05:21:22 +0000
@@ -50,6 +50,12 @@
5050
51 networks = ManyToManyField('maasserver.Network', blank=True)51 networks = ManyToManyField('maasserver.Network', blank=True)
5252
53 ip_addresses = ManyToManyField(
54 'maasserver.IPAddress', through='maasserver.MACIPAddressLink',
55 blank=True)
56
57 # future columns: tags, nic_name, metadata, bonding info
58
53 objects = BulkManager()59 objects = BulkManager()
5460
55 class Meta(DefaultMeta):61 class Meta(DefaultMeta):
5662
=== added file 'src/maasserver/models/macipaddresslink.py'
--- src/maasserver/models/macipaddresslink.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/macipaddresslink.py 2014-06-02 05:21:22 +0000
@@ -0,0 +1,44 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Model definition for MACIPAddressLink.
5
6Maintains a relationship between MACAddress and IPAddress. This is defined
7instead of using Django's auto-generated link table because it also contains
8additional metadata about the link, such as a NIC alias.
9"""
10
11from __future__ import (
12 absolute_import,
13 print_function,
14 unicode_literals,
15 )
16
17str = None
18
19__metaclass__ = type
20__all__ = [
21 'MACIPAddressLink',
22 ]
23
24
25from django.db.models import (
26 ForeignKey,
27 IntegerField,
28 )
29from maasserver import DefaultMeta
30from maasserver.models.cleansave import CleanSave
31from maasserver.models.timestampedmodel import TimestampedModel
32
33
34class MACIPAddressLink(CleanSave, TimestampedModel):
35
36 class Meta(DefaultMeta):
37 unique_together = ('ip_address', 'mac_address')
38
39 mac_address = ForeignKey('maasserver.MACAddress')
40 ip_address = ForeignKey('maasserver.IPAddress', unique=True)
41
42 # Optional NIC alias for multi-homed NICs (e.g. 'eth0:1')
43 nic_alias = IntegerField(
44 editable=True, null=True, blank=True, default=None)
045
=== modified file 'src/maasserver/models/nodegroupinterface.py'
--- src/maasserver/models/nodegroupinterface.py 2014-03-31 08:19:12 +0000
+++ src/maasserver/models/nodegroupinterface.py 2014-06-02 05:21:22 +0000
@@ -78,6 +78,10 @@
78 editable=True, unique=False, blank=True, null=True, default=None)78 editable=True, unique=False, blank=True, null=True, default=None)
79 ip_range_high = GenericIPAddressField(79 ip_range_high = GenericIPAddressField(
80 editable=True, unique=False, blank=True, null=True, default=None)80 editable=True, unique=False, blank=True, null=True, default=None)
81 static_ip_range_low = GenericIPAddressField(
82 editable=True, unique=False, blank=True, null=True, default=None)
83 static_ip_range_high = GenericIPAddressField(
84 editable=True, unique=False, blank=True, null=True, default=None)
8185
82 # Foreign DHCP server address, if any, that was detected on this86 # Foreign DHCP server address, if any, that was detected on this
83 # interface.87 # interface.