Merge lp:~andreserl/maas/lp1460193 into lp:~maas-committers/maas/trunk

Proposed by Andres Rodriguez
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 4262
Proposed branch: lp:~andreserl/maas/lp1460193
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 770 lines (+617/-2)
10 files modified
etc/maas/templates/commissioning-user-data/user_data.template (+7/-0)
src/maasserver/api/nodes.py (+19/-0)
src/maasserver/migrations/0176_add_enable_ssh_and_block_poweroff_commissioning_attributes.py (+495/-0)
src/maasserver/models/node.py (+28/-0)
src/maasserver/models/tests/test_node.py (+37/-0)
src/maasserver/tests/test_preseed.py (+1/-1)
src/maasserver/websockets/handlers/device.py (+2/-0)
src/maasserver/websockets/handlers/node.py (+2/-0)
src/metadataserver/api.py (+4/-1)
src/metadataserver/tests/test_api.py (+22/-0)
To merge this branch: bzr merge lp:~andreserl/maas/lp1460193
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Blake Rouse (community) Approve
Review via email: mp+269965@code.launchpad.net

Commit message

Set the user as the owner during commissioning, which allows the import of the user's SSH key into the commissioning environment. However, only import SSH keys if the 'enable_ssh' parameters is enabled. Additionally, allow blocking the power off the machine after commissioning, by enabling 'block_poweroff'.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

This is still needs more work.

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

Looks good. Just two comments about coding style.

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

The attempt to merge lp:~andreserl/maas/lp1460193 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates Release [63.5 kB]
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [234 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [135 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [619 kB]
Get:6 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [312 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/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,365 kB in 3s (443 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-psyc...

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/templates/commissioning-user-data/user_data.template'
2--- etc/maas/templates/commissioning-user-data/user_data.template 2015-03-26 22:43:10 +0000
3+++ etc/maas/templates/commissioning-user-data/user_data.template 2015-09-15 14:12:24 +0000
4@@ -142,7 +142,14 @@
5 fi
6 }
7
8+write_block_poweroff() {
9+ touch /tmp/block-poweroff
10+}
11+
12 main() {
13+ {{if node.block_poweroff}}
14+ write_block_poweroff
15+ {{endif}}
16 write_poweroff_job
17 write_systemd_poweroff_job
18
19
20=== modified file 'src/maasserver/api/nodes.py'
21--- src/maasserver/api/nodes.py 2015-09-09 18:38:34 +0000
22+++ src/maasserver/api/nodes.py 2015-09-15 14:12:24 +0000
23@@ -35,6 +35,7 @@
24 OperationsHandler,
25 )
26 from maasserver.api.utils import (
27+ extract_bool,
28 get_mandatory_param,
29 get_oauth_token,
30 get_optional_list,
31@@ -508,6 +509,13 @@
32 def commission(self, request, system_id):
33 """Begin commissioning process for a node.
34
35+ :param enable_ssh: Whether to enable SSH for the commissioning
36+ environment using the user's SSH key(s).
37+ :type enable_ssh: bool ('0' for False, '1' for True)
38+ :param block_poweroff: Whether to prevent the power off the node
39+ after the commissioning has completed.
40+ :type block_poweroff: bool ('0' for False, '1' for True)
41+
42 A node in the 'ready', 'declared' or 'failed test' state may
43 initiate a commissioning cycle where it is checked out and tested
44 in preparation for transitioning to the 'ready' state. If it is
45@@ -517,7 +525,18 @@
46
47 Returns 404 if the node is not found.
48 """
49+ enable_ssh = get_optional_param(request.data, 'enable_ssh', 0)
50+ block_poweroff = get_optional_param(request.data, 'block_poweroff', 0)
51+
52 node = get_object_or_404(Node, system_id=system_id)
53+
54+ if enable_ssh:
55+ enable_ssh = extract_bool(enable_ssh)
56+ if block_poweroff:
57+ block_poweroff = extract_bool(block_poweroff)
58+ node.set_commissioning_parameters(
59+ enable_ssh=enable_ssh, block_poweroff=block_poweroff)
60+
61 form_class = get_action_form(user=request.user)
62 form = form_class(
63 node, data={NodeActionForm.input_name: Commission.name})
64
65=== added file 'src/maasserver/migrations/0176_add_enable_ssh_and_block_poweroff_commissioning_attributes.py'
66--- src/maasserver/migrations/0176_add_enable_ssh_and_block_poweroff_commissioning_attributes.py 1970-01-01 00:00:00 +0000
67+++ src/maasserver/migrations/0176_add_enable_ssh_and_block_poweroff_commissioning_attributes.py 2015-09-15 14:12:24 +0000
68@@ -0,0 +1,495 @@
69+from django.db import models
70+from south.db import db
71+# -*- coding: utf-8 -*-
72+from south.utils import datetime_utils as datetime
73+from south.v2 import SchemaMigration
74+
75+
76+class Migration(SchemaMigration):
77+
78+ def forwards(self, orm):
79+ # Adding field 'Node.enable_ssh'
80+ db.add_column(u'maasserver_node', 'enable_ssh',
81+ self.gf('django.db.models.fields.BooleanField')(default=False),
82+ keep_default=False)
83+
84+ # Adding field 'Node.block_poweroff'
85+ db.add_column(u'maasserver_node', 'block_poweroff',
86+ self.gf('django.db.models.fields.BooleanField')(default=False),
87+ keep_default=False)
88+
89+
90+ def backwards(self, orm):
91+ # Deleting field 'Node.enable_ssh'
92+ db.delete_column(u'maasserver_node', 'enable_ssh')
93+
94+ # Deleting field 'Node.block_poweroff'
95+ db.delete_column(u'maasserver_node', 'block_poweroff')
96+
97+
98+ models = {
99+ u'auth.group': {
100+ 'Meta': {'object_name': 'Group'},
101+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
102+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
103+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
104+ },
105+ u'auth.permission': {
106+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
107+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
108+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
109+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
110+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
111+ },
112+ u'auth.user': {
113+ 'Meta': {'object_name': 'User'},
114+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
115+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
116+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
117+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
118+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
120+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
121+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
122+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
123+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
124+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
125+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
126+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
127+ },
128+ u'contenttypes.contenttype': {
129+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
130+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
131+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
133+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
134+ },
135+ u'maasserver.blockdevice': {
136+ 'Meta': {'ordering': "[u'id']", 'unique_together': "((u'node', u'name'),)", 'object_name': 'BlockDevice'},
137+ 'block_size': ('django.db.models.fields.IntegerField', [], {}),
138+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
139+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'id_path': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
141+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
142+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
143+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
144+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
145+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
146+ },
147+ u'maasserver.bootresource': {
148+ 'Meta': {'unique_together': "((u'name', u'architecture'),)", 'object_name': 'BootResource'},
149+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
150+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
151+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
152+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
153+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
154+ 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
155+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
156+ },
157+ u'maasserver.bootresourcefile': {
158+ 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
159+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
160+ 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
161+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
162+ 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'root-tgz'", 'max_length': '20'}),
163+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
164+ 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
165+ 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
166+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
167+ },
168+ u'maasserver.bootresourceset': {
169+ 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
170+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
171+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
173+ 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
174+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
175+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
176+ },
177+ u'maasserver.bootsource': {
178+ 'Meta': {'object_name': 'BootSource'},
179+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
180+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
181+ 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
182+ 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
183+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
184+ 'url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'})
185+ },
186+ u'maasserver.bootsourcecache': {
187+ 'Meta': {'object_name': 'BootSourceCache'},
188+ 'arch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
189+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
190+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
191+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
192+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
193+ 'os': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
194+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
195+ 'subarch': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
196+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
197+ },
198+ u'maasserver.bootsourceselection': {
199+ 'Meta': {'unique_together': "((u'boot_source', u'os', u'release'),)", 'object_name': 'BootSourceSelection'},
200+ 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
201+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
202+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
203+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204+ 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
205+ 'os': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
206+ 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
207+ 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
208+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
209+ },
210+ u'maasserver.cacheset': {
211+ 'Meta': {'object_name': 'CacheSet'},
212+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
213+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
214+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
215+ },
216+ u'maasserver.candidatename': {
217+ 'Meta': {'unique_together': "((u'name', u'position'),)", 'object_name': 'CandidateName'},
218+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
219+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
220+ 'position': ('django.db.models.fields.IntegerField', [], {})
221+ },
222+ u'maasserver.componenterror': {
223+ 'Meta': {'object_name': 'ComponentError'},
224+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
225+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
226+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
227+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
229+ },
230+ u'maasserver.config': {
231+ 'Meta': {'object_name': 'Config'},
232+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
233+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
234+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
235+ },
236+ u'maasserver.downloadprogress': {
237+ 'Meta': {'object_name': 'DownloadProgress'},
238+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
239+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
240+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
241+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
242+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
244+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
245+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
246+ },
247+ u'maasserver.event': {
248+ 'Meta': {'object_name': 'Event'},
249+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
250+ 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
251+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
252+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
253+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
254+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
255+ },
256+ u'maasserver.eventtype': {
257+ 'Meta': {'object_name': 'EventType'},
258+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
259+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
260+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
261+ 'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
262+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
263+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
264+ },
265+ u'maasserver.fabric': {
266+ 'Meta': {'object_name': 'Fabric'},
267+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
268+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
270+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
271+ },
272+ u'maasserver.filestorage': {
273+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
274+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
275+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
276+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
277+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'3ba44986-5bb3-11e5-9f58-7c7a9140484f'", 'unique': 'True', 'max_length': '36'}),
278+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
279+ },
280+ u'maasserver.filesystem': {
281+ 'Meta': {'object_name': 'Filesystem'},
282+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
283+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.CacheSet']"}),
284+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
285+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
286+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filesystems'", 'null': 'True', 'to': u"orm['maasserver.FilesystemGroup']"}),
287+ 'fstype': ('django.db.models.fields.CharField', [], {'default': "u'ext4'", 'max_length': '20'}),
288+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
289+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
290+ 'mount_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
291+ 'mount_point': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
292+ 'partition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Partition']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
293+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
294+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
295+ },
296+ u'maasserver.filesystemgroup': {
297+ 'Meta': {'object_name': 'FilesystemGroup'},
298+ 'cache_mode': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
299+ 'cache_set': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.CacheSet']", 'null': 'True', 'blank': 'True'}),
300+ 'create_params': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
301+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
302+ 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
303+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
304+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
305+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
306+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
307+ },
308+ u'maasserver.interface': {
309+ 'Meta': {'ordering': "(u'created',)", 'object_name': 'Interface'},
310+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
311+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
312+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
313+ 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.StaticIPAddress']", 'null': 'True', 'blank': 'True'}),
314+ 'ipv4_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
315+ 'ipv6_params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
316+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'null': 'True', 'blank': 'True'}),
317+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
318+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']", 'null': 'True', 'blank': 'True'}),
319+ 'params': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
320+ 'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['maasserver.Interface']", 'null': 'True', 'through': u"orm['maasserver.InterfaceRelationship']", 'blank': 'True'}),
321+ 'tags': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'blank': 'True'}),
322+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
323+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
324+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
325+ },
326+ u'maasserver.interfacerelationship': {
327+ 'Meta': {'object_name': 'InterfaceRelationship'},
328+ 'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'parent_relationships'", 'to': u"orm['maasserver.Interface']"}),
329+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
330+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
331+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'children_relationships'", 'to': u"orm['maasserver.Interface']"}),
332+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
333+ },
334+ u'maasserver.largefile': {
335+ 'Meta': {'object_name': 'LargeFile'},
336+ 'content': ('maasserver.fields.LargeObjectField', [], {}),
337+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
338+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
339+ 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
340+ 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
341+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
342+ },
343+ u'maasserver.licensekey': {
344+ 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
345+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
346+ 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
347+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
348+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
349+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
350+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
351+ },
352+ u'maasserver.node': {
353+ 'Meta': {'object_name': 'Node'},
354+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
355+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
356+ 'bios_boot_method': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
357+ 'block_poweroff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
358+ 'boot_cluster_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
359+ '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'}),
360+ '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'}),
361+ 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
362+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
363+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
364+ 'disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
365+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
366+ 'enable_ssh': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
367+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
368+ 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
369+ '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'}),
370+ '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'}),
371+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
372+ 'hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
373+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
374+ 'installable': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
375+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
376+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
377+ 'min_hwe_kernel': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True', 'blank': 'True'}),
378+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
379+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
380+ 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
381+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
382+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "u'children'", 'null': 'True', 'blank': 'True', 'to': u"orm['maasserver.Node']"}),
383+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'max_length': '32768', 'blank': 'True'}),
384+ 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
385+ 'power_state_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
386+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
387+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
388+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
389+ 'swap_size': ('django.db.models.fields.BigIntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
390+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-3ba0c5fe-5bb3-11e5-9f58-7c7a9140484f'", 'unique': 'True', 'max_length': '41'}),
391+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
392+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
393+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
394+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
395+ },
396+ u'maasserver.nodegroup': {
397+ 'Meta': {'object_name': 'NodeGroup'},
398+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
399+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
400+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
401+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
402+ 'default_disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
403+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
404+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
405+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
406+ 'name': ('maasserver.models.nodegroup.DomainNameField', [], {'max_length': '80', 'blank': 'True'}),
407+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
408+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
409+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
410+ },
411+ u'maasserver.nodegroupinterface': {
412+ 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
413+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
414+ 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
415+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
416+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
417+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
418+ 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
419+ 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
420+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
421+ 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
422+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
423+ 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
424+ 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
425+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'on_delete': 'models.PROTECT', 'blank': 'True'}),
426+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
427+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
428+ },
429+ u'maasserver.partition': {
430+ 'Meta': {'object_name': 'Partition'},
431+ 'bootable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
432+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
433+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
434+ 'partition_table': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'partitions'", 'to': u"orm['maasserver.PartitionTable']"}),
435+ 'size': ('django.db.models.fields.BigIntegerField', [], {}),
436+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
437+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'unique': 'True', 'null': 'True', 'blank': 'True'})
438+ },
439+ u'maasserver.partitiontable': {
440+ 'Meta': {'object_name': 'PartitionTable'},
441+ 'block_device': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BlockDevice']"}),
442+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
443+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
444+ 'table_type': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '20'}),
445+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
446+ },
447+ u'maasserver.physicalblockdevice': {
448+ 'Meta': {'ordering': "[u'id']", 'object_name': 'PhysicalBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
449+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
450+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
451+ 'serial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
452+ },
453+ u'maasserver.space': {
454+ 'Meta': {'object_name': 'Space'},
455+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
456+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
457+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
458+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
459+ },
460+ u'maasserver.sshkey': {
461+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
462+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
463+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
464+ 'key': ('django.db.models.fields.TextField', [], {}),
465+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
466+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
467+ },
468+ u'maasserver.sslkey': {
469+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
470+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
471+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
472+ 'key': ('django.db.models.fields.TextField', [], {}),
473+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
474+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
475+ },
476+ u'maasserver.staticipaddress': {
477+ 'Meta': {'object_name': 'StaticIPAddress'},
478+ 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
479+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
480+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
481+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
482+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
483+ 'subnet': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Subnet']", 'null': 'True', 'blank': 'True'}),
484+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
485+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
486+ },
487+ u'maasserver.subnet': {
488+ 'Meta': {'unique_together': "((u'name', u'space'),)", 'object_name': 'Subnet'},
489+ 'cidr': ('maasserver.fields.CIDRField', [], {'unique': 'True'}),
490+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
491+ 'dns_servers': ('djorm_pgarray.fields.ArrayField', [], {'default': '[]', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
492+ 'gateway_ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
493+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
494+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
495+ 'space': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Space']", 'on_delete': 'models.PROTECT'}),
496+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
497+ 'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.VLAN']", 'on_delete': 'models.PROTECT'})
498+ },
499+ u'maasserver.tag': {
500+ 'Meta': {'object_name': 'Tag'},
501+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
502+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
503+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
504+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
505+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
506+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
507+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
508+ },
509+ u'maasserver.userprofile': {
510+ 'Meta': {'object_name': 'UserProfile'},
511+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
512+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
513+ },
514+ u'maasserver.virtualblockdevice': {
515+ 'Meta': {'ordering': "[u'id']", 'object_name': 'VirtualBlockDevice', '_ormbases': [u'maasserver.BlockDevice']},
516+ u'blockdevice_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['maasserver.BlockDevice']", 'unique': 'True', 'primary_key': 'True'}),
517+ 'filesystem_group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'virtual_devices'", 'to': u"orm['maasserver.FilesystemGroup']"}),
518+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
519+ },
520+ u'maasserver.vlan': {
521+ 'Meta': {'unique_together': "((u'vid', u'fabric'), (u'name', u'fabric'))", 'object_name': 'VLAN'},
522+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
523+ 'fabric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Fabric']"}),
524+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
525+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
526+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
527+ 'vid': ('django.db.models.fields.IntegerField', [], {})
528+ },
529+ u'maasserver.zone': {
530+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
531+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
532+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
533+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
534+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
535+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
536+ },
537+ u'piston.consumer': {
538+ 'Meta': {'object_name': 'Consumer'},
539+ 'description': ('django.db.models.fields.TextField', [], {}),
540+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
541+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
542+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
543+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
544+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
545+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
546+ },
547+ u'piston.token': {
548+ 'Meta': {'object_name': 'Token'},
549+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
550+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
551+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
552+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
553+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
554+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
555+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
556+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1442326105L'}),
557+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
558+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
559+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
560+ }
561+ }
562+
563+ complete_apps = ['maasserver']
564\ No newline at end of file
565
566=== modified file 'src/maasserver/models/node.py'
567--- src/maasserver/models/node.py 2015-09-14 19:31:45 +0000
568+++ src/maasserver/models/node.py 2015-09-15 14:12:24 +0000
569@@ -407,6 +407,12 @@
570 :ivar nodegroup: The `NodeGroup` this `Node` belongs to.
571 :ivar tags: The list of :class:`Tag`s associated with this `Node`.
572 :ivar objects: The :class:`NodeManager`.
573+ :ivar enable_ssh: An optional flag to indicate if this node can have
574+ ssh enabled during commissioning, allowing the user to ssh into the
575+ machine's commissioning environment using the user's SSH key.
576+ :ivar block_poweroff: An optional flag to indicate if this node needs to
577+ can be prevented from being powered off automatically after the
578+ commissioning has finished.
579
580 """
581
582@@ -572,6 +578,12 @@
583 # 'devices' are all the non-installable nodes.
584 devices = DeviceManager()
585
586+ # Used to determine whether to:
587+ # 1. Import the SSH Key during commissioning.
588+ # 2. Block the automatic power off during commissioning.
589+ enable_ssh = BooleanField(default=False)
590+ block_poweroff = BooleanField(default=False)
591+
592 def __unicode__(self):
593 if self.hostname:
594 return "%s (%s)" % (self.system_id, self.fqdn)
595@@ -900,6 +912,18 @@
596 self.start_commissioning(user)
597 return self
598
599+ def set_commissioning_parameters(self, enable_ssh=False,
600+ block_poweroff=False):
601+ """Set the commissioning parameters.
602+
603+ This sets the parameters to:
604+ enable_ssh - Enable SSH during commissioning.
605+ block_poweroff - Blocks the automatic poweroff after commisioning.
606+ """
607+ self.enable_ssh = enable_ssh
608+ self.block_poweroff = block_poweroff
609+ self.save()
610+
611 @transactional
612 def start_commissioning(self, user):
613 """Install OS and self-test a new node.
614@@ -925,6 +949,7 @@
615 # case the power action fails.
616 old_status = self.status
617 self.status = NODE_STATUS.COMMISSIONING
618+ self.owner = user
619 self.save()
620
621 # Prepare a transition monitor for later.
622@@ -1038,6 +1063,9 @@
623 # exceptions arising synchronously, and chain callbacks to the
624 # Deferred it returns for the asynchronous (post-commit) bits.
625 stopping = self.stop(user)
626+ if self.owner is not None:
627+ self.owner = None
628+ self.save()
629 except Exception as error:
630 maaslog.error(
631 "%s: Error when aborting commissioning: %s",
632
633=== modified file 'src/maasserver/models/tests/test_node.py'
634--- src/maasserver/models/tests/test_node.py 2015-09-14 19:31:45 +0000
635+++ src/maasserver/models/tests/test_node.py 2015-09-15 14:12:24 +0000
636@@ -1574,6 +1574,43 @@
637 NodeStateViolation, node.abort_commissioning,
638 factory.make_admin())
639
640+ def test_start_commissioning_sets_owner(self):
641+ node = factory.make_Node(
642+ status=NODE_STATUS.NEW, power_type='ether_wake',
643+ enable_ssh=True)
644+ node_start = self.patch(node, 'start')
645+ # Return a post-commit hook from Node.start().
646+ node_start.side_effect = lambda user, user_data: post_commit()
647+ admin = factory.make_admin()
648+ node.start_commissioning(admin)
649+ post_commit_hooks.reset() # Ignore these for now.
650+ node = reload_object(node)
651+ expected_attrs = {
652+ 'status': NODE_STATUS.COMMISSIONING,
653+ 'owner': admin,
654+ }
655+ self.expectThat(node.owner, Equals(admin))
656+ self.assertAttributes(node, expected_attrs)
657+
658+ def test_abort_commissioning_unsets_owner(self):
659+ node = factory.make_Node(
660+ status=NODE_STATUS.COMMISSIONING, power_type='virsh',
661+ enable_ssh=True)
662+ admin = factory.make_admin()
663+
664+ node_stop = self.patch(node, 'stop')
665+ # Return a post-commit hook from Node.stop().
666+ node_stop.side_effect = lambda user: post_commit()
667+ self.patch(Node, "_set_status")
668+
669+ with post_commit_hooks:
670+ node.abort_commissioning(admin)
671+
672+ self.assertThat(node_stop, MockCalledOnceWith(admin))
673+ self.assertThat(node._set_status, MockCalledOnceWith(
674+ node.system_id, status=NODE_STATUS.NEW))
675+ self.assertThat(node.owner, Is(None))
676+
677 def test_full_clean_logs_node_status_transition(self):
678 node = factory.make_Node(
679 status=NODE_STATUS.DEPLOYING, owner=factory.make_User())
680
681=== modified file 'src/maasserver/tests/test_preseed.py'
682--- src/maasserver/tests/test_preseed.py 2015-09-10 23:27:25 +0000
683+++ src/maasserver/tests/test_preseed.py 2015-09-15 14:12:24 +0000
684@@ -673,7 +673,7 @@
685 reporter = self.load_reporter(preseeds)
686 self.assertIsInstance(reporter, dict)
687 if curtin_supports_webhook_events():
688- self.assertEqual(['reporting'], list(reporter.keys()))
689+ self.assertEqual(['reporting', 'install'], list(reporter.keys()))
690 else:
691 self.assertEqual(['reporter'], list(reporter.keys()))
692
693
694=== modified file 'src/maasserver/websockets/handlers/device.py'
695--- src/maasserver/websockets/handlers/device.py 2015-09-14 01:43:41 +0000
696+++ src/maasserver/websockets/handlers/device.py 2015-09-15 14:12:24 +0000
697@@ -151,6 +151,8 @@
698 "hwe_kernel",
699 "gateway_link_ipv4",
700 "gateway_link_ipv6",
701+ "block_poweroff",
702+ "enable_ssh",
703 ]
704 list_fields = [
705 "system_id",
706
707=== modified file 'src/maasserver/websockets/handlers/node.py'
708--- src/maasserver/websockets/handlers/node.py 2015-09-14 01:43:41 +0000
709+++ src/maasserver/websockets/handlers/node.py 2015-09-15 14:12:24 +0000
710@@ -109,6 +109,8 @@
711 "power_state_updated",
712 "gateway_link_ipv4",
713 "gateway_link_ipv6",
714+ "block_poweroff",
715+ "enable_ssh",
716 ]
717 list_fields = [
718 "system_id",
719
720=== modified file 'src/metadataserver/api.py'
721--- src/metadataserver/api.py 2015-09-09 18:52:24 +0000
722+++ src/metadataserver/api.py 2015-09-15 14:12:24 +0000
723@@ -599,10 +599,13 @@
724 # attribute.
725 if item is None or len(item) == 0:
726 fields = list(self.fields)
727+ commissioning_without_ssh = (
728+ node.status == NODE_STATUS.COMMISSIONING and
729+ not node.enable_ssh)
730 # Add public-keys to the list of attributes, if the
731 # node has registered SSH keys.
732 keys = SSHKey.objects.get_keys_for_user(user=node.owner)
733- if not keys:
734+ if not keys or commissioning_without_ssh:
735 fields.remove('public-keys')
736 return make_list_response(sorted(fields))
737
738
739=== modified file 'src/metadataserver/tests/test_api.py'
740--- src/metadataserver/tests/test_api.py 2015-09-10 00:22:26 +0000
741+++ src/metadataserver/tests/test_api.py 2015-09-15 14:12:24 +0000
742@@ -332,6 +332,28 @@
743 self.assertNotIn(
744 'public-keys', response.content.decode('ascii').split('\n'))
745
746+ def test_public_keys_not_listed_for_comm_node_with_ssh_disabled(self):
747+ user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
748+ node = factory.make_Node(
749+ owner=user, status=NODE_STATUS.COMMISSIONING, enable_ssh=False)
750+ view_name = self.get_metadata_name('-meta-data')
751+ url = reverse(view_name, args=['latest', ''])
752+ client = make_node_client(node=node)
753+ response = client.get(url)
754+ self.assertNotIn(
755+ 'public-keys', response.content.decode('ascii').split('\n'))
756+
757+ def test_public_keys_listed_for_comm_node_with_ssh_enabled(self):
758+ user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
759+ node = factory.make_Node(
760+ owner=user, status=NODE_STATUS.COMMISSIONING, enable_ssh=True)
761+ view_name = self.get_metadata_name('-meta-data')
762+ url = reverse(view_name, args=['latest', ''])
763+ client = make_node_client(node=node)
764+ response = client.get(url)
765+ self.assertIn(
766+ 'public-keys', response.content.decode('ascii').split('\n'))
767+
768 def test_public_keys_listed_for_node_with_public_keys(self):
769 user, _ = factory.make_user_with_keys(n_keys=2, username='my-user')
770 node = factory.make_Node(owner=user)