Merge lp:~rvb/maas/add-vlan-fabric into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 4015
Proposed branch: lp:~rvb/maas/add-vlan-fabric
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 965 lines (+879/-2)
7 files modified
src/maasserver/migrations/0139_add_vlan_fabric.py (+494/-0)
src/maasserver/models/__init__.py (+5/-2)
src/maasserver/models/fabric.py (+113/-0)
src/maasserver/models/tests/test_fabric.py (+85/-0)
src/maasserver/models/tests/test_vlan.py (+79/-0)
src/maasserver/models/vlan.py (+81/-0)
src/maasserver/testing/factory.py (+22/-0)
To merge this branch: bzr merge lp:~rvb/maas/add-vlan-fabric
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+261693@code.launchpad.net

Commit message

Add VLAN and Fabric models.

Description of the change

These models are only connect to each other right now. Connections to the new Interface model and the ClusterInterface model will be added in separate branches.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Lots of questions!

review: Needs Information
Revision history for this message
Mike Pontillo (mpontillo) wrote :

A few comments.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Minor clarification.

Revision history for this message
Raphaël Badin (rvb) :
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks for all your replies. Looks good, +1.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/maasserver/migrations/0139_add_vlan_fabric.py'
--- src/maasserver/migrations/0139_add_vlan_fabric.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0139_add_vlan_fabric.py 2015-06-11 14:44:57 +0000
@@ -0,0 +1,494 @@
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 'VLAN'
12 db.create_table(u'maasserver_vlan', (
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 ('name', self.gf('django.db.models.fields.CharField')(max_length=256)),
17 ('vid', self.gf('django.db.models.fields.IntegerField')()),
18 ('fabric', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['maasserver.Fabric'])),
19 ))
20 db.send_create_signal(u'maasserver', ['VLAN'])
21
22 # Adding unique constraint on 'VLAN', fields ['vid', 'fabric']
23 db.create_unique(u'maasserver_vlan', ['vid', 'fabric_id'])
24
25 # Adding unique constraint on 'VLAN', fields ['name', 'fabric']
26 db.create_unique(u'maasserver_vlan', ['name', 'fabric_id'])
27
28 # Adding model 'Fabric'
29 db.create_table(u'maasserver_fabric', (
30 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
31 ('created', self.gf('django.db.models.fields.DateTimeField')()),
32 ('updated', self.gf('django.db.models.fields.DateTimeField')()),
33 ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=256)),
34 ('default_vlan', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name=u'+', null=True, to=orm['maasserver.VLAN'])),
35 ))
36 db.send_create_signal(u'maasserver', ['Fabric'])
37
38
39 def backwards(self, orm):
40 # Removing unique constraint on 'VLAN', fields ['name', 'fabric']
41 db.delete_unique(u'maasserver_vlan', ['name', 'fabric_id'])
42
43 # Removing unique constraint on 'VLAN', fields ['vid', 'fabric']
44 db.delete_unique(u'maasserver_vlan', ['vid', 'fabric_id'])
45
46 # Deleting model 'VLAN'
47 db.delete_table(u'maasserver_vlan')
48
49 # Deleting model 'Fabric'
50 db.delete_table(u'maasserver_fabric')
51
52
53 models = {
54 u'auth.group': {
55 'Meta': {'object_name': 'Group'},
56 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
57 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
58 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
59 },
60 u'auth.permission': {
61 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
62 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
63 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
64 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
65 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
66 },
67 u'auth.user': {
68 'Meta': {'object_name': 'User'},
69 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
70 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
71 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
72 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
73 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
75 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
76 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
77 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
78 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
79 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
80 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
81 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
82 },
83 u'contenttypes.contenttype': {
84 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
85 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
86 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
87 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
88 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
89 },
90 u'maasserver.blockdevice': {
91 'Meta': {'ordering': "[u'id']", 'unique_together': "((u'node', u'path'),)", 'object_name': 'BlockDevice'},
92 'block_size': ('django.db.models.fields.IntegerField', [], {}),
93 'created': ('django.db.models.fields.DateTimeField', [], {}),
94 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95 'id_path': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
96 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
97 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
98 'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
99 'size': ('django.db.models.fields.BigIntegerField', [], {}),
100 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
101 'updated': ('django.db.models.fields.DateTimeField', [], {})
102 },
103 u'maasserver.bootresource': {
104 'Meta': {'unique_together': "((u'name', u'architecture'),)", 'object_name': 'BootResource'},
105 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
106 'created': ('django.db.models.fields.DateTimeField', [], {}),
107 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
108 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
110 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
111 'updated': ('django.db.models.fields.DateTimeField', [], {})
112 },
113 u'maasserver.bootresourcefile': {
114 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
115 'created': ('django.db.models.fields.DateTimeField', [], {}),
116 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
117 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
118 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'root-tgz'", 'max_length': '20'}),
119 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
121 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
122 'updated': ('django.db.models.fields.DateTimeField', [], {})
123 },
124 u'maasserver.bootresourceset': {
125 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
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': '255'}),
129 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
130 'updated': ('django.db.models.fields.DateTimeField', [], {}),
131 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
132 },
133 u'maasserver.bootsource': {
134 'Meta': {'object_name': 'BootSource'},
135 'created': ('django.db.models.fields.DateTimeField', [], {}),
136 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
138 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
139 'updated': ('django.db.models.fields.DateTimeField', [], {}),
140 'url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'})
141 },
142 u'maasserver.bootsourcecache': {
143 'Meta': {'object_name': 'BootSourceCache'},
144 'arch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
145 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
146 'created': ('django.db.models.fields.DateTimeField', [], {}),
147 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
148 'label': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
149 'os': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
150 'release': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
151 'subarch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
152 'updated': ('django.db.models.fields.DateTimeField', [], {})
153 },
154 u'maasserver.bootsourceselection': {
155 'Meta': {'unique_together': "((u'boot_source', u'os', u'release'),)", 'object_name': 'BootSourceSelection'},
156 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
157 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
158 'created': ('django.db.models.fields.DateTimeField', [], {}),
159 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
161 'os': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
162 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
163 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
164 'updated': ('django.db.models.fields.DateTimeField', [], {})
165 },
166 u'maasserver.candidatename': {
167 'Meta': {'unique_together': "((u'name', u'position'),)", 'object_name': 'CandidateName'},
168 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
170 'position': ('django.db.models.fields.IntegerField', [], {})
171 },
172 u'maasserver.componenterror': {
173 'Meta': {'object_name': 'ComponentError'},
174 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
175 'created': ('django.db.models.fields.DateTimeField', [], {}),
176 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
177 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178 'updated': ('django.db.models.fields.DateTimeField', [], {})
179 },
180 u'maasserver.config': {
181 'Meta': {'object_name': 'Config'},
182 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
183 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
184 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
185 },
186 u'maasserver.dhcplease': {
187 'Meta': {'object_name': 'DHCPLease'},
188 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
189 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
190 'mac': ('maasserver.fields.MACAddressField', [], {}),
191 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
192 },
193 u'maasserver.downloadprogress': {
194 'Meta': {'object_name': 'DownloadProgress'},
195 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
196 'created': ('django.db.models.fields.DateTimeField', [], {}),
197 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
198 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
199 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
200 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
201 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
202 'updated': ('django.db.models.fields.DateTimeField', [], {})
203 },
204 u'maasserver.event': {
205 'Meta': {'object_name': 'Event'},
206 'created': ('django.db.models.fields.DateTimeField', [], {}),
207 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
208 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
209 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
210 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
211 'updated': ('django.db.models.fields.DateTimeField', [], {})
212 },
213 u'maasserver.eventtype': {
214 'Meta': {'object_name': 'EventType'},
215 'created': ('django.db.models.fields.DateTimeField', [], {}),
216 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
217 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
218 'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
219 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
220 'updated': ('django.db.models.fields.DateTimeField', [], {})
221 },
222 u'maasserver.fabric': {
223 'Meta': {'object_name': 'Fabric'},
224 'created': ('django.db.models.fields.DateTimeField', [], {}),
225 'default_vlan': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'+'", 'null': 'True', 'to': u"orm['maasserver.VLAN']"}),
226 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
227 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
228 'updated': ('django.db.models.fields.DateTimeField', [], {})
229 },
230 u'maasserver.filestorage': {
231 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
232 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
233 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
234 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
235 'key': ('django.db.models.fields.CharField', [], {'default': "u'4bab59ce-1017-11e5-9b34-3c970e0e56dc'", 'unique': 'True', 'max_length': '36'}),
236 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
237 },
238 u'maasserver.filesystem': {
239 'Meta': {'object_name': 'Filesystem'},
240 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']", 'null': 'True', 'blank': 'True'}),
241 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
242 'created': ('django.db.models.fields.DateTimeField', [], {}),
243 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.FilesystemGroup']"}),
244 'fstype': ('django.db.models.fields.CharField', [], {'default': "u'ext4'", 'max_length': '20'}),
245 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
246 'mount_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
247 'mount_point': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
248 'partition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Partition']", 'null': 'True', 'blank': 'True'}),
249 'updated': ('django.db.models.fields.DateTimeField', [], {}),
250 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
251 },
252 u'maasserver.filesystemgroup': {
253 'Meta': {'object_name': 'FilesystemGroup'},
254 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
255 'created': ('django.db.models.fields.DateTimeField', [], {}),
256 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
257 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
258 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
259 'updated': ('django.db.models.fields.DateTimeField', [], {}),
260 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
261 },
262 u'maasserver.largefile': {
263 'Meta': {'object_name': 'LargeFile'},
264 'content': ('maasserver.fields.LargeObjectField', [], {}),
265 'created': ('django.db.models.fields.DateTimeField', [], {}),
266 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
267 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
268 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
269 'updated': ('django.db.models.fields.DateTimeField', [], {})
270 },
271 u'maasserver.licensekey': {
272 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
273 'created': ('django.db.models.fields.DateTimeField', [], {}),
274 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
275 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
276 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
277 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
278 'updated': ('django.db.models.fields.DateTimeField', [], {})
279 },
280 u'maasserver.macaddress': {
281 'Meta': {'ordering': "(u'created',)", 'object_name': 'MACAddress'},
282 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
283 'created': ('django.db.models.fields.DateTimeField', [], {}),
284 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
285 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}),
286 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
287 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
288 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']", 'null': 'True', 'blank': 'True'}),
289 'updated': ('django.db.models.fields.DateTimeField', [], {})
290 },
291 u'maasserver.macstaticipaddresslink': {
292 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'},
293 'created': ('django.db.models.fields.DateTimeField', [], {}),
294 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}),
296 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}),
297 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
298 'updated': ('django.db.models.fields.DateTimeField', [], {})
299 },
300 u'maasserver.network': {
301 'Meta': {'object_name': 'Network'},
302 'default_gateway': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
303 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
304 'dns_servers': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
305 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
306 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
307 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
308 'netmask': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
309 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
310 },
311 u'maasserver.node': {
312 'Meta': {'object_name': 'Node'},
313 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
314 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
315 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
316 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
317 'created': ('django.db.models.fields.DateTimeField', [], {}),
318 'disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
319 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
320 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
321 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
322 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
323 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
324 'installable': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
325 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
326 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
327 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
328 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
329 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
330 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
331 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "u'children'", 'null': 'True', 'blank': 'True', 'to': u"orm['maasserver.Node']"}),
332 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
333 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
334 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
335 'pxe_mac': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['maasserver.MACAddress']", 'blank': 'True', 'null': 'True'}),
336 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
337 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
338 'swap_size': ('django.db.models.fields.BigIntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
339 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-4bae813a-1017-11e5-9b34-3c970e0e56dc'", 'unique': 'True', 'max_length': '41'}),
340 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
341 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
342 'updated': ('django.db.models.fields.DateTimeField', [], {}),
343 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
344 },
345 u'maasserver.nodegroup': {
346 'Meta': {'object_name': 'NodeGroup'},
347 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
348 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
349 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
350 'created': ('django.db.models.fields.DateTimeField', [], {}),
351 'default_disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
352 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
353 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
354 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
355 'name': ('maasserver.models.nodegroup.DomainNameField', [], {'max_length': '80', 'blank': 'True'}),
356 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
357 'updated': ('django.db.models.fields.DateTimeField', [], {}),
358 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
359 },
360 u'maasserver.nodegroupinterface': {
361 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
362 'broadcast_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
363 'created': ('django.db.models.fields.DateTimeField', [], {}),
364 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
365 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
366 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
367 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
368 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
369 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
370 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
371 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
372 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
373 'router_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
374 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
375 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
376 'subnet_mask': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
377 'updated': ('django.db.models.fields.DateTimeField', [], {})
378 },
379 u'maasserver.partition': {
380 'Meta': {'object_name': 'Partition'},
381 'bootable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
382 'created': ('django.db.models.fields.DateTimeField', [], {}),
383 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
384 'partition_table': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'partitions'", 'to': u"orm['maasserver.PartitionTable']"}),
385 'size': ('django.db.models.fields.BigIntegerField', [], {}),
386 'start_offset': ('django.db.models.fields.BigIntegerField', [], {}),
387 'updated': ('django.db.models.fields.DateTimeField', [], {}),
388 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'unique': 'True', 'null': 'True', 'blank': 'True'})
389 },
390 u'maasserver.partitiontable': {
391 'Meta': {'object_name': 'PartitionTable'},
392 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']"}),
393 'created': ('django.db.models.fields.DateTimeField', [], {}),
394 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
395 'table_type': ('django.db.models.fields.CharField', [], {'default': "u'GPT'", 'max_length': '20'}),
396 'updated': ('django.db.models.fields.DateTimeField', [], {})
397 },
398 u'maasserver.physicalblockdevice': {
399 'Meta': {'ordering': "[u'id']", 'object_name': 'PhysicalBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
400 u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
401 'model': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
402 'serial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
403 },
404 u'maasserver.sshkey': {
405 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
406 'created': ('django.db.models.fields.DateTimeField', [], {}),
407 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
408 'key': ('django.db.models.fields.TextField', [], {}),
409 'updated': ('django.db.models.fields.DateTimeField', [], {}),
410 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
411 },
412 u'maasserver.sslkey': {
413 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
414 'created': ('django.db.models.fields.DateTimeField', [], {}),
415 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
416 'key': ('django.db.models.fields.TextField', [], {}),
417 'updated': ('django.db.models.fields.DateTimeField', [], {}),
418 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
419 },
420 u'maasserver.staticipaddress': {
421 'Meta': {'object_name': 'StaticIPAddress'},
422 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
423 'created': ('django.db.models.fields.DateTimeField', [], {}),
424 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
425 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
426 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
427 'updated': ('django.db.models.fields.DateTimeField', [], {}),
428 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
429 },
430 u'maasserver.tag': {
431 'Meta': {'object_name': 'Tag'},
432 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
433 'created': ('django.db.models.fields.DateTimeField', [], {}),
434 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
435 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
436 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
437 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
438 'updated': ('django.db.models.fields.DateTimeField', [], {})
439 },
440 u'maasserver.userprofile': {
441 'Meta': {'object_name': 'UserProfile'},
442 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
443 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
444 },
445 u'maasserver.virtualblockdevice': {
446 'Meta': {'ordering': "[u'id']", 'object_name': 'VirtualBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
447 u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
448 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'virtual_devices'", 'to': u"orm['maasserver.FilesystemGroup']"}),
449 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
450 },
451 u'maasserver.vlan': {
452 'Meta': {'unique_together': "((u'vid', u'fabric'), (u'name', u'fabric'))", 'object_name': 'VLAN'},
453 'created': ('django.db.models.fields.DateTimeField', [], {}),
454 'fabric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Fabric']"}),
455 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
456 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
457 'updated': ('django.db.models.fields.DateTimeField', [], {}),
458 'vid': ('django.db.models.fields.IntegerField', [], {})
459 },
460 u'maasserver.zone': {
461 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
462 'created': ('django.db.models.fields.DateTimeField', [], {}),
463 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
464 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
465 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
466 'updated': ('django.db.models.fields.DateTimeField', [], {})
467 },
468 u'piston.consumer': {
469 'Meta': {'object_name': 'Consumer'},
470 'description': ('django.db.models.fields.TextField', [], {}),
471 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
472 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
473 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
474 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
475 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
476 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
477 },
478 u'piston.token': {
479 'Meta': {'object_name': 'Token'},
480 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
481 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
482 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
483 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
484 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
485 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
486 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
487 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1434012793L'}),
488 'token_type': ('django.db.models.fields.IntegerField', [], {}),
489 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
490 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
491 }
492 }
493
494 complete_apps = ['maasserver']
0\ No newline at end of file495\ No newline at end of file
1496
=== modified file 'src/maasserver/models/__init__.py'
--- src/maasserver/models/__init__.py 2015-06-09 20:16:46 +0000
+++ src/maasserver/models/__init__.py 2015-06-11 14:44:57 +0000
@@ -29,6 +29,7 @@
29 'FileStorage',29 'FileStorage',
30 'Filesystem',30 'Filesystem',
31 'FilesystemGroup',31 'FilesystemGroup',
32 'Interface',
32 'LargeFile',33 'LargeFile',
33 'LicenseKey',34 'LicenseKey',
34 'logger',35 'logger',
@@ -72,6 +73,7 @@
72from maasserver.models.downloadprogress import DownloadProgress73from maasserver.models.downloadprogress import DownloadProgress
73from maasserver.models.event import Event74from maasserver.models.event import Event
74from maasserver.models.eventtype import EventType75from maasserver.models.eventtype import EventType
76from maasserver.models.fabric import Fabric
75from maasserver.models.filestorage import FileStorage77from maasserver.models.filestorage import FileStorage
76from maasserver.models.filesystem import Filesystem78from maasserver.models.filesystem import Filesystem
77from maasserver.models.filesystemgroup import FilesystemGroup79from maasserver.models.filesystemgroup import FilesystemGroup
@@ -96,6 +98,7 @@
96from maasserver.models.user import create_user98from maasserver.models.user import create_user
97from maasserver.models.userprofile import UserProfile99from maasserver.models.userprofile import UserProfile
98from maasserver.models.virtualblockdevice import VirtualBlockDevice100from maasserver.models.virtualblockdevice import VirtualBlockDevice
101from maasserver.models.vlan import VLAN
99from maasserver.models.zone import Zone102from maasserver.models.zone import Zone
100from maasserver.utils import ignore_unused103from maasserver.utils import ignore_unused
101from piston.doc import HandlerDocumentation104from piston.doc import HandlerDocumentation
@@ -105,10 +108,10 @@
105ignore_unused(108ignore_unused(
106 BootResource, BootResourceFile, BootResourceSet, CandidateName,109 BootResource, BootResourceFile, BootResourceSet, CandidateName,
107 ComponentError, Config, DHCPLease, DownloadProgress, Event, EventType,110 ComponentError, Config, DHCPLease, DownloadProgress, Event, EventType,
108 FileStorage, Filesystem, FilesystemGroup, LargeFile, LicenseKey,111 Fabric, FileStorage, Filesystem, FilesystemGroup, LargeFile, LicenseKey,
109 StaticIPAddress, MACAddress, MACStaticIPAddressLink, Network, NodeGroup,112 StaticIPAddress, MACAddress, MACStaticIPAddressLink, Network, NodeGroup,
110 NodeGroupInterface, Partition, PartitionTable, SSHKey, Tag, UserProfile,113 NodeGroupInterface, Partition, PartitionTable, SSHKey, Tag, UserProfile,
111 VirtualBlockDevice, Zone, logger)114 VirtualBlockDevice, VLAN, Zone, logger)
112115
113116
114# Connect the 'create_user' method to the post save signal of User.117# Connect the 'create_user' method to the post save signal of User.
115118
=== added file 'src/maasserver/models/fabric.py'
--- src/maasserver/models/fabric.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/fabric.py 2015-06-11 14:44:57 +0000
@@ -0,0 +1,113 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Fabric objects."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = [
16 "DEFAULT_FABRIC_NAME",
17 "Fabric",
18 "FABRIC_NAME_VALIDATOR",
19 ]
20
21import datetime
22
23from django.core.exceptions import ValidationError
24from django.core.validators import RegexValidator
25from django.db.models import (
26 CharField,
27 ForeignKey,
28 Manager,
29)
30from maasserver import DefaultMeta
31from maasserver.models.cleansave import CleanSave
32from maasserver.models.timestampedmodel import TimestampedModel
33
34
35FABRIC_NAME_VALIDATOR = RegexValidator('^[ \w-]+$')
36
37# Name of the special, default fabric. This fabric cannot be deleted.
38DEFAULT_FABRIC_NAME = 'Default fabric'
39
40
41class FabricManager(Manager):
42 """Manager for :class:`Fabric` model."""
43
44 def get_default_fabric(self):
45 """Return the default fabric."""
46 now = datetime.datetime.now()
47 fabric, _ = self.get_or_create(
48 id=0,
49 defaults={
50 'id': 0,
51 'name': DEFAULT_FABRIC_NAME,
52 'created': now,
53 'updated': now,
54 }
55 )
56 return fabric
57
58
59class Fabric(CleanSave, TimestampedModel):
60 """A `Fabric`.
61
62 :ivar name: The short-human-identifiable name for this fabric.
63 :ivar objects: An instance of the class :class:`FabricManager`.
64 """
65
66 class Meta(DefaultMeta):
67 """Needed for South to recognize this model."""
68 verbose_name = "Fabric"
69 verbose_name_plural = "Fabrics"
70
71 objects = FabricManager()
72
73 name = CharField(
74 max_length=256, unique=True, editable=True,
75 validators=[FABRIC_NAME_VALIDATOR])
76
77 default_vlan = ForeignKey(
78 'VLAN', blank=True, null=True, editable=True, related_name='+')
79
80 def __unicode__(self):
81 return "name=%s" % self.name
82
83 def is_default(self):
84 """Is this the default fabric?"""
85 return self.id == 0
86
87 def clean(self, *args, **kwargs):
88 wrong_fabric = (
89 self.default_vlan_id is not None and
90 self.default_vlan.fabric != self)
91 if wrong_fabric:
92 raise ValidationError(
93 {'default_vlan':
94 ["Can't set a default VLAN that's not in this fabric."]})
95 super(Fabric, self).clean(*args, **kwargs)
96
97 def delete(self):
98 if self.is_default():
99 raise ValidationError(
100 "This fabric is the default fabric, it cannot be deleted.")
101 super(Fabric, self).delete()
102
103 def save(self, *args, **kwargs):
104 created = self.id is None
105 super(Fabric, self).save(*args, **kwargs)
106 # Create default VLAN if this is a fabric creation.
107 if created:
108 from maasserver.models.vlan import (
109 VLAN, DEFAULT_VLAN_NAME, DEFAULT_VID)
110 default_vlan = VLAN.objects.create(
111 name=DEFAULT_VLAN_NAME, vid=DEFAULT_VID, fabric=self)
112 self.default_vlan = default_vlan
113 self.save()
0114
=== added file 'src/maasserver/models/tests/test_fabric.py'
--- src/maasserver/models/tests/test_fabric.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/tests/test_fabric.py 2015-06-11 14:44:57 +0000
@@ -0,0 +1,85 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the Fabric model."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17
18from django.core.exceptions import ValidationError
19from maasserver.models.fabric import (
20 DEFAULT_FABRIC_NAME,
21 Fabric,
22)
23from maasserver.models.vlan import (
24 DEFAULT_VID,
25 DEFAULT_VLAN_NAME,
26)
27from maasserver.testing.factory import factory
28from maasserver.testing.testcase import MAASServerTestCase
29from testtools.matchers import MatchesStructure
30from testtools.testcase import ExpectedException
31
32
33class FabricTest(MAASServerTestCase):
34
35 def test_creates_fabric_with_default_vlan(self):
36 name = factory.make_name('name')
37 fabric = factory.make_Fabric(name=name)
38 self.assertEqual(name, fabric.name)
39 default_vlan = fabric.default_vlan
40 self.assertThat(default_vlan, MatchesStructure.byEquality(
41 vid=DEFAULT_VID, name=DEFAULT_VLAN_NAME))
42
43 def test_get_default_fabric_creates_default_fabric(self):
44 default_fabric = Fabric.objects.get_default_fabric()
45 self.assertThat(default_fabric, MatchesStructure.byEquality(
46 id=0, name=DEFAULT_FABRIC_NAME))
47
48 def test_get_default_fabric_is_idempotent(self):
49 default_fabric = Fabric.objects.get_default_fabric()
50 default_fabric2 = Fabric.objects.get_default_fabric()
51 self.assertEqual(default_fabric.id, default_fabric2.id)
52
53 def test_is_default_detects_default_fabric(self):
54 default_fabric = Fabric.objects.get_default_fabric()
55 self.assertTrue(default_fabric.is_default())
56
57 def test_is_default_detects_non_default_fabric(self):
58 name = factory.make_name('name')
59 fabric = factory.make_Fabric(name=name)
60 self.assertFalse(fabric.is_default())
61
62 def test_cant_delete_default_fabric(self):
63 default_fabric = Fabric.objects.get_default_fabric()
64 with ExpectedException(ValidationError):
65 default_fabric.delete()
66
67 def test_can_delete_non_default_fabric(self):
68 name = factory.make_name('name')
69 fabric = factory.make_Fabric(name=name)
70 fabric.delete()
71 self.assertItemsEqual([], Fabric.objects.all())
72
73 def test_save_rejects_default_vlan_not_in_fabric(self):
74 vlan = factory.make_VLAN()
75 fabric = factory.make_Fabric()
76 fabric.default_vlan = vlan
77 with ExpectedException(ValidationError):
78 fabric.save()
79
80 def test_save_accepts_default_vlan_in_fabric(self):
81 fabric = factory.make_Fabric()
82 vlan = factory.make_VLAN(fabric=fabric)
83 fabric.default_vlan = vlan
84 # No exception.
85 self.assertIsNone(fabric.save())
086
=== added file 'src/maasserver/models/tests/test_vlan.py'
--- src/maasserver/models/tests/test_vlan.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/tests/test_vlan.py 2015-06-11 14:44:57 +0000
@@ -0,0 +1,79 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the VLAN model."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17import random
18
19from django.core.exceptions import ValidationError
20from maasserver.models.vlan import VLAN
21from maasserver.testing.factory import factory
22from maasserver.testing.testcase import MAASServerTestCase
23from testtools.matchers import MatchesStructure
24from testtools.testcase import ExpectedException
25
26
27class VLANTest(MAASServerTestCase):
28
29 def test_creates_vlan(self):
30 name = factory.make_name('name')
31 vid = random.randint(3, 55)
32 fabric = factory.make_Fabric()
33 vlan = VLAN(vid=vid, name=name, fabric=fabric)
34 vlan.save()
35 self.assertThat(vlan, MatchesStructure.byEquality(
36 vid=vid, name=name))
37
38 def test_is_fabric_default_detects_default_vlan(self):
39 fabric = factory.make_Fabric()
40 vlan = factory.make_VLAN(fabric=fabric)
41 fabric.default_vlan = vlan
42 fabric.save()
43 self.assertTrue(vlan.is_fabric_default())
44
45 def test_is_fabric_default_detects_non_default_vlan(self):
46 vlan = factory.make_VLAN()
47 self.assertFalse(vlan.is_fabric_default())
48
49
50class VLANVidValidationTest(MAASServerTestCase):
51
52 scenarios = [
53 ('0', {'vid': 0, 'valid': True}),
54 ('12', {'vid': 12, 'valid': True}),
55 ('250', {'vid': 250, 'valid': True}),
56 ('3000', {'vid': 3000, 'valid': True}),
57 ('4095', {'vid': 4095, 'valid': True}),
58 ('-23', {'vid': -23, 'valid': False}),
59 ('4096', {'vid': 4096, 'valid': False}),
60 ('10000', {'vid': 10000, 'valid': False}),
61 ]
62
63 def test_validates_vid(self):
64 fabric = factory.make_Fabric()
65 # Remove the auto-created default VLAN so that
66 # we can create it in this test.
67 default_vlan = fabric.default_vlan
68 fabric.default_vlan = None
69 fabric.save()
70 default_vlan.delete()
71 name = factory.make_name('name')
72 vlan = VLAN(vid=self.vid, name=name, fabric=fabric)
73 if self.valid:
74 # No exception.
75 self.assertIsNone(vlan.save())
76
77 else:
78 with ExpectedException(ValidationError):
79 vlan.save()
080
=== added file 'src/maasserver/models/vlan.py'
--- src/maasserver/models/vlan.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/vlan.py 2015-06-11 14:44:57 +0000
@@ -0,0 +1,81 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""VLAN objects."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = [
16 "DEFAULT_VID",
17 "DEFAULT_VLAN_NAME",
18 "Fabric",
19 ]
20
21
22from django.core.exceptions import ValidationError
23from django.core.validators import RegexValidator
24from django.db.models import (
25 CharField,
26 ForeignKey,
27 IntegerField,
28)
29from maasserver import DefaultMeta
30from maasserver.models.cleansave import CleanSave
31from maasserver.models.fabric import Fabric
32from maasserver.models.timestampedmodel import TimestampedModel
33
34
35VLAN_NAME_VALIDATOR = RegexValidator('^[ \w-]+$')
36
37DEFAULT_VLAN_NAME = 'Default VLAN'
38DEFAULT_VID = 0
39
40
41class VLAN(CleanSave, TimestampedModel):
42 """A `VLAN`.
43
44 :ivar name: The short-human-identifiable name for this VLAN.
45 :ivar vid: The VLAN ID of this VLAN.
46 :ivar fabric: The `Fabric` this VLAN belongs to.
47 """
48
49 class Meta(DefaultMeta):
50 """Needed for South to recognize this model."""
51 verbose_name = "VLAN"
52 verbose_name_plural = "VLANs"
53 unique_together = (
54 ('vid', 'fabric'),
55 ('name', 'fabric'),
56 )
57
58 name = CharField(
59 max_length=256, editable=True, validators=[VLAN_NAME_VALIDATOR])
60
61 vid = IntegerField(editable=True)
62
63 fabric = ForeignKey(
64 'Fabric', blank=False, editable=True)
65
66 def __unicode__(self):
67 return "name=%s, vid=%d, fabric=%s" % (
68 self.name, self.vid, self.fabric.name)
69
70 def clean_vid(self):
71 if self.vid < 0 or self.vid > 4095:
72 raise ValidationError(
73 {'vid':
74 ["Vid must be between 0 and 4095."]})
75
76 def clean(self):
77 self.clean_vid()
78
79 def is_fabric_default(self):
80 """Is this the default VLAN in the fabric?"""
81 return self.fabric.default_vlan == self
082
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2015-05-20 13:51:49 +0000
+++ src/maasserver/testing/factory.py 2015-06-11 14:44:57 +0000
@@ -56,6 +56,7 @@
56 DownloadProgress,56 DownloadProgress,
57 Event,57 Event,
58 EventType,58 EventType,
59 Fabric,
59 FileStorage,60 FileStorage,
60 Filesystem,61 Filesystem,
61 FilesystemGroup,62 FilesystemGroup,
@@ -75,6 +76,7 @@
75 StaticIPAddress,76 StaticIPAddress,
76 Tag,77 Tag,
77 VirtualBlockDevice,78 VirtualBlockDevice,
79 VLAN,
78 Zone,80 Zone,
79)81)
80from maasserver.models.bootresourceset import (82from maasserver.models.bootresourceset import (
@@ -581,6 +583,26 @@
581 key.save()583 key.save()
582 return key584 return key
583585
586 def make_Fabric(self, name=None):
587 if name is None:
588 name = self.make_name('fabric')
589 fabric = Fabric(name=name)
590 fabric.save()
591 return fabric
592
593 def make_VLAN(self, name=None, vid=None, fabric=None):
594 assert vid != 0, "VID=0 VLANs are auto-created"
595 if name is None:
596 name = self.make_name('vlan')
597 if vid is None:
598 # Don't create the vid=0 VLAN, it's auto-created.
599 vid = random.randint(1, 4095)
600 if fabric is None:
601 fabric = self.make_Fabric()
602 vlan = VLAN(name=name, vid=vid, fabric=fabric)
603 vlan.save()
604 return vlan
605
584 def make_Tag(self, name=None, definition=None, comment='',606 def make_Tag(self, name=None, definition=None, comment='',
585 kernel_opts=None, created=None, updated=None):607 kernel_opts=None, created=None, updated=None):
586 if name is None:608 if name is None: