Merge lp:~blake-rouse/maas/fix-vlan-to-untagged into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4370
Proposed branch: lp:~blake-rouse/maas/fix-vlan-to-untagged
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 1262 lines (+742/-63)
26 files modified
src/maasserver/api/fabrics.py (+5/-0)
src/maasserver/api/tests/test_fabrics.py (+1/-11)
src/maasserver/api/tests/test_vlans.py (+3/-4)
src/maasserver/api/vlans.py (+6/-1)
src/maasserver/context_processors.py (+1/-0)
src/maasserver/fixtures/dev_fixture.yaml (+4/-4)
src/maasserver/forms_vlan.py (+8/-0)
src/maasserver/migrations/0183_remove_required_name_from_vlan_and_fabric.py (+519/-0)
src/maasserver/models/fabric.py (+8/-1)
src/maasserver/models/tests/test_fabric.py (+9/-0)
src/maasserver/models/tests/test_vlan.py (+9/-0)
src/maasserver/models/vlan.py (+10/-4)
src/maasserver/static/js/angular/controllers/node_details_networking.js (+28/-3)
src/maasserver/static/js/angular/controllers/subnets_list.js (+3/-1)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+58/-11)
src/maasserver/static/js/angular/filters/remove_default_vlan.js (+17/-0)
src/maasserver/static/js/angular/filters/tests/test_remove_default_vlan.js (+30/-0)
src/maasserver/static/partials/node-details.html (+6/-6)
src/maasserver/testing/factory.py (+0/-4)
src/maasserver/tests/test_forms_fabric.py (+0/-7)
src/maasserver/tests/test_forms_space.py (+2/-2)
src/maasserver/tests/test_forms_vlan.py (+11/-2)
src/maasserver/websockets/handlers/fabric.py (+1/-0)
src/maasserver/websockets/handlers/tests/test_fabric.py (+1/-1)
src/maasserver/websockets/handlers/tests/test_vlan.py (+1/-1)
src/maasserver/websockets/handlers/vlan.py (+1/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-vlan-to-untagged
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Mike Pontillo (community) Approve
Review via email: mp+273948@code.launchpad.net

Commit message

Remove the requirement of names on fabric and vlan. Fix fabric name and vlan name based on the id and vid respectively. Fix the UI to not allow the VLAN editing on physical interfaces. Fix other small UI issues with node networking. Prevent the default VLAN from being editable on a fabric.

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

Just one minor comment - not sure it's desired to not allow any editing of the default VLAN.

It's a minor enough debate that I wouldn't block landing this, but please call it out in the commit message too.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Thanks for the review. I added it to the commit message.

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

The attempt to merge lp:~blake-rouse/maas/fix-vlan-to-untagged into lp:maas failed. Below is the output from the failed tests.

Hit http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [238 kB]
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [140 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [628 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [323 kB]
Get:6 http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en [305 kB]
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en [170 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,868 kB in 4s (466 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testresources python-testscenarios python-testtools python-twisted pytho...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/fabrics.py'
2--- src/maasserver/api/fabrics.py 2015-08-18 15:10:54 +0000
3+++ src/maasserver/api/fabrics.py 2015-10-09 21:26:30 +0000
4@@ -75,6 +75,11 @@
5 return ('fabric_handler', (fabric_id,))
6
7 @classmethod
8+ def name(cls, fabric):
9+ """Return the name of the fabric."""
10+ return fabric.get_name()
11+
12+ @classmethod
13 def vlans(cls, fabric):
14 """Return VLANs within the specified fabric."""
15 return fabric.vlan_set.all()
16
17=== modified file 'src/maasserver/api/tests/test_fabrics.py'
18--- src/maasserver/api/tests/test_fabrics.py 2015-08-13 19:29:08 +0000
19+++ src/maasserver/api/tests/test_fabrics.py 2015-10-09 21:26:30 +0000
20@@ -82,16 +82,6 @@
21 self.assertEqual(
22 httplib.FORBIDDEN, response.status_code, response.content)
23
24- def test_create_requires_name(self):
25- self.become_admin()
26- uri = get_fabrics_uri()
27- response = self.client.post(uri, {})
28- self.assertEqual(
29- httplib.BAD_REQUEST, response.status_code, response.content)
30- self.assertEqual({
31- "name": ["This field is required."],
32- }, json.loads(response.content))
33-
34
35 class TestFabricAPI(APITestCase):
36
37@@ -112,7 +102,7 @@
38 parsed_fabric = json.loads(response.content)
39 self.assertThat(parsed_fabric, ContainsDict({
40 "id": Equals(fabric.id),
41- "name": Equals(fabric.name),
42+ "name": Equals(fabric.get_name()),
43 }))
44 self.assertItemsEqual([
45 vlan.id
46
47=== modified file 'src/maasserver/api/tests/test_vlans.py'
48--- src/maasserver/api/tests/test_vlans.py 2015-08-15 00:08:54 +0000
49+++ src/maasserver/api/tests/test_vlans.py 2015-10-09 21:26:30 +0000
50@@ -93,7 +93,7 @@
51 self.assertEqual(
52 httplib.FORBIDDEN, response.status_code, response.content)
53
54- def test_create_requires_name_and_vid(self):
55+ def test_create_requires_vid(self):
56 self.become_admin()
57 fabric = factory.make_Fabric()
58 uri = get_vlans_uri(fabric)
59@@ -101,7 +101,6 @@
60 self.assertEqual(
61 httplib.BAD_REQUEST, response.status_code, response.content)
62 self.assertEqual({
63- "name": ["This field is required."],
64 "vid": [
65 "This field is required.",
66 "Vid must be between 0 and 4095."],
67@@ -128,9 +127,9 @@
68 parsed_vlan = json.loads(response.content)
69 self.assertThat(parsed_vlan, ContainsDict({
70 "id": Equals(vlan.id),
71- "name": Equals(vlan.name),
72+ "name": Equals(vlan.get_name()),
73 "vid": Equals(vlan.vid),
74- "fabric": Equals(fabric.name),
75+ "fabric": Equals(fabric.get_name()),
76 "resource_uri": Equals(get_vlan_uri(vlan)),
77 }))
78
79
80=== modified file 'src/maasserver/api/vlans.py'
81--- src/maasserver/api/vlans.py 2015-08-18 15:25:03 +0000
82+++ src/maasserver/api/vlans.py 2015-10-09 21:26:30 +0000
83@@ -90,7 +90,12 @@
84 @classmethod
85 def fabric(cls, vlan):
86 """Return fabric name."""
87- return vlan.fabric.name
88+ return vlan.fabric.get_name()
89+
90+ @classmethod
91+ def name(cls, vlan):
92+ """Return the VLAN name."""
93+ return vlan.get_name()
94
95 def read(self, request, fabric_id, vlan_id):
96 """Read VLAN on fabric.
97
98=== modified file 'src/maasserver/context_processors.py'
99--- src/maasserver/context_processors.py 2015-09-30 22:30:55 +0000
100+++ src/maasserver/context_processors.py 2015-10-09 21:26:30 +0000
101@@ -76,6 +76,7 @@
102 'js/angular/filters/by_fabric.js',
103 'js/angular/filters/by_vlan.js',
104 'js/angular/filters/by_space.js',
105+ 'js/angular/filters/remove_default_vlan.js',
106 'js/angular/controllers/error.js',
107 'js/angular/controllers/nodes_list.js',
108 'js/angular/controllers/add_hardware.js',
109
110=== modified file 'src/maasserver/fixtures/dev_fixture.yaml'
111--- src/maasserver/fixtures/dev_fixture.yaml 2015-09-27 20:01:05 +0000
112+++ src/maasserver/fixtures/dev_fixture.yaml 2015-10-09 21:26:30 +0000
113@@ -9484,11 +9484,11 @@
114 updated: ! '2015-09-18 15:38:26.981744'}
115 model: maasserver.event
116 pk: 236
117-- fields: {created: ! '2015-09-18 15:14:45.727015', name: Default fabric, updated: ! '2015-09-18
118+- fields: {created: ! '2015-09-18 15:14:45.727015', updated: ! '2015-09-18
119 15:14:45.727015'}
120 model: maasserver.fabric
121 pk: 0
122-- fields: {created: ! '2015-09-18 15:14:45.727015', name: Extra fabric, updated: ! '2015-09-18
123+- fields: {created: ! '2015-09-18 15:14:45.727015', updated: ! '2015-09-18
124 15:14:45.727015'}
125 model: maasserver.fabric
126 pk: 1
127@@ -9512,11 +9512,11 @@
128 - fields: {filesystem_group: 4, uuid: 3a140acf-6a98-4572-aa2f-3de2ba56b818}
129 model: maasserver.virtualblockdevice
130 pk: 12
131-- fields: {created: ! '2015-09-18 15:14:45.727015', fabric: 0, name: Default VLAN,
132+- fields: {created: ! '2015-09-18 15:14:45.727015', fabric: 0,
133 updated: ! '2015-09-18 15:14:45.727015', vid: 0}
134 model: maasserver.vlan
135 pk: 0
136-- fields: {created: ! '2015-09-18 15:14:45.727015', fabric: 1, name: Extra VLAN,
137+- fields: {created: ! '2015-09-18 15:14:45.727015', fabric: 1,
138 updated: ! '2015-09-18 15:14:45.727015', vid: 0}
139 model: maasserver.vlan
140 pk: 1
141
142=== modified file 'src/maasserver/forms_vlan.py'
143--- src/maasserver/forms_vlan.py 2015-08-14 14:00:46 +0000
144+++ src/maasserver/forms_vlan.py 2015-10-09 21:26:30 +0000
145@@ -16,6 +16,7 @@
146 "VLANForm",
147 ]
148
149+from django.core.exceptions import ValidationError
150 from maasserver.forms import MAASModelForm
151 from maasserver.models.vlan import VLAN
152
153@@ -37,6 +38,13 @@
154 if instance is None and self.fabric is None:
155 raise ValueError("Form requires either a instance or a fabric.")
156
157+ def clean(self):
158+ cleaned_data = super(VLANForm, self).clean()
159+ if self.instance.id is not None and self.instance.is_fabric_default():
160+ raise ValidationError(
161+ "Cannot modify the default VLAN for a fabric.")
162+ return cleaned_data
163+
164 def save(self):
165 """Persist the interface into the database."""
166 interface = super(VLANForm, self).save(commit=False)
167
168=== added file 'src/maasserver/migrations/0183_remove_required_name_from_vlan_and_fabric.py'
169--- src/maasserver/migrations/0183_remove_required_name_from_vlan_and_fabric.py 1970-01-01 00:00:00 +0000
170+++ src/maasserver/migrations/0183_remove_required_name_from_vlan_and_fabric.py 2015-10-09 21:26:30 +0000
171@@ -0,0 +1,519 @@
172+from django.db import models
173+from south.db import db
174+# -*- coding: utf-8 -*-
175+from south.utils import datetime_utils as datetime
176+from south.v2 import SchemaMigration
177+
178+
179+class Migration(SchemaMigration):
180+
181+ def forwards(self, orm):
182+ # Removing unique constraint on 'Fabric', fields ['name']
183+ db.delete_unique(u'maasserver_fabric', ['name'])
184+
185+ # Removing unique constraint on 'VLAN', fields ['name', 'fabric']
186+ db.delete_unique(u'maasserver_vlan', ['name', 'fabric_id'])
187+
188+
189+ # Changing field 'VLAN.name'
190+ db.alter_column(u'maasserver_vlan', 'name', self.gf('django.db.models.fields.CharField')(max_length=256, null=True))
191+
192+ # Changing field 'Fabric.name'
193+ db.alter_column(u'maasserver_fabric', 'name', self.gf('django.db.models.fields.CharField')(max_length=256, null=True))
194+
195+ def backwards(self, orm):
196+
197+ # User chose to not deal with backwards NULL issues for 'VLAN.name'
198+ raise RuntimeError("Cannot reverse this migration. 'VLAN.name' and its values cannot be restored.")
199+ # Adding unique constraint on 'VLAN', fields ['name', 'fabric']
200+ db.create_unique(u'maasserver_vlan', ['name', 'fabric_id'])
201+
202+
203+ # User chose to not deal with backwards NULL issues for 'Fabric.name'
204+ raise RuntimeError("Cannot reverse this migration. 'Fabric.name' and its values cannot be restored.")
205+ # Adding unique constraint on 'Fabric', fields ['name']
206+ db.create_unique(u'maasserver_fabric', ['name'])
207+
208+
209+ models = {
210+ u'auth.group': {
211+ 'Meta': {'object_name': 'Group'},
212+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
213+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
214+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
215+ },
216+ u'auth.permission': {
217+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
218+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
219+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
220+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
221+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
222+ },
223+ u'auth.user': {
224+ 'Meta': {'object_name': 'User'},
225+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
226+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
227+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
228+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
229+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
230+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
231+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
232+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
233+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
234+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
235+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
236+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
237+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
238+ },
239+ u'contenttypes.contenttype': {
240+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
241+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
242+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
244+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
245+ },
246+ u'maasserver.blockdevice': {
247+ 'Meta': {'ordering': "[u'id']", 'unique_together': "((u'node', u'name'),)", 'object_name': 'BlockDevice'},
248+ 'block_size': ('django.db.models.fields.IntegerField', [], {}),
249+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
250+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
251+ 'id_path': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
252+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
253+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
254+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
255+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
256+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
257+ },
258+ u'maasserver.bootresource': {
259+ 'Meta': {'unique_together': "((u'name', u'architecture'),)", 'object_name': 'BootResource'},
260+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
261+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
262+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
263+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
264+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
265+ 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
266+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
267+ },
268+ u'maasserver.bootresourcefile': {
269+ 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
270+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
271+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
272+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
273+ 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'root-tgz'", 'max_length': '20'}),
274+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275+ 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
276+ 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
277+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
278+ },
279+ u'maasserver.bootresourceset': {
280+ 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
281+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
282+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
283+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
284+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
285+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
286+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
287+ },
288+ u'maasserver.bootsource': {
289+ 'Meta': {'object_name': 'BootSource'},
290+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
291+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
292+ 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
293+ 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
294+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
295+ 'url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'})
296+ },
297+ u'maasserver.bootsourcecache': {
298+ 'Meta': {'object_name': 'BootSourceCache'},
299+ 'arch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
300+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
301+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
302+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
303+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
304+ 'os': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
305+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
306+ 'subarch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
307+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
308+ },
309+ u'maasserver.bootsourceselection': {
310+ 'Meta': {'unique_together': "((u'boot_source', u'os', u'release'),)", 'object_name': 'BootSourceSelection'},
311+ 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
312+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
313+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
314+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
315+ 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
316+ 'os': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
317+ 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
318+ 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
319+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
320+ },
321+ u'maasserver.cacheset': {
322+ 'Meta': {'object_name': 'CacheSet'},
323+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
324+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
325+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
326+ },
327+ u'maasserver.candidatename': {
328+ 'Meta': {'unique_together': "((u'name', u'position'),)", 'object_name': 'CandidateName'},
329+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
330+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
331+ 'position': ('django.db.models.fields.IntegerField', [], {})
332+ },
333+ u'maasserver.componenterror': {
334+ 'Meta': {'object_name': 'ComponentError'},
335+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
336+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
337+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
338+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
339+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
340+ },
341+ u'maasserver.config': {
342+ 'Meta': {'object_name': 'Config'},
343+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
344+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
345+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
346+ },
347+ u'maasserver.downloadprogress': {
348+ 'Meta': {'object_name': 'DownloadProgress'},
349+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
350+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
351+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
352+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
353+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
354+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
355+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
356+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
357+ },
358+ u'maasserver.event': {
359+ 'Meta': {'object_name': 'Event'},
360+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
361+ 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
362+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
363+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
364+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
365+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
366+ },
367+ u'maasserver.eventtype': {
368+ 'Meta': {'object_name': 'EventType'},
369+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
370+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
371+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
372+ 'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
373+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
374+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
375+ },
376+ u'maasserver.fabric': {
377+ 'Meta': {'object_name': 'Fabric'},
378+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
379+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
380+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
381+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
382+ },
383+ u'maasserver.fannetwork': {
384+ 'Meta': {'object_name': 'FanNetwork'},
385+ 'bridge': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
386+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
387+ 'dhcp': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
388+ 'host_reserve': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
389+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
390+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
391+ 'off': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
392+ 'overlay': ('maasserver.fields.IPv4CIDRField', [], {'unique': 'True'}),
393+ 'underlay': ('maasserver.fields.IPv4CIDRField', [], {'unique': 'True'}),
394+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
395+ },
396+ u'maasserver.filestorage': {
397+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
398+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
399+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
400+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
401+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'a1d75a4c-6e44-11e5-a1dc-c48e8ffbb365'", 'unique': 'True', 'max_length': '36'}),
402+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
403+ },
404+ u'maasserver.filesystem': {
405+ 'Meta': {'unique_together': "((u'partition', u'acquired'), (u'block_device', u'acquired'))", 'object_name': 'Filesystem'},
406+ 'acquired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
407+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']", 'null': 'True', 'blank': 'True'}),
408+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.CacheSet']"}),
409+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
410+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
411+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.FilesystemGroup']"}),
412+ 'fstype': ('django.db.models.fields.CharField', [], {'default': "u'ext4'", 'max_length': '20'}),
413+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
414+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
415+ 'mount_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
416+ 'mount_point': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
417+ 'partition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Partition']", 'null': 'True', 'blank': 'True'}),
418+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
419+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36'})
420+ },
421+ u'maasserver.filesystemgroup': {
422+ 'Meta': {'object_name': 'FilesystemGroup'},
423+ 'cache_mode': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
424+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.CacheSet']", 'null': 'True', 'blank': 'True'}),
425+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
426+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
427+ 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
428+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
429+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
430+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
431+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
432+ },
433+ u'maasserver.interface': {
434+ 'Meta': {'ordering': "(u'created',)", 'object_name': 'Interface'},
435+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
436+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
437+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
438+ 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.StaticIPAddress']", 'null': 'True', 'blank': 'True'}),
439+ 'ipv4_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
440+ 'ipv6_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
441+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'null': 'True', 'blank': 'True'}),
442+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
443+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']", 'null': 'True', 'blank': 'True'}),
444+ 'params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
445+ 'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.Interface']", 'null': 'True', 'through': u"orm['maasserver.InterfaceRelationship']", 'blank': 'True'}),
446+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
447+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
448+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
449+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
450+ },
451+ u'maasserver.interfacerelationship': {
452+ 'Meta': {'object_name': 'InterfaceRelationship'},
453+ 'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'parent_relationships'", 'to': u"orm['maasserver.Interface']"}),
454+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
455+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
456+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'children_relationships'", 'to': u"orm['maasserver.Interface']"}),
457+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
458+ },
459+ u'maasserver.largefile': {
460+ 'Meta': {'object_name': 'LargeFile'},
461+ 'content': ('maasserver.fields.LargeObjectField', [], {}),
462+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
463+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
464+ 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
465+ 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
466+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
467+ },
468+ u'maasserver.licensekey': {
469+ 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
470+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
471+ 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
472+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
473+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
474+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
475+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
476+ },
477+ u'maasserver.node': {
478+ 'Meta': {'object_name': 'Node'},
479+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
480+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
481+ 'bios_boot_method': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
482+ 'block_poweroff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
483+ 'boot_cluster_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
484+ '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'}),
485+ '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'}),
486+ 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
487+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
488+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
489+ 'disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
490+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
491+ 'enable_ssh': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
492+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
493+ 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
494+ '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'}),
495+ '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'}),
496+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
497+ 'hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
498+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
499+ 'installable': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
500+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
501+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
502+ 'min_hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
503+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
504+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
505+ 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
506+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
507+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "u'children'", 'null': 'True', 'blank': 'True', 'to': u"orm['maasserver.Node']"}),
508+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'max_length': '32768', 'blank': 'True'}),
509+ 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
510+ 'power_state_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
511+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
512+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
513+ 'skip_networking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
514+ 'skip_storage': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
515+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
516+ 'swap_size': ('django.db.models.fields.BigIntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
517+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-a1d55db4-6e44-11e5-a1dc-c48e8ffbb365'", 'unique': 'True', 'max_length': '41'}),
518+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
519+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
520+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
521+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
522+ },
523+ u'maasserver.nodegroup': {
524+ 'Meta': {'object_name': 'NodeGroup'},
525+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
526+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
527+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
528+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
529+ 'default_disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
530+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
531+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
532+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
533+ 'name': ('maasserver.models.nodegroup.DomainNameField', [], {'max_length': '80', 'blank': 'True'}),
534+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
535+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
536+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
537+ },
538+ u'maasserver.nodegroupinterface': {
539+ 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
540+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
541+ 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
542+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
543+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
544+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
545+ 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
546+ 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
547+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
548+ 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
549+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
550+ 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
551+ 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
552+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}),
553+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
554+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
555+ },
556+ u'maasserver.partition': {
557+ 'Meta': {'object_name': 'Partition'},
558+ 'bootable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
559+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
560+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
561+ 'partition_table': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'partitions'", 'to': u"orm['maasserver.PartitionTable']"}),
562+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
563+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
564+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'unique': 'True', 'null': 'True', 'blank': 'True'})
565+ },
566+ u'maasserver.partitiontable': {
567+ 'Meta': {'object_name': 'PartitionTable'},
568+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']"}),
569+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
570+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
571+ 'table_type': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '20'}),
572+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
573+ },
574+ u'maasserver.physicalblockdevice': {
575+ 'Meta': {'ordering': "[u'id']", 'object_name': 'PhysicalBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
576+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
577+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
578+ 'serial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
579+ },
580+ u'maasserver.space': {
581+ 'Meta': {'object_name': 'Space'},
582+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
583+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
584+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
585+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
586+ },
587+ u'maasserver.sshkey': {
588+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
589+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
590+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
591+ 'key': ('django.db.models.fields.TextField', [], {}),
592+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
593+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
594+ },
595+ u'maasserver.sslkey': {
596+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
597+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
598+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
599+ 'key': ('django.db.models.fields.TextField', [], {}),
600+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
601+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
602+ },
603+ u'maasserver.staticipaddress': {
604+ 'Meta': {'object_name': 'StaticIPAddress'},
605+ 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
606+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
607+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
608+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
609+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
610+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'blank': 'True'}),
611+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
612+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
613+ },
614+ u'maasserver.subnet': {
615+ 'Meta': {'unique_together': "((u'name', u'space'),)", 'object_name': 'Subnet'},
616+ 'cidr': ('maasserver.fields.CIDRField', [], {'unique': 'True'}),
617+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
618+ 'dns_servers': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
619+ 'gateway_ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
620+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
621+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
622+ 'space': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Space']", 'on_delete': 'models.PROTECT'}),
623+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
624+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
625+ },
626+ u'maasserver.tag': {
627+ 'Meta': {'object_name': 'Tag'},
628+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
629+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
630+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
631+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
632+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
633+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
634+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
635+ },
636+ u'maasserver.userprofile': {
637+ 'Meta': {'object_name': 'UserProfile'},
638+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
639+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
640+ },
641+ u'maasserver.virtualblockdevice': {
642+ 'Meta': {'ordering': "[u'id']", 'object_name': 'VirtualBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
643+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
644+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'virtual_devices'", 'to': u"orm['maasserver.FilesystemGroup']"}),
645+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
646+ },
647+ u'maasserver.vlan': {
648+ 'Meta': {'unique_together': "((u'vid', u'fabric'),)", 'object_name': 'VLAN'},
649+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
650+ 'fabric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Fabric']"}),
651+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
652+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
653+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
654+ 'vid': ('django.db.models.fields.IntegerField', [], {})
655+ },
656+ u'maasserver.zone': {
657+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
658+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
659+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
660+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
661+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
662+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
663+ },
664+ u'piston.consumer': {
665+ 'Meta': {'object_name': 'Consumer'},
666+ 'description': ('django.db.models.fields.TextField', [], {}),
667+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
668+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
669+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
670+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
671+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
672+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
673+ },
674+ u'piston.token': {
675+ 'Meta': {'object_name': 'Token'},
676+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
677+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
678+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
679+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
680+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
681+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
682+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
683+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1444367666L'}),
684+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
685+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
686+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
687+ }
688+ }
689+
690+ complete_apps = ['maasserver']
691\ No newline at end of file
692
693=== modified file 'src/maasserver/models/fabric.py'
694--- src/maasserver/models/fabric.py 2015-08-13 23:00:22 +0000
695+++ src/maasserver/models/fabric.py 2015-10-09 21:26:30 +0000
696@@ -101,7 +101,7 @@
697 objects = FabricManager()
698
699 name = CharField(
700- max_length=256, unique=True, editable=True,
701+ max_length=256, editable=True, null=True, blank=True,
702 validators=[FABRIC_NAME_VALIDATOR])
703
704 def __unicode__(self):
705@@ -114,6 +114,13 @@
706 def get_default_vlan(self):
707 return self.vlan_set.all().order_by('id').first()
708
709+ def get_name(self):
710+ """Return the name of the fabric."""
711+ if self.name:
712+ return self.name
713+ else:
714+ return "fabric-%s" % self.id
715+
716 def delete(self):
717 if self.is_default():
718 raise ValidationError(
719
720=== modified file 'src/maasserver/models/tests/test_fabric.py'
721--- src/maasserver/models/tests/test_fabric.py 2015-08-28 02:34:50 +0000
722+++ src/maasserver/models/tests/test_fabric.py 2015-10-09 21:26:30 +0000
723@@ -90,6 +90,15 @@
724
725 class TestFabric(MAASServerTestCase):
726
727+ def test_get_name_for_empty_name(self):
728+ fabric = factory.make_Fabric()
729+ self.assertEquals("fabric-%s" % fabric.id, fabric.get_name())
730+
731+ def test_get_name_for_set_name(self):
732+ name = factory.make_name('name')
733+ fabric = factory.make_Fabric(name=name)
734+ self.assertEquals(name, fabric.get_name())
735+
736 def test_creates_fabric_with_default_vlan(self):
737 name = factory.make_name('name')
738 fabric = factory.make_Fabric(name=name)
739
740=== modified file 'src/maasserver/models/tests/test_vlan.py'
741--- src/maasserver/models/tests/test_vlan.py 2015-08-28 02:34:50 +0000
742+++ src/maasserver/models/tests/test_vlan.py 2015-10-09 21:26:30 +0000
743@@ -34,6 +34,15 @@
744
745 class VLANTest(MAASServerTestCase):
746
747+ def test_get_name_for_default_vlan_is_untagged(self):
748+ fabric = factory.make_Fabric()
749+ self.assertEquals("untagged", fabric.get_default_vlan().get_name())
750+
751+ def test_get_name_for_set_name(self):
752+ name = factory.make_name('name')
753+ vlan = factory.make_VLAN(name=name)
754+ self.assertEquals(name, vlan.get_name())
755+
756 def test_creates_vlan(self):
757 name = factory.make_name('name')
758 vid = random.randint(3, 55)
759
760=== modified file 'src/maasserver/models/vlan.py'
761--- src/maasserver/models/vlan.py 2015-06-19 09:53:30 +0000
762+++ src/maasserver/models/vlan.py 2015-10-09 21:26:30 +0000
763@@ -66,19 +66,18 @@
764 verbose_name_plural = "VLANs"
765 unique_together = (
766 ('vid', 'fabric'),
767- ('name', 'fabric'),
768 )
769
770 name = CharField(
771- max_length=256, editable=True, validators=[VLAN_NAME_VALIDATOR])
772+ max_length=256, editable=True, null=True, blank=True,
773+ validators=[VLAN_NAME_VALIDATOR])
774
775 vid = IntegerField(editable=True)
776
777 fabric = ForeignKey('Fabric', blank=False, editable=True)
778
779 def __unicode__(self):
780- return "name=%s, vid=%d, fabric=%s" % (
781- self.name, self.vid, self.fabric.name)
782+ return "%s.%s" % (self.fabric.get_name(), self.get_name())
783
784 def clean_vid(self):
785 if self.vid < 0 or self.vid > 4095:
786@@ -93,6 +92,13 @@
787 """Is this the default VLAN in the fabric?"""
788 return self.fabric.get_default_vlan() == self
789
790+ def get_name(self):
791+ """Return the name of the VLAN."""
792+ if self.is_fabric_default():
793+ return "untagged"
794+ else:
795+ return self.name
796+
797 def manage_connected_interfaces(self):
798 """Deal with connected interfaces:
799
800
801=== modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js'
802--- src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-06 05:50:44 +0000
803+++ src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-09 21:26:30 +0000
804@@ -61,6 +61,26 @@
805 });
806
807
808+// Filter that is specific to the NodeNetworkingController. Remove the default
809+// VLAN if the interface is a VLAN interface.
810+angular.module('MAAS').filter('removeDefaultVLANIfVLAN', function() {
811+ return function(vlans, interfaceType) {
812+ if(!angular.isString(interfaceType)) {
813+ return vlans;
814+ }
815+ var filtered = [];
816+ angular.forEach(vlans, function(vlan) {
817+ if(interfaceType !== "vlan") {
818+ filtered.push(vlan);
819+ } else if(vlan.vid !== 0) {
820+ filtered.push(vlan);
821+ }
822+ });
823+ return filtered;
824+ };
825+});
826+
827+
828 angular.module('MAAS').controller('NodeNetworkingController', [
829 '$scope', '$filter', 'FabricsManager', 'VLANsManager', 'SubnetsManager',
830 'NodesManager', 'GeneralManager', 'ManagerHelperService',
831@@ -332,7 +352,8 @@
832 // Return list of unused VLANs for an interface. Also remove the
833 // ignoreVLANs from the returned list.
834 function getUnusedVLANs(nic, ignoreVLANs) {
835- var vlans = $filter('filterByFabric')($scope.vlans, nic.fabric);
836+ var vlans = $filter('removeDefaultVLAN')($scope.vlans);
837+ vlans = $filter('filterByFabric')(vlans, nic.fabric);
838 vlans = $filter('filterByUnusedForInterface')(
839 vlans, nic, $scope.originalInterfaces);
840
841@@ -413,7 +434,9 @@
842 return "";
843 }
844
845- if(angular.isString(vlan.name) && vlan.name.length > 0) {
846+ if(vlan.vid === 0) {
847+ return "untagged";
848+ } else if(angular.isString(vlan.name) && vlan.name.length > 0) {
849 return vlan.vid + " (" + vlan.name + ")";
850 } else {
851 return vlan.vid;
852@@ -624,7 +647,8 @@
853 // Return True if the interface IP address that the user typed is
854 // invalid.
855 $scope.isIPAddressInvalid = function(nic) {
856- return (nic.ip_address.length === 0 ||
857+ return (!angular.isString(nic.ip_address) ||
858+ nic.ip_address.length === 0 ||
859 !ValidationService.validateIP(nic.ip_address) ||
860 !ValidationService.validateIPInNetwork(
861 nic.ip_address, nic.subnet.cidr));
862@@ -907,6 +931,7 @@
863 } else {
864 $scope.selectedMode = SELECTION_MODE.NONE;
865 $scope.selectedInterfaces = [];
866+ $scope.newInterface = {};
867 }
868 };
869
870
871=== modified file 'src/maasserver/static/js/angular/controllers/subnets_list.js'
872--- src/maasserver/static/js/angular/controllers/subnets_list.js 2015-10-06 22:24:06 +0000
873+++ src/maasserver/static/js/angular/controllers/subnets_list.js 2015-10-09 21:26:30 +0000
874@@ -104,7 +104,9 @@
875 // Return the name name for the VLAN.
876 function getVLANName(vlan) {
877 var name = vlan.vid;
878- if(angular.isString(vlan.name) && vlan.name !== "") {
879+ if(vlan.vid === 0) {
880+ name = "untagged";
881+ } else if(angular.isString(vlan.name) && vlan.name !== "") {
882 name += " (" + vlan.name + ")";
883 }
884 return name;
885
886=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js'
887--- src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-06 05:50:44 +0000
888+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-09 21:26:30 +0000
889@@ -26,7 +26,7 @@
890 expect(filterByUnusedForInterface(vlans)).toEqual([]);
891 });
892
893- it("returns empty if undefined originalInterfaces", function() {
894+ it("returns only free vlans", function() {
895 var i, vlan, used_vlans = [], free_vlans = [], all_vlans = [];
896 for(i = 0; i < 3; i++) {
897 vlan = {
898@@ -125,6 +125,48 @@
899 });
900
901
902+describe("removeDefaultVLANIfVLAN", function() {
903+
904+ // Load the MAAS module.
905+ beforeEach(module("MAAS"));
906+
907+ // Load the removeDefaultVLANIfVLAN.
908+ var removeDefaultVLANIfVLAN;
909+ beforeEach(inject(function($filter) {
910+ removeDefaultVLANIfVLAN = $filter("removeDefaultVLANIfVLAN");
911+ }));
912+
913+ it("returns vlans if undefined type", function() {
914+ var i, vlan, vlans = [];
915+ for(i = 0; i < 3; i++) {
916+ vlan = {
917+ id: i,
918+ vid: i,
919+ fabric: 0
920+ };
921+ vlans.push(vlan);
922+ }
923+ expect(removeDefaultVLANIfVLAN(vlans)).toEqual(vlans);
924+ });
925+
926+ it("removes default vlans from vlans", function() {
927+ var i, vlan, vlans = [];
928+ for(i = 0; i < 3; i++) {
929+ vlan = {
930+ id: i,
931+ vid: i,
932+ fabric: 0
933+ };
934+ vlans.push(vlan);
935+ }
936+
937+ expect(
938+ removeDefaultVLANIfVLAN(
939+ vlans, "vlan")).toEqual([vlans[1], vlans[2]]);
940+ });
941+});
942+
943+
944 describe("NodeNetworkingController", function() {
945 // Load the MAAS module.
946 beforeEach(module("MAAS"));
947@@ -2498,10 +2540,11 @@
948 var parent = {
949 id: makeInteger(0, 100)
950 };
951+ var subnet = {};
952 $scope.newInterface = {
953 type: "alias",
954 mode: "auto",
955- subnet: {},
956+ subnet: subnet,
957 parent: parent
958 };
959 $scope.selectedInterfaces = [{}];
960@@ -2511,11 +2554,12 @@
961 expect($scope.saveInterfaceLink).toHaveBeenCalledWith({
962 id: parent.id,
963 mode: "auto",
964- subnet: $scope.newInterface.subnet,
965+ subnet: subnet,
966 ip_address: ""
967 });
968 expect($scope.selectedMode).toBeNull();
969 expect($scope.selectedInterfaces).toEqual([]);
970+ expect($scope.newInterface).toEqual({});
971 });
972
973 it("calls createVLANInterface with correct params", function() {
974@@ -2523,16 +2567,18 @@
975 var parent = {
976 id: makeInteger(0, 100)
977 };
978+ var vlan = {
979+ id: makeInteger(0, 100)
980+ };
981+ var subnet = {
982+ id: makeInteger(0, 100)
983+ };
984 $scope.newInterface = {
985 type: "vlan",
986 mode: "auto",
987 parent: parent,
988- vlan: {
989- id: makeInteger(0, 100)
990- },
991- subnet: {
992- id: makeInteger(0, 100)
993- }
994+ vlan: vlan,
995+ subnet: subnet
996 };
997 $scope.selectedInterfaces = [{}];
998 $scope.selectedMode = "add";
999@@ -2543,12 +2589,13 @@
1000 expect(NodesManager.createVLANInterface).toHaveBeenCalledWith(
1001 node, {
1002 parent: parent.id,
1003- vlan: $scope.newInterface.vlan.id,
1004+ vlan: vlan.id,
1005 mode: "auto",
1006- subnet: $scope.newInterface.subnet.id
1007+ subnet: subnet.id
1008 });
1009 expect($scope.selectedMode).toBeNull();
1010 expect($scope.selectedInterfaces).toEqual([]);
1011+ expect($scope.newInterface).toEqual({});
1012 });
1013
1014 it("calls add again with type", function() {
1015
1016=== added file 'src/maasserver/static/js/angular/filters/remove_default_vlan.js'
1017--- src/maasserver/static/js/angular/filters/remove_default_vlan.js 1970-01-01 00:00:00 +0000
1018+++ src/maasserver/static/js/angular/filters/remove_default_vlan.js 2015-10-09 21:26:30 +0000
1019@@ -0,0 +1,17 @@
1020+/* Copyright 2015 Canonical Ltd. This software is licensed under the
1021+ * GNU Affero General Public License version 3 (see the file LICENSE).
1022+ *
1023+ * MAAS Filter to remove the default VLAN as an option.
1024+ */
1025+
1026+angular.module('MAAS').filter('removeDefaultVLAN', function() {
1027+ return function(vlans) {
1028+ var filtered = [];
1029+ angular.forEach(vlans, function(vlan) {
1030+ if(vlan.vid !== 0) {
1031+ filtered.push(vlan);
1032+ }
1033+ });
1034+ return filtered;
1035+ };
1036+});
1037
1038=== added file 'src/maasserver/static/js/angular/filters/tests/test_remove_default_vlan.js'
1039--- src/maasserver/static/js/angular/filters/tests/test_remove_default_vlan.js 1970-01-01 00:00:00 +0000
1040+++ src/maasserver/static/js/angular/filters/tests/test_remove_default_vlan.js 2015-10-09 21:26:30 +0000
1041@@ -0,0 +1,30 @@
1042+/* Copyright 2015 Canonical Ltd. This software is licensed under the
1043+ * GNU Affero General Public License version 3 (see the file LICENSE).
1044+ *
1045+ * Unit tests for removeDefaultVLAN.
1046+ */
1047+
1048+describe("removeDefaultVLAN", function() {
1049+
1050+ // Load the MAAS module.
1051+ beforeEach(module("MAAS"));
1052+
1053+ // Load the removeDefaultVLAN.
1054+ var removeDefaultVLAN;
1055+ beforeEach(inject(function($filter) {
1056+ removeDefaultVLAN = $filter("removeDefaultVLAN");
1057+ }));
1058+
1059+ it("only returns vlans without vid 0", function() {
1060+ var i, vlan, vlans = [];
1061+ for(i = 0; i < 3; i++) {
1062+ vlan = {
1063+ id: i,
1064+ vid: i,
1065+ fabric: 0
1066+ };
1067+ vlans.push(vlan);
1068+ }
1069+ expect(removeDefaultVLAN(vlans)).toEqual([vlans[1], vlans[2]]);
1070+ });
1071+});
1072
1073=== modified file 'src/maasserver/static/partials/node-details.html'
1074--- src/maasserver/static/partials/node-details.html 2015-10-09 07:58:28 +0000
1075+++ src/maasserver/static/partials/node-details.html 2015-10-09 21:26:30 +0000
1076@@ -340,9 +340,9 @@
1077 <label class="checkbox-label" for="{$ getUniqueKey(interface) $}"></label>
1078 </div>
1079 <div class="table__data table__column--15" data-ng-show="column == 'name'">
1080- <span class="ng-hide" data-ng-show="interface.type == 'alias'">{$ interface.name $}</span>
1081+ <span class="ng-hide" data-ng-show="interface.type == 'alias' || interface.type == 'vlan'">{$ interface.name $}</span>
1082 <input type="text" class="table__input"
1083- data-ng-show="interface.type != 'alias'"
1084+ data-ng-show="interface.type != 'alias' && interface.type != 'vlan'"
1085 data-ng-model="interface.name"
1086 data-maas-enter-blur
1087 data-ng-focus="setFocusInterface(interface)"
1088@@ -368,7 +368,7 @@
1089 <div class="table__data table__column--14">
1090 <select class="table__input" name="fabric" id="fabric"
1091 data-ng-model="interface.fabric"
1092- data-ng-disabled="interface.type == 'alias'"
1093+ data-ng-disabled="interface.type == 'alias' || interface.type == 'vlan'"
1094 data-ng-change="fabricChanged(interface)"
1095 data-ng-options="fabric as fabric.name for fabric in fabrics">
1096 </select>
1097@@ -376,9 +376,9 @@
1098 <div class="table__data table__column--14">
1099 <select class="table__input" name="vlan" id="vlan"
1100 data-ng-model="interface.vlan"
1101- data-ng-disabled="interface.type == 'alias'"
1102+ data-ng-disabled="interface.type == 'alias' || interface.vlan.vid === 0"
1103 data-ng-change="saveInterface(interface)"
1104- data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | filterByFabric:interface.fabric">
1105+ data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | removeDefaultVLANIfVLAN:interface.type | filterByFabric:interface.fabric">
1106 </select>
1107 </div>
1108 <div class="table__data table__column--18">
1109@@ -453,7 +453,7 @@
1110 <select class="table__input" name="vlan" id="vlan"
1111 data-ng-model="newInterface.vlan"
1112 data-ng-disabled="newInterface.type == 'alias'"
1113- data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | filterByFabric:newInterface.parent.fabric | filterByUnusedForInterface:newInterface.parent:originalInterfaces"
1114+ data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | removeDefaultVLAN | filterByFabric:newInterface.parent.fabric | filterByUnusedForInterface:newInterface.parent:originalInterfaces"
1115 data-ng-show="newInterface.type === 'vlan'"
1116 data-ng-change="addVLANChanged()">
1117 </select>
1118
1119=== modified file 'src/maasserver/testing/factory.py'
1120--- src/maasserver/testing/factory.py 2015-09-24 16:22:12 +0000
1121+++ src/maasserver/testing/factory.py 2015-10-09 21:26:30 +0000
1122@@ -709,8 +709,6 @@
1123 return fannetwork
1124
1125 def make_Fabric(self, name=None):
1126- if name is None:
1127- name = self.make_name('fabric')
1128 fabric = Fabric(name=name)
1129 fabric.save()
1130 return fabric
1131@@ -727,8 +725,6 @@
1132
1133 def make_VLAN(self, name=None, vid=None, fabric=None):
1134 assert vid != 0, "VID=0 VLANs are auto-created"
1135- if name is None:
1136- name = self.make_name('vlan')
1137 if fabric is None:
1138 fabric = Fabric.objects.get_default_fabric()
1139 if vid is None:
1140
1141=== modified file 'src/maasserver/tests/test_forms_fabric.py'
1142--- src/maasserver/tests/test_forms_fabric.py 2015-08-13 19:29:08 +0000
1143+++ src/maasserver/tests/test_forms_fabric.py 2015-10-09 21:26:30 +0000
1144@@ -22,13 +22,6 @@
1145
1146 class TestFabricForm(MAASServerTestCase):
1147
1148- def test__requires_name(self):
1149- form = FabricForm({})
1150- self.assertFalse(form.is_valid(), form.errors)
1151- self.assertEquals({
1152- "name": ["This field is required."]
1153- }, form.errors)
1154-
1155 def test__creates_fabric(self):
1156 fabric_name = factory.make_name("fabric")
1157 form = FabricForm({
1158
1159=== modified file 'src/maasserver/tests/test_forms_space.py'
1160--- src/maasserver/tests/test_forms_space.py 2015-09-01 18:29:48 +0000
1161+++ src/maasserver/tests/test_forms_space.py 2015-10-09 21:26:30 +0000
1162@@ -39,13 +39,13 @@
1163 self.assertEquals(space_name, space.name)
1164
1165 def test__doest_require_name_on_update(self):
1166- space = factory.make_Fabric()
1167+ space = factory.make_Space()
1168 form = SpaceForm(instance=space, data={})
1169 self.assertTrue(form.is_valid(), form.errors)
1170
1171 def test__updates_space(self):
1172 new_name = factory.make_name("space")
1173- space = factory.make_Fabric()
1174+ space = factory.make_Space()
1175 form = SpaceForm(instance=space, data={
1176 "name": new_name,
1177 })
1178
1179=== modified file 'src/maasserver/tests/test_forms_vlan.py'
1180--- src/maasserver/tests/test_forms_vlan.py 2015-08-18 15:55:43 +0000
1181+++ src/maasserver/tests/test_forms_vlan.py 2015-10-09 21:26:30 +0000
1182@@ -24,12 +24,11 @@
1183
1184 class TestVLANForm(MAASServerTestCase):
1185
1186- def test__requires_name(self):
1187+ def test__requires_vid(self):
1188 fabric = factory.make_Fabric()
1189 form = VLANForm(fabric=fabric, data={})
1190 self.assertFalse(form.is_valid(), form.errors)
1191 self.assertEquals({
1192- "name": ["This field is required."],
1193 "vid": [
1194 "This field is required.",
1195 "Vid must be between 0 and 4095.",
1196@@ -55,6 +54,16 @@
1197 form = VLANForm(instance=vlan, data={})
1198 self.assertTrue(form.is_valid(), form.errors)
1199
1200+ def test__cannot_edit_default_vlan(self):
1201+ fabric = factory.make_Fabric()
1202+ form = VLANForm(instance=fabric.get_default_vlan(), data={})
1203+ self.assertFalse(form.is_valid(), form.errors)
1204+ self.assertEquals({
1205+ "__all__": [
1206+ "Cannot modify the default VLAN for a fabric.",
1207+ ],
1208+ }, form.errors)
1209+
1210 def test__updates_vlan(self):
1211 vlan = factory.make_VLAN()
1212 new_name = factory.make_name("vlan")
1213
1214=== modified file 'src/maasserver/websockets/handlers/fabric.py'
1215--- src/maasserver/websockets/handlers/fabric.py 2015-10-04 22:28:33 +0000
1216+++ src/maasserver/websockets/handlers/fabric.py 2015-10-09 21:26:30 +0000
1217@@ -35,6 +35,7 @@
1218 ]
1219
1220 def dehydrate(self, obj, data, for_list=False):
1221+ data["name"] = obj.get_name()
1222 data["vlan_ids"] = [
1223 vlan.id
1224 for vlan in obj.vlan_set.all()
1225
1226=== modified file 'src/maasserver/websockets/handlers/tests/test_fabric.py'
1227--- src/maasserver/websockets/handlers/tests/test_fabric.py 2015-09-16 20:59:38 +0000
1228+++ src/maasserver/websockets/handlers/tests/test_fabric.py 2015-10-09 21:26:30 +0000
1229@@ -26,7 +26,7 @@
1230 def dehydrate_fabric(self, fabric):
1231 data = {
1232 "id": fabric.id,
1233- "name": fabric.name,
1234+ "name": fabric.get_name(),
1235 "updated": dehydrate_datetime(fabric.updated),
1236 "created": dehydrate_datetime(fabric.created),
1237 "vlan_ids": [
1238
1239=== modified file 'src/maasserver/websockets/handlers/tests/test_vlan.py'
1240--- src/maasserver/websockets/handlers/tests/test_vlan.py 2015-09-16 20:59:38 +0000
1241+++ src/maasserver/websockets/handlers/tests/test_vlan.py 2015-10-09 21:26:30 +0000
1242@@ -26,7 +26,7 @@
1243 def dehydrate_vlan(self, vlan):
1244 data = {
1245 "id": vlan.id,
1246- "name": vlan.name,
1247+ "name": vlan.get_name(),
1248 "vid": vlan.vid,
1249 "fabric": vlan.fabric_id,
1250 "updated": dehydrate_datetime(vlan.updated),
1251
1252=== modified file 'src/maasserver/websockets/handlers/vlan.py'
1253--- src/maasserver/websockets/handlers/vlan.py 2015-09-16 20:59:38 +0000
1254+++ src/maasserver/websockets/handlers/vlan.py 2015-10-09 21:26:30 +0000
1255@@ -36,6 +36,7 @@
1256 ]
1257
1258 def dehydrate(self, obj, data, for_list=False):
1259+ data["name"] = obj.get_name()
1260 data["subnet_ids"] = [
1261 subnet.id
1262 for subnet in obj.subnet_set.all()