Merge lp:~gz/maas/1.2_add_db_kernel_params into lp:maas/1.2
- 1.2_add_db_kernel_params
- Merge into 1.2
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 |
Related bugs: |
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.
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.
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
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 |
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?