Merge lp:~gz/maas/1.2_add_db_kernel_params into lp:maas/1.2

Proposed by Martin Packman
Status: Merged
Approved by: Martin Packman
Approved revision: no longer in the source branch.
Merged at revision: 1285
Proposed branch: lp:~gz/maas/1.2_add_db_kernel_params
Merge into: lp:maas/1.2
Prerequisite: lp:~gz/maas/1.2_fix_duplicated_039_migration
Diff against target: 271 lines (+220/-3)
4 files modified
src/maasserver/migrations/0043_add_tag_kernel_opts.py (+203/-0)
src/maasserver/models/tag.py (+3/-0)
src/maasserver/testing/factory.py (+4/-3)
src/maasserver/tests/test_tag.py (+10/-0)
To merge this branch: bzr merge lp:~gz/maas/1.2_add_db_kernel_params
Reviewer Review Type Date Requested Status
John A Meinel (community) Approve
Review via email: mp+133044@code.launchpad.net

Commit message

Add kernel_opts field to Tag to allow for customisation of kernel command line

Description of the change

Adds a field to Tag for holding custom kernel parameters, which is a step towards resolving bug 1044503.

The plan is to store the global options as a specially named field in Config, which will not require a migration. The values there are json, but it's fine to just use a string instead of an object. The per-tag customisation could also be done there by packing the tag name into the key, but having a string field on tag is simple enough.

The added field allows null in the hope that we may be able to distinguish "use global" from "use no params" simply.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

The actual change seems good. But I'm thinking there should be a docstring somewhere that needs updating as well. And maybe some sort of test about setting & getting the value?

review: Approve
Revision history for this message
Martin Packman (gz) wrote :

Have added to the class docstring, and integrated a little with the testing environment. Real testing can wait for when there's some actual logic I think.

Revision history for this message
Martin Packman (gz) wrote :

As suggested by John on call, have renamed kernel_params field to kernel_opts which is closer to the existing usage in provisioningserver.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'src/maasserver/migrations/0043_add_tag_kernel_opts.py'
2--- src/maasserver/migrations/0043_add_tag_kernel_opts.py 1970-01-01 00:00:00 +0000
3+++ src/maasserver/migrations/0043_add_tag_kernel_opts.py 2012-11-07 10:40:26 +0000
4@@ -0,0 +1,203 @@
5+# -*- coding: utf-8 -*-
6+import datetime
7+from south.db import db
8+from south.v2 import SchemaMigration
9+from django.db import models
10+
11+
12+class Migration(SchemaMigration):
13+
14+ def forwards(self, orm):
15+ # Adding field 'Tag.kernel_opts'
16+ db.add_column(u'maasserver_tag', 'kernel_opts',
17+ self.gf('django.db.models.fields.TextField')(null=True, blank=True),
18+ keep_default=False)
19+
20+
21+ def backwards(self, orm):
22+ # Deleting field 'Tag.kernel_opts'
23+ db.delete_column(u'maasserver_tag', 'kernel_opts')
24+
25+
26+ models = {
27+ 'auth.group': {
28+ 'Meta': {'object_name': 'Group'},
29+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
30+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
31+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
32+ },
33+ 'auth.permission': {
34+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
35+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
36+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
37+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
39+ },
40+ 'auth.user': {
41+ 'Meta': {'object_name': 'User'},
42+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
43+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
44+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
45+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
46+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
48+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
50+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
53+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
54+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
55+ },
56+ 'contenttypes.contenttype': {
57+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
58+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
59+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62+ },
63+ u'maasserver.bootimage': {
64+ 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
65+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
66+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
68+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
69+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
70+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
71+ },
72+ u'maasserver.componenterror': {
73+ 'Meta': {'object_name': 'ComponentError'},
74+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
75+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
76+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
77+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
78+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
79+ },
80+ u'maasserver.config': {
81+ 'Meta': {'object_name': 'Config'},
82+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
84+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
85+ },
86+ u'maasserver.dhcplease': {
87+ 'Meta': {'object_name': 'DHCPLease'},
88+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
89+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
90+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
91+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
92+ },
93+ u'maasserver.filestorage': {
94+ 'Meta': {'object_name': 'FileStorage'},
95+ 'content': ('metadataserver.fields.BinaryField', [], {}),
96+ 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
97+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
98+ },
99+ u'maasserver.macaddress': {
100+ 'Meta': {'object_name': 'MACAddress'},
101+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
102+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
103+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
104+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
105+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
106+ },
107+ u'maasserver.node': {
108+ 'Meta': {'object_name': 'Node'},
109+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
110+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
111+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
112+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
113+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}),
114+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
115+ 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
116+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
117+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
118+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
119+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
120+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
121+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
122+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
123+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
124+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
125+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-8b729164-2805-11e2-bc60-fa163e384ad1'", 'unique': 'True', 'max_length': '41'}),
126+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
127+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}),
128+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
129+ },
130+ u'maasserver.nodegroup': {
131+ 'Meta': {'object_name': 'NodeGroup'},
132+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
133+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}),
134+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
135+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
136+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
137+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
138+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
139+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
140+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
141+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
142+ },
143+ u'maasserver.nodegroupinterface': {
144+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
145+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
146+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
147+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
148+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
149+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
150+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
151+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
152+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
153+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
154+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
155+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
156+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
157+ },
158+ u'maasserver.sshkey': {
159+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
160+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
161+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
162+ 'key': ('django.db.models.fields.TextField', [], {}),
163+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
164+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
165+ },
166+ u'maasserver.tag': {
167+ 'Meta': {'object_name': 'Tag'},
168+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
169+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
170+ 'definition': ('django.db.models.fields.TextField', [], {}),
171+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
173+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
174+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
175+ },
176+ u'maasserver.userprofile': {
177+ 'Meta': {'object_name': 'UserProfile'},
178+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
179+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
180+ },
181+ 'piston.consumer': {
182+ 'Meta': {'object_name': 'Consumer'},
183+ 'description': ('django.db.models.fields.TextField', [], {}),
184+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
185+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
186+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
187+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
188+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
189+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"})
190+ },
191+ 'piston.token': {
192+ 'Meta': {'object_name': 'Token'},
193+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
194+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
195+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}),
196+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
198+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
199+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
200+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1352201505L'}),
201+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
202+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}),
203+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
204+ }
205+ }
206+
207+ complete_apps = ['maasserver']
208
209=== modified file 'src/maasserver/models/tag.py'
210--- src/maasserver/models/tag.py 2012-10-10 10:25:28 +0000
211+++ src/maasserver/models/tag.py 2012-11-07 10:40:26 +0000
212@@ -89,6 +89,8 @@
213 tag.
214 :ivar comment: A long-form description for humans about what this tag is
215 trying to accomplish.
216+ :ivar kernel_opts: Optional kernel command-line parameters string to be
217+ used in the PXE config for nodes with this tags.
218 :ivar objects: The :class:`TagManager`.
219 """
220
221@@ -101,6 +103,7 @@
222 validators=[RegexValidator(_tag_name_regex)])
223 definition = TextField()
224 comment = TextField(blank=True)
225+ kernel_opts = TextField(blank=True, null=True)
226
227 objects = TagManager()
228
229
230=== modified file 'src/maasserver/testing/factory.py'
231--- src/maasserver/testing/factory.py 2012-10-30 10:25:24 +0000
232+++ src/maasserver/testing/factory.py 2012-11-07 10:40:26 +0000
233@@ -251,14 +251,15 @@
234 key.save()
235 return key
236
237- def make_tag(self, name=None, definition=None, comment='', created=None,
238- updated=None):
239+ def make_tag(self, name=None, definition=None, comment='',
240+ kernel_opts=None, created=None, updated=None):
241 if name is None:
242 name = self.make_name('tag')
243 if definition is None:
244 # Is there a 'node' in this xml?
245 definition = '//node'
246- tag = Tag(name=name, definition=definition, comment=comment)
247+ tag = Tag(name=name, definition=definition, comment=comment,
248+ kernel_opts=kernel_opts)
249 self._save_node_unchecked(tag)
250 # Update the 'updated'/'created' fields with a call to 'update'
251 # preventing a call to save() from overriding the values.
252
253=== modified file 'src/maasserver/tests/test_tag.py'
254--- src/maasserver/tests/test_tag.py 2012-10-10 09:41:48 +0000
255+++ src/maasserver/tests/test_tag.py 2012-11-07 10:40:26 +0000
256@@ -29,6 +29,16 @@
257 self.assertEqual('tag-name', tag.name)
258 self.assertEqual('//node[@id=display]', tag.definition)
259 self.assertEqual('', tag.comment)
260+ self.assertIs(None, tag.kernel_opts)
261+ self.assertIsNot(None, tag.updated)
262+ self.assertIsNot(None, tag.created)
263+
264+ def test_factory_make_tag_with_hardware_details(self):
265+ tag = factory.make_tag('a-tag', 'true', kernel_opts="console=ttyS0")
266+ self.assertEqual('a-tag', tag.name)
267+ self.assertEqual('true', tag.definition)
268+ self.assertEqual('', tag.comment)
269+ self.assertEqual('console=ttyS0', tag.kernel_opts)
270 self.assertIsNot(None, tag.updated)
271 self.assertIsNot(None, tag.created)
272

Subscribers

People subscribed via source and target branches

to status/vote changes: