Merge lp:~racb/maas/subarch-model into lp:~maas-committers/maas/trunk
- subarch-model
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1105 |
Proposed branch: | lp:~racb/maas/subarch-model |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
735 lines (+503/-49) 11 files modified
src/maasserver/api.py (+26/-3) src/maasserver/enum.py (+6/-6) src/maasserver/messages.py (+1/-1) src/maasserver/migrations/0031_node_architecture_field_size.py (+190/-0) src/maasserver/migrations/0032_node_subarch.py (+191/-0) src/maasserver/models/node.py (+1/-1) src/maasserver/preseed.py (+1/-0) src/maasserver/tests/test_api.py (+70/-6) src/maasserver/tests/test_node.py (+1/-1) src/maasserver/tests/test_node_constraint_filter.py (+8/-6) src/maasserver/tests/test_preseed.py (+8/-25) |
To merge this branch: | bzr merge lp:~racb/maas/subarch-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+126712@code.launchpad.net |
Commit message
Add subarchitecture support to the Node model
This also requires an addition to the enlistment API. A new field "subarchitecture", if specified, specifies the subarchitecture of the machine being enlisted. The corresponding change in maas-enlist is tracked in bug 1056816.
Description of the change
Martin Packman (gz) wrote : | # |
Gavin Panella (allenap) wrote : | # |
Looks good! A few comments, but they're all very minor.
[1]
+ architecture = factory.
+ filter(lambda choice: choice[
+ ARCHITECTURE_
On the whole we try to avoid filter and map, because comprehensions
are normally much easier to read, and because their behaviour changes
in Python 3. I think the above would clearer as:
[choice for choice in ARCHITECTURE_
if choice[
[2]
+ parsed_result = json.loads(
+
+ self.assertEqua
+ self.assertIn(
Don't parse the result until after the response has been checked; it
might fail and then we know less about the failure. How about:
If the response is not OK or the content-type is not JSON, this will
blow up with the mismatch and the response content as detail.
[3]
+ self.assertIn(
There's assertStartsWith too, if you just want to check that it's
text/plain.
[4]
+ self.assertEqua
Sentences end in a full-stop... unless the convention elsewhere in the
codebase to not do that. Very minor point, but it makes a difference
to the overall polish.
[5]
+ self.assertEqua
+ self.assertEqua
Failures would be more informative if this were done in a single
assertion:
[6]
- prefix, node.architecture, release, node.hostname)
+ prefix, node.architectu
+ release, node.hostname)
Because the components are delimited by _, perhaps replace / in the
architecture with - or : instead?
Robie Basak (racb) wrote : | # |
Thanks for the review, Gavin!
[1] Done
[2] This was the pattern followed before, but as I've touched every test that followed this pattern I've changed them all to check the response status. This should cause a test failure to be more useful to understand.
[3] assertIn is the pattern followed in all the existing tests, and this change only touches some of them. So I've kept this the same to stay consistent. I have dropped the "charset=utf-8" part though, as I don't think this is really what I'm testing.
[4] Done
[5] Done
[6] node_template_name must follow the template filename layout defined in src/maasserver/
Raphaël Badin (rvb) wrote : | # |
Wait, migrations number 29 and 30 have been taken already. You need to recreate your migrations. That's because the entire state of the db is present in each transaction file.
You need to
- delete your migrations
- merge trunk
- regenerate your migrations, they should be numbered 31 and 32.
Preview Diff
1 | === modified file 'src/maasserver/api.py' |
2 | --- src/maasserver/api.py 2012-09-28 12:29:50 +0000 |
3 | +++ src/maasserver/api.py 2012-09-28 17:03:22 +0000 |
4 | @@ -578,8 +578,31 @@ |
5 | :rtype: :class:`maasserver.models.Node`. |
6 | :raises: ValidationError |
7 | """ |
8 | + |
9 | + # For backwards compatibilty reasons, requests may be sent with: |
10 | + # architecture with a '/' in it: use normally |
11 | + # architecture without a '/' and no subarchitecture: assume 'generic' |
12 | + # architecture without a '/' and a subarchitecture: use as specified |
13 | + # architecture with a '/' and a subarchitecture: error |
14 | + given_arch = request.data.get('architecture', None) |
15 | + given_subarch = request.data.get('subarchitecture', None) |
16 | + altered_query_data = request.data.copy() |
17 | + if given_arch and '/' in given_arch: |
18 | + if given_subarch: |
19 | + # architecture with a '/' and a subarchitecture: error |
20 | + raise ValidationError('Subarchitecture cannot be specified twice.') |
21 | + # architecture with a '/' in it: use normally |
22 | + elif given_arch: |
23 | + if given_subarch: |
24 | + # architecture without a '/' and a subarchitecture: use as specified |
25 | + altered_query_data['architecture'] = '/'.join([given_arch, given_subarch]) |
26 | + del altered_query_data['subarchitecture'] |
27 | + else: |
28 | + # architecture without a '/' and no subarchitecture: assume 'generic' |
29 | + altered_query_data['architecture'] += '/generic' |
30 | + |
31 | Form = get_node_create_form(request.user) |
32 | - form = Form(request.data) |
33 | + form = Form(altered_query_data) |
34 | if form.is_valid(): |
35 | return form.save() |
36 | else: |
37 | @@ -1514,12 +1537,12 @@ |
38 | # Default to i386 as a works-for-all solution. This will not support |
39 | # non-x86 architectures, but for now this assumption holds. |
40 | node = None |
41 | - arch, subarch = ARCHITECTURE.i386, "generic" |
42 | + arch, subarch = ARCHITECTURE.i386.split('/') |
43 | preseed_url = compose_enlistment_preseed_url() |
44 | hostname = 'maas-enlist' |
45 | else: |
46 | node = macaddress.node |
47 | - arch, subarch = node.architecture, "generic" |
48 | + arch, subarch = node.architecture.split('/') |
49 | preseed_url = compose_preseed_url(node) |
50 | hostname = node.hostname |
51 | |
52 | |
53 | === modified file 'src/maasserver/enum.py' |
54 | --- src/maasserver/enum.py 2012-09-19 09:50:46 +0000 |
55 | +++ src/maasserver/enum.py 2012-09-28 17:03:22 +0000 |
56 | @@ -104,18 +104,18 @@ |
57 | class ARCHITECTURE: |
58 | """List of supported architectures.""" |
59 | #: |
60 | - i386 = 'i386' |
61 | - #: |
62 | - amd64 = 'amd64' |
63 | - #: |
64 | - armhf = 'armhf' |
65 | + i386 = 'i386/generic' |
66 | + #: |
67 | + amd64 = 'amd64/generic' |
68 | + #: |
69 | + armhf_highbank = 'armhf/highbank' |
70 | |
71 | |
72 | # Architecture names. |
73 | ARCHITECTURE_CHOICES = ( |
74 | (ARCHITECTURE.i386, "i386"), |
75 | (ARCHITECTURE.amd64, "amd64"), |
76 | - (ARCHITECTURE.armhf, "armhf"), |
77 | + (ARCHITECTURE.armhf_highbank, "armhf/highbank"), |
78 | ) |
79 | |
80 | |
81 | |
82 | === modified file 'src/maasserver/messages.py' |
83 | --- src/maasserver/messages.py 2012-04-16 10:00:51 +0000 |
84 | +++ src/maasserver/messages.py 2012-09-28 17:03:22 +0000 |
85 | @@ -111,7 +111,7 @@ |
86 | "instance": { |
87 | "hostname": "sun", |
88 | "system_id": "node-17ca41c2-6c39-11e1-a961-00219bd0a2de", |
89 | - "architecture": "i386", |
90 | + "architecture": "i386/generic", |
91 | [...] |
92 | } |
93 | } |
94 | |
95 | === added file 'src/maasserver/migrations/0031_node_architecture_field_size.py' |
96 | --- src/maasserver/migrations/0031_node_architecture_field_size.py 1970-01-01 00:00:00 +0000 |
97 | +++ src/maasserver/migrations/0031_node_architecture_field_size.py 2012-09-28 17:03:22 +0000 |
98 | @@ -0,0 +1,190 @@ |
99 | +# -*- coding: utf-8 -*- |
100 | +import datetime |
101 | +from south.db import db |
102 | +from south.v2 import SchemaMigration |
103 | +from django.db import models |
104 | + |
105 | + |
106 | +class Migration(SchemaMigration): |
107 | + |
108 | + def forwards(self, orm): |
109 | + |
110 | + # Changing field 'Node.architecture' |
111 | + db.alter_column(u'maasserver_node', 'architecture', self.gf('django.db.models.fields.CharField')(max_length=31)) |
112 | + |
113 | + def backwards(self, orm): |
114 | + |
115 | + # Changing field 'Node.architecture' |
116 | + db.alter_column(u'maasserver_node', 'architecture', self.gf('django.db.models.fields.CharField')(max_length=10)) |
117 | + |
118 | + models = { |
119 | + 'auth.group': { |
120 | + 'Meta': {'object_name': 'Group'}, |
121 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
122 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
123 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
124 | + }, |
125 | + 'auth.permission': { |
126 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
127 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
128 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
129 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
130 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
131 | + }, |
132 | + 'auth.user': { |
133 | + 'Meta': {'object_name': 'User'}, |
134 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
135 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
136 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
137 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
138 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
139 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
140 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
141 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
142 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
143 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
144 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
145 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
146 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
147 | + }, |
148 | + 'contenttypes.contenttype': { |
149 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
150 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
151 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
152 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
153 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
154 | + }, |
155 | + u'maasserver.bootimage': { |
156 | + 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
157 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
158 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
159 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
160 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
161 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
162 | + }, |
163 | + u'maasserver.config': { |
164 | + 'Meta': {'object_name': 'Config'}, |
165 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
166 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
167 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
168 | + }, |
169 | + u'maasserver.dhcplease': { |
170 | + 'Meta': {'object_name': 'DHCPLease'}, |
171 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
172 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
173 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
174 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
175 | + }, |
176 | + u'maasserver.filestorage': { |
177 | + 'Meta': {'object_name': 'FileStorage'}, |
178 | + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), |
179 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
180 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
181 | + }, |
182 | + u'maasserver.macaddress': { |
183 | + 'Meta': {'object_name': 'MACAddress'}, |
184 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
185 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
186 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
187 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
188 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
189 | + }, |
190 | + u'maasserver.node': { |
191 | + 'Meta': {'object_name': 'Node'}, |
192 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
193 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
194 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
195 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
196 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
197 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
198 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
199 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
200 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
201 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
202 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
203 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
204 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
205 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
206 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
207 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
208 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-c99b0122-0980-11e2-aaba-525400f6f496'", 'unique': 'True', 'max_length': '41'}), |
209 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
210 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
211 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
212 | + }, |
213 | + u'maasserver.nodegroup': { |
214 | + 'Meta': {'object_name': 'NodeGroup'}, |
215 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
216 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
217 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
218 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
219 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
220 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
221 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
222 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
223 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
224 | + }, |
225 | + u'maasserver.nodegroupinterface': { |
226 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
227 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
228 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
229 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
230 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
231 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
232 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}), |
233 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}), |
234 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
235 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
236 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
237 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
238 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
239 | + }, |
240 | + u'maasserver.sshkey': { |
241 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
242 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
243 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
244 | + 'key': ('django.db.models.fields.TextField', [], {}), |
245 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
246 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
247 | + }, |
248 | + u'maasserver.tag': { |
249 | + 'Meta': {'object_name': 'Tag'}, |
250 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
251 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
252 | + 'definition': ('django.db.models.fields.TextField', [], {}), |
253 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
254 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
255 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
256 | + }, |
257 | + u'maasserver.userprofile': { |
258 | + 'Meta': {'object_name': 'UserProfile'}, |
259 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
260 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
261 | + }, |
262 | + 'piston.consumer': { |
263 | + 'Meta': {'object_name': 'Consumer'}, |
264 | + 'description': ('django.db.models.fields.TextField', [], {}), |
265 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
266 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
267 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
268 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
269 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
270 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
271 | + }, |
272 | + 'piston.token': { |
273 | + 'Meta': {'object_name': 'Token'}, |
274 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
275 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
276 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
277 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
278 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
279 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
280 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
281 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1348845951L'}), |
282 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
283 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
284 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
285 | + } |
286 | + } |
287 | + |
288 | + complete_apps = ['maasserver'] |
289 | \ No newline at end of file |
290 | |
291 | === added file 'src/maasserver/migrations/0032_node_subarch.py' |
292 | --- src/maasserver/migrations/0032_node_subarch.py 1970-01-01 00:00:00 +0000 |
293 | +++ src/maasserver/migrations/0032_node_subarch.py 2012-09-28 17:03:22 +0000 |
294 | @@ -0,0 +1,191 @@ |
295 | +# -*- coding: utf-8 -*- |
296 | +import datetime |
297 | +from south.db import db |
298 | +from south.v2 import DataMigration |
299 | +from django.db import models |
300 | + |
301 | +class Migration(DataMigration): |
302 | + |
303 | + def forwards(self, orm): |
304 | + for node in orm['maasserver.Node'].objects.all(): |
305 | + node.architecture += '/generic' |
306 | + node.save() |
307 | + |
308 | + def backwards(self, orm): |
309 | + for node in orm['maasserver.Node'].objects.all(): |
310 | + # Drop subarch for backwards migration. This is lossy. |
311 | + node.architecture = node.architecture.split('/')[0] |
312 | + node.save() |
313 | + |
314 | + models = { |
315 | + 'auth.group': { |
316 | + 'Meta': {'object_name': 'Group'}, |
317 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
318 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
319 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
320 | + }, |
321 | + 'auth.permission': { |
322 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
323 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
324 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
325 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
326 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
327 | + }, |
328 | + 'auth.user': { |
329 | + 'Meta': {'object_name': 'User'}, |
330 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
331 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
332 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
333 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
334 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
335 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
336 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
337 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
338 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
339 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
340 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
341 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
342 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
343 | + }, |
344 | + 'contenttypes.contenttype': { |
345 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
346 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
347 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
348 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
349 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
350 | + }, |
351 | + u'maasserver.bootimage': { |
352 | + 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
353 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
354 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
355 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
356 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
357 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
358 | + }, |
359 | + u'maasserver.config': { |
360 | + 'Meta': {'object_name': 'Config'}, |
361 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
362 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
363 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
364 | + }, |
365 | + u'maasserver.dhcplease': { |
366 | + 'Meta': {'object_name': 'DHCPLease'}, |
367 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
368 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
369 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
370 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
371 | + }, |
372 | + u'maasserver.filestorage': { |
373 | + 'Meta': {'object_name': 'FileStorage'}, |
374 | + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), |
375 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
376 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
377 | + }, |
378 | + u'maasserver.macaddress': { |
379 | + 'Meta': {'object_name': 'MACAddress'}, |
380 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
381 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
382 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
383 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
384 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
385 | + }, |
386 | + u'maasserver.node': { |
387 | + 'Meta': {'object_name': 'Node'}, |
388 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
389 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
390 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
391 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
392 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
393 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
394 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
395 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
396 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
397 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
398 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
399 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
400 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
401 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
402 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
403 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
404 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-3c1542ee-0981-11e2-8be8-525400f6f496'", 'unique': 'True', 'max_length': '41'}), |
405 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
406 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
407 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
408 | + }, |
409 | + u'maasserver.nodegroup': { |
410 | + 'Meta': {'object_name': 'NodeGroup'}, |
411 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
412 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
413 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
414 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
415 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
416 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
417 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
418 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
419 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
420 | + }, |
421 | + u'maasserver.nodegroupinterface': { |
422 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
423 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
424 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
425 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
426 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
427 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
428 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}), |
429 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}), |
430 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
431 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
432 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
433 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
434 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
435 | + }, |
436 | + u'maasserver.sshkey': { |
437 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
438 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
439 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
440 | + 'key': ('django.db.models.fields.TextField', [], {}), |
441 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
442 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
443 | + }, |
444 | + u'maasserver.tag': { |
445 | + 'Meta': {'object_name': 'Tag'}, |
446 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
447 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
448 | + 'definition': ('django.db.models.fields.TextField', [], {}), |
449 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
450 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
451 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
452 | + }, |
453 | + u'maasserver.userprofile': { |
454 | + 'Meta': {'object_name': 'UserProfile'}, |
455 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
456 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
457 | + }, |
458 | + 'piston.consumer': { |
459 | + 'Meta': {'object_name': 'Consumer'}, |
460 | + 'description': ('django.db.models.fields.TextField', [], {}), |
461 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
462 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
463 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
464 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
465 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
466 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
467 | + }, |
468 | + 'piston.token': { |
469 | + 'Meta': {'object_name': 'Token'}, |
470 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
471 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
472 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
473 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
474 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
475 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
476 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
477 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1348846143L'}), |
478 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
479 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
480 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
481 | + } |
482 | + } |
483 | + |
484 | + complete_apps = ['maasserver'] |
485 | + symmetrical = True |
486 | |
487 | === modified file 'src/maasserver/models/node.py' |
488 | --- src/maasserver/models/node.py 2012-09-28 10:30:51 +0000 |
489 | +++ src/maasserver/models/node.py 2012-09-28 17:03:22 +0000 |
490 | @@ -411,7 +411,7 @@ |
491 | blank=True, default=None) |
492 | |
493 | architecture = CharField( |
494 | - max_length=10, choices=ARCHITECTURE_CHOICES, blank=False, |
495 | + max_length=31, choices=ARCHITECTURE_CHOICES, blank=False, |
496 | default=ARCHITECTURE.i386) |
497 | |
498 | # Juju expects the following standard constraints, which are stored here |
499 | |
500 | === modified file 'src/maasserver/preseed.py' |
501 | --- src/maasserver/preseed.py 2012-09-21 07:37:56 +0000 |
502 | +++ src/maasserver/preseed.py 2012-09-28 17:03:22 +0000 |
503 | @@ -93,6 +93,7 @@ |
504 | lookup order: |
505 | {prefix}_{node_architecture}_{node_subarchitecture}_{release}_{node_name} |
506 | {prefix}_{node_architecture}_{node_subarchitecture}_{release} |
507 | + {prefix}_{node_architecture}_{node_subarchitecture} |
508 | {prefix}_{node_architecture} |
509 | {prefix} |
510 | 'generic' |
511 | |
512 | === modified file 'src/maasserver/tests/test_api.py' |
513 | --- src/maasserver/tests/test_api.py 2012-09-28 12:55:42 +0000 |
514 | +++ src/maasserver/tests/test_api.py 2012-09-28 17:03:22 +0000 |
515 | @@ -268,6 +268,50 @@ |
516 | ] |
517 | |
518 | def test_POST_new_creates_node(self): |
519 | + architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES) |
520 | + response = self.client.post( |
521 | + self.get_uri('nodes/'), |
522 | + { |
523 | + 'op': 'new', |
524 | + 'hostname': 'diane', |
525 | + 'architecture': architecture, |
526 | + 'after_commissioning_action': |
527 | + NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, |
528 | + 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'], |
529 | + }) |
530 | + |
531 | + self.assertEqual(httplib.OK, response.status_code) |
532 | + parsed_result = json.loads(response.content) |
533 | + self.assertIn('application/json', response['Content-Type']) |
534 | + self.assertEqual('diane', parsed_result['hostname']) |
535 | + self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
536 | + [diane] = Node.objects.filter(hostname='diane') |
537 | + self.assertEqual(architecture, diane.architecture) |
538 | + |
539 | + def test_POST_new_creates_node_with_arch_only(self): |
540 | + architecture = factory.getRandomChoice( |
541 | + [choice for choice in ARCHITECTURE_CHOICES |
542 | + if choice[0].endswith('/generic')]) |
543 | + response = self.client.post( |
544 | + self.get_uri('nodes/'), |
545 | + { |
546 | + 'op': 'new', |
547 | + 'hostname': 'diane', |
548 | + 'architecture': architecture.split('/')[0], |
549 | + 'after_commissioning_action': |
550 | + NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, |
551 | + 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'], |
552 | + }) |
553 | + |
554 | + self.assertEqual(httplib.OK, response.status_code) |
555 | + parsed_result = json.loads(response.content) |
556 | + self.assertIn('application/json', response['Content-Type']) |
557 | + self.assertEqual('diane', parsed_result['hostname']) |
558 | + self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
559 | + [diane] = Node.objects.filter(hostname='diane') |
560 | + self.assertEqual(architecture, diane.architecture) |
561 | + |
562 | + def test_POST_new_creates_node_with_subarchitecture(self): |
563 | # The API allows a Node to be created. |
564 | architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES) |
565 | response = self.client.post( |
566 | @@ -275,20 +319,39 @@ |
567 | { |
568 | 'op': 'new', |
569 | 'hostname': 'diane', |
570 | - 'architecture': architecture, |
571 | + 'architecture': architecture.split('/')[0], |
572 | + 'subarchitecture': architecture.split('/')[1], |
573 | 'after_commissioning_action': |
574 | NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, |
575 | 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'], |
576 | }) |
577 | + |
578 | + self.assertEqual(httplib.OK, response.status_code) |
579 | parsed_result = json.loads(response.content) |
580 | - |
581 | - self.assertEqual(httplib.OK, response.status_code) |
582 | self.assertIn('application/json', response['Content-Type']) |
583 | self.assertEqual('diane', parsed_result['hostname']) |
584 | self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
585 | [diane] = Node.objects.filter(hostname='diane') |
586 | self.assertEqual(architecture, diane.architecture) |
587 | |
588 | + def test_POST_new_fails_node_with_double_subarchitecture(self): |
589 | + architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES) |
590 | + response = self.client.post( |
591 | + self.get_uri('nodes/'), |
592 | + { |
593 | + 'op': 'new', |
594 | + 'hostname': 'diane', |
595 | + 'architecture': architecture, |
596 | + 'subarchitecture': architecture.split('/')[1], |
597 | + 'after_commissioning_action': |
598 | + NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, |
599 | + 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'], |
600 | + }) |
601 | + self.assertEqual(httplib.BAD_REQUEST, response.status_code) |
602 | + self.assertIn('text/plain', response['Content-Type']) |
603 | + self.assertEqual("Subarchitecture cannot be specified twice.", |
604 | + response.content) |
605 | + |
606 | def test_POST_new_power_type_defaults_to_asking_config(self): |
607 | architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES) |
608 | response = self.client.post( |
609 | @@ -1703,7 +1766,7 @@ |
610 | status=NODE_STATUS.READY, architecture=ARCHITECTURE.i386) |
611 | response = self.client.post(self.get_uri('nodes/'), { |
612 | 'op': 'acquire', |
613 | - 'arch': 'i386', |
614 | + 'arch': 'i386/generic', |
615 | }) |
616 | self.assertEqual(httplib.OK, response.status_code) |
617 | response_json = json.loads(response.content) |
618 | @@ -2696,9 +2759,10 @@ |
619 | def test_pxeconfig_defaults_to_i386_when_node_unknown(self): |
620 | # As a lowest-common-denominator, i386 is chosen when the node is not |
621 | # yet known to MAAS. |
622 | + expected_arch = tuple(ARCHITECTURE.i386.split('/')) |
623 | params_out = self.get_pxeconfig() |
624 | - self.assertEqual(ARCHITECTURE.i386, params_out["arch"]) |
625 | - self.assertEqual("generic", params_out["subarch"]) |
626 | + observed_arch = params_out["arch"], params_out["subarch"] |
627 | + self.assertEqual(expected_arch, observed_arch) |
628 | |
629 | def get_without_param(self, param): |
630 | """Request a `pxeconfig()` response, but omit `param` from request.""" |
631 | |
632 | === modified file 'src/maasserver/tests/test_node.py' |
633 | --- src/maasserver/tests/test_node.py 2012-09-28 14:38:32 +0000 |
634 | +++ src/maasserver/tests/test_node.py 2012-09-28 17:03:22 +0000 |
635 | @@ -702,7 +702,7 @@ |
636 | nodes = [self.make_node(architecture=s) |
637 | for s in (ARCHITECTURE.amd64, ARCHITECTURE.i386)] |
638 | available_node = Node.objects.get_available_node_for_acquisition( |
639 | - user, {'architecture': "i386"}) |
640 | + user, {'architecture': "i386/generic"}) |
641 | self.assertEqual(ARCHITECTURE.i386, available_node.architecture) |
642 | self.assertEqual(nodes[1], available_node) |
643 | |
644 | |
645 | === modified file 'src/maasserver/tests/test_node_constraint_filter.py' |
646 | --- src/maasserver/tests/test_node_constraint_filter.py 2012-09-28 14:38:32 +0000 |
647 | +++ src/maasserver/tests/test_node_constraint_filter.py 2012-09-28 17:03:22 +0000 |
648 | @@ -42,9 +42,10 @@ |
649 | |
650 | def test_architecture(self): |
651 | node1 = factory.make_node(architecture=ARCHITECTURE.i386) |
652 | - node2 = factory.make_node(architecture=ARCHITECTURE.armhf) |
653 | - self.assertConstrainedNodes([node1], {'architecture': 'i386'}) |
654 | - self.assertConstrainedNodes([node2], {'architecture': 'armhf'}) |
655 | + node2 = factory.make_node(architecture=ARCHITECTURE.armhf_highbank) |
656 | + self.assertConstrainedNodes([node1], {'architecture': 'i386/generic'}) |
657 | + self.assertConstrainedNodes( |
658 | + [node2], {'architecture': 'armhf/highbank'}) |
659 | self.assertConstrainedNodes([], {'architecture': 'sparc'}) |
660 | |
661 | def test_cpu_count(self): |
662 | @@ -95,9 +96,10 @@ |
663 | node_big.tags.add(tag_big) |
664 | node_small = factory.make_node(architecture=ARCHITECTURE.i386) |
665 | ignore_unused(node_small) |
666 | - node_big_arm = factory.make_node(architecture=ARCHITECTURE.armhf) |
667 | + node_big_arm = factory.make_node( |
668 | + architecture=ARCHITECTURE.armhf_highbank) |
669 | node_big_arm.tags.add(tag_big) |
670 | self.assertConstrainedNodes([node_big, node_big_arm], |
671 | {'tags': 'big'}) |
672 | - self.assertConstrainedNodes([node_big], |
673 | - {'architecture': 'i386', 'tags': 'big'}) |
674 | + self.assertConstrainedNodes( |
675 | + [node_big], {'architecture': 'i386/generic', 'tags': 'big'}) |
676 | |
677 | === modified file 'src/maasserver/tests/test_preseed.py' |
678 | --- src/maasserver/tests/test_preseed.py 2012-08-21 20:27:47 +0000 |
679 | +++ src/maasserver/tests/test_preseed.py 2012-09-28 17:03:22 +0000 |
680 | @@ -64,27 +64,7 @@ |
681 | prefix = factory.getRandomString() |
682 | release = factory.getRandomString() |
683 | node = factory.make_node(hostname=hostname) |
684 | - self.assertSequenceEqual( |
685 | - [ |
686 | - '%s_%s_%s_%s' % (prefix, node.architecture, release, hostname), |
687 | - '%s_%s_%s' % (prefix, node.architecture, release), |
688 | - '%s_%s' % (prefix, node.architecture), |
689 | - '%s' % prefix, |
690 | - 'generic', |
691 | - ], |
692 | - list(get_preseed_filenames(node, prefix, release, default=True))) |
693 | - |
694 | - def test_get_preseed_filenames_returns_filenames_with_subarch(self): |
695 | - arch = factory.getRandomString() |
696 | - subarch = factory.getRandomString() |
697 | - fake_arch = '%s/%s' % (arch, subarch) |
698 | - hostname = factory.getRandomString() |
699 | - prefix = factory.getRandomString() |
700 | - release = factory.getRandomString() |
701 | - node = factory.make_node(hostname=hostname) |
702 | - # Set an architecture of the form '%s/%s' i.e. with a |
703 | - # sub-architecture. |
704 | - node.architecture = fake_arch |
705 | + arch, subarch = node.architecture.split('/') |
706 | self.assertSequenceEqual( |
707 | [ |
708 | '%s_%s_%s_%s_%s' % (prefix, arch, subarch, release, hostname), |
709 | @@ -110,11 +90,13 @@ |
710 | hostname = factory.getRandomString() |
711 | release = factory.getRandomString() |
712 | node = factory.make_node(hostname=hostname) |
713 | + arch, subarch = node.architecture.split('/') |
714 | self.assertSequenceEqual( |
715 | [ |
716 | - '%s_%s_%s' % (node.architecture, release, hostname), |
717 | - '%s_%s' % (node.architecture, release), |
718 | - '%s' % node.architecture, |
719 | + '%s_%s_%s_%s' % (arch, subarch, release, hostname), |
720 | + '%s_%s_%s' % (arch, subarch, release), |
721 | + '%s_%s' % (arch, subarch), |
722 | + '%s' % arch, |
723 | ], |
724 | list(get_preseed_filenames(node, '', release))) |
725 | |
726 | @@ -266,7 +248,8 @@ |
727 | self.create_template(self.location, prefix) |
728 | node = factory.make_node(hostname=factory.getRandomString()) |
729 | node_template_name = "%s_%s_%s_%s" % ( |
730 | - prefix, node.architecture, release, node.hostname) |
731 | + prefix, node.architecture.replace('/', '_'), |
732 | + release, node.hostname) |
733 | # Create the node-specific template. |
734 | content = self.create_template(self.location, node_template_name) |
735 | template = load_preseed_template(node, prefix, release) |
Temporarily making constraints specify the full form "i386/generic" rather than just "i386" is fine. A follow up branch can do the fix we talked about that maps the main architecture strings to the set of all supported sub-architectures.