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: 1287
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
lp:~gz/maas/1.2_add_db_kernel_params updated
1286. By Martin Packman

Document and test kernel_params a little

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.

lp:~gz/maas/1.2_add_db_kernel_params updated
1287. By Martin Packman

Rename added field to kernel_opts from kernel_params

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

Subscribers

People subscribed via source and target branches

to status/vote changes: