Merge lp:~blake-rouse/maas/add-osystem-to-bootimage into lp:~maas-committers/maas/trunk
- add-osystem-to-bootimage
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Blake Rouse | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2313 | ||||
Proposed branch: | lp:~blake-rouse/maas/add-osystem-to-bootimage | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
1431 lines (+546/-111) 26 files modified
src/maasserver/api.py (+21/-17) src/maasserver/migrations/0076_add_osystem_to_bootimage.py (+275/-0) src/maasserver/models/bootimage.py (+41/-20) src/maasserver/models/tests/test_bootimage.py (+100/-20) src/maasserver/preseed.py (+5/-2) src/maasserver/templates/maasserver/bootimage-list.html (+2/-0) src/maasserver/testing/factory.py (+6/-3) src/maasserver/tests/test_api_boot_images.py (+4/-1) src/maasserver/tests/test_api_pxeconfig.py (+2/-0) src/maasserver/tests/test_preseed.py (+13/-5) src/maasserver/views/clusters.py (+2/-2) src/maasserver/views/tests/test_boot_image_list.py (+1/-2) src/metadataserver/tests/test_api.py (+1/-0) src/provisioningserver/boot/__init__.py (+1/-1) src/provisioningserver/boot/tests/test_pxe.py (+3/-2) src/provisioningserver/boot/tests/test_tftppath.py (+6/-3) src/provisioningserver/boot/tests/test_uefi.py (+1/-1) src/provisioningserver/boot/tftppath.py (+11/-10) src/provisioningserver/import_images/boot_resources.py (+16/-6) src/provisioningserver/import_images/download_resources.py (+2/-1) src/provisioningserver/import_images/tests/test_boot_resources.py (+5/-3) src/provisioningserver/kernel_opts.py (+12/-4) src/provisioningserver/rpc/cluster.py (+2/-1) src/provisioningserver/rpc/tests/test_clusterservice.py (+5/-3) src/provisioningserver/testing/boot_images.py (+5/-2) src/provisioningserver/tests/test_kernel_opts.py (+4/-2) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/add-osystem-to-bootimage | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+216923@code.launchpad.net |
Commit message
Adds an operating system field 'osystem' to the BootImage model and restructures the boot-resources folder to add another level for osystem.
Description of the change
This is the first change in the series of changes to all MAAS to deploy other operating systems. As we have Windows and CentOS support coming soon this is needed to easily add new operating systems.=
Adds an operating system field 'osystem' to the BootImage model. Selection of a BootImage is now based on the operating system to be booted. Currently this is hard coded to 'ubuntu', following changes remove this hard code. The WebUI is updated to show the 'osystem' field in the BootImage list.
The folder structure of the boot-resources folder has also added another level. Currently it is structured arch/subarch/
Note: osystem was used throughout the code instead of os, as the python os module would conflict throughout the code base.
Changes were coordinated with allenap.
Andres Rodriguez (andreserl) wrote : | # |
BTW... How will this affect upgrades? My concern is that if we backport
this after upgrades things will be broken because the location of the
bootimages has now changed right? What should we do about it? Julian, Gavin
any thoughts?
On Apr 23, 2014 11:06 PM, "Julian Edwards" <email address hidden>
wrote:
> Review: Approve
>
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> review: approve
>
> Wow, adding something as simple as an OS name was quite a lot of
> changes! Thanks for doing this, it's a very nice branch.
>
> I have a couple of niggles but it looks mostly OK to me. Please land
> it once these are addressed.
>
> [1]
>
> > + def get_usable_
> > the list of usable operating systems for a nodegroup. + """
> > + query = BootImage.
> > return set(query.
> > get_usable_
> > the list of usable releases for a nodegroup and + operating
> > system. + """ + query =
> > BootImage.
> > releases = query.values_
> > set(releases)
>
> Can you add tests for these new functions please. Also they are not
> actually used anywhere, I presume a follow up branch makes use of them?
>
> [2]
>
> Please run "make lint" and fix all the errors it finds.
>
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1
> Comment: Using GnuPG with Thunderbird - http://
>
> iEYEARECAAYFAlN
> VaEAniGAr/
> =VxoG
> -----END PGP SIGNATURE-----
>
> --
>
> https:/
> You are subscribed to branch lp:maas.
>
Blake Rouse (blake-rouse) wrote : | # |
I added test for get_usable_osystems and get_useable_
Andres you are correct in an issue on upgrade, this should be the only upgrade issue I can think of out of all of these branches. I don't think it would be to hard to handle, as we know the only operating system that is in boot-resources folder is Ubuntu.
So on upgrade move all folders in the boot-resources folder into a sub-folder named ubuntu, except for the grub folder it should not move. The migration already sets all BootImage items to ubuntu, so nothing more need to be done there. Only thing left to do is update the maas.tgt, with ubuntu in the path.
Andres Rodriguez (andreserl) wrote : | # |
So what can we do to address this? This might also break fast path installer when it goes to download the root tarball because it looks for the tarball in a location that does not expect to have the osrelease. Gavin, Julian, do you think we can just avoid using osrelease in the boot-resources path? Or what can we do to move this forward and not break things?
Blake Rouse (blake-rouse) wrote : | # |
It should not break fast path installer as all of those paths now include the operating system first. So upon installation the preseed that is sent will have the correct path including the operating system.
If you remove the os from the path, you make it really hard for the boot resources to be identified for other operating systems. As that first path should be the operating system.
Gavin Panella (allenap) wrote : | # |
What's going to move those boot resources? Can it be done just-in-time? Or do we need to arrange for a run of an import?
My feeling is that we should empty the BootImages table in the migration (both ways) and schedule imports to run.
Julian Edwards (julian-edwards) wrote : | # |
On 25/04/14 20:24, Gavin Panella wrote:
> What's going to move those boot resources? Can it be done just-in-time? Or do we need to arrange for a run of an import?
>
> My feeling is that we should empty the BootImages table in the migration (both ways) and schedule imports to run.
>
We have the per-cluster built-in migration now, which is called on every
start-up. It can easily shuffle files around as needed, and issue API
calls to update the cluster's BootImages.
Gavin Panella (allenap) wrote : | # |
> We have the per-cluster built-in migration now, which is called on every
> start-up. It can easily shuffle files around as needed, and issue API
> calls to update the cluster's BootImages.
I had forgotten about that. Indeed, that's the better solution.
Julian Edwards (julian-edwards) wrote : | # |
On 28/04/14 16:40, Gavin Panella wrote:
>> We have the per-cluster built-in migration now, which is called on every
>> start-up. It can easily shuffle files around as needed, and issue API
>> calls to update the cluster's BootImages.
>
> I had forgotten about that. Indeed, that's the better solution.
>
I should mention that we need to make doubly sure that this stuff gets
called from the right charm hooks!
Preview Diff
1 | === modified file 'src/maasserver/api.py' | |||
2 | --- src/maasserver/api.py 2014-04-28 07:41:51 +0000 | |||
3 | +++ src/maasserver/api.py 2014-05-02 19:22:04 +0000 | |||
4 | @@ -2440,7 +2440,7 @@ | |||
5 | 2440 | # current series. If nothing is found, fall back to i386 like | 2440 | # current series. If nothing is found, fall back to i386 like |
6 | 2441 | # we used to. LP #1181334 | 2441 | # we used to. LP #1181334 |
7 | 2442 | image = BootImage.objects.get_default_arch_image_in_nodegroup( | 2442 | image = BootImage.objects.get_default_arch_image_in_nodegroup( |
9 | 2443 | nodegroup, series, purpose=purpose) | 2443 | nodegroup, 'ubuntu', series, purpose=purpose) |
10 | 2444 | if image is None: | 2444 | if image is None: |
11 | 2445 | arch = 'i386' | 2445 | arch = 'i386' |
12 | 2446 | else: | 2446 | else: |
13 | @@ -2453,7 +2453,7 @@ | |||
14 | 2453 | # (which should never happen in reality but may happen in tests), we | 2453 | # (which should never happen in reality but may happen in tests), we |
15 | 2454 | # fall back to using 'no-such-image' as our default. | 2454 | # fall back to using 'no-such-image' as our default. |
16 | 2455 | latest_image = BootImage.objects.get_latest_image( | 2455 | latest_image = BootImage.objects.get_latest_image( |
18 | 2456 | nodegroup, arch, subarch, series, purpose) | 2456 | nodegroup, 'ubuntu', arch, subarch, series, purpose) |
19 | 2457 | if latest_image is None: | 2457 | if latest_image is None: |
20 | 2458 | # XXX 2014-03-18 gmb bug=1294131: | 2458 | # XXX 2014-03-18 gmb bug=1294131: |
21 | 2459 | # We really ought to raise an exception here so that client | 2459 | # We really ought to raise an exception here so that client |
22 | @@ -2494,8 +2494,8 @@ | |||
23 | 2494 | cluster_address = get_mandatory_param(request.GET, "local") | 2494 | cluster_address = get_mandatory_param(request.GET, "local") |
24 | 2495 | 2495 | ||
25 | 2496 | params = KernelParameters( | 2496 | params = KernelParameters( |
28 | 2497 | arch=arch, subarch=subarch, release=series, label=label, | 2497 | osystem='ubuntu', arch=arch, subarch=subarch, release=series, |
29 | 2498 | purpose=purpose, hostname=hostname, domain=domain, | 2498 | label=label, purpose=purpose, hostname=hostname, domain=domain, |
30 | 2499 | preseed_url=preseed_url, log_host=server_address, | 2499 | preseed_url=preseed_url, log_host=server_address, |
31 | 2500 | fs_host=cluster_address, extra_opts=extra_kernel_opts) | 2500 | fs_host=cluster_address, extra_opts=extra_kernel_opts) |
32 | 2501 | 2501 | ||
33 | @@ -2536,10 +2536,11 @@ | |||
34 | 2536 | This function has a counterpart, `summarise_boot_image_dict`. The two | 2536 | This function has a counterpart, `summarise_boot_image_dict`. The two |
35 | 2537 | return the same value for the same boot image. | 2537 | return the same value for the same boot image. |
36 | 2538 | 2538 | ||
39 | 2539 | :return: A tuple of the image's architecture, subarchitecture, release, | 2539 | :return: A tuple of the image's osystem, architecture, subarchitecture, |
40 | 2540 | label, and purpose. | 2540 | release, label, and purpose. |
41 | 2541 | """ | 2541 | """ |
42 | 2542 | return ( | 2542 | return ( |
43 | 2543 | image_object.osystem, | ||
44 | 2543 | image_object.architecture, | 2544 | image_object.architecture, |
45 | 2544 | image_object.subarchitecture, | 2545 | image_object.subarchitecture, |
46 | 2545 | image_object.release, | 2546 | image_object.release, |
47 | @@ -2554,10 +2555,11 @@ | |||
48 | 2554 | This is the counterpart to `summarise_boot_image_object`. The two return | 2555 | This is the counterpart to `summarise_boot_image_object`. The two return |
49 | 2555 | the same value for the same boot image. | 2556 | the same value for the same boot image. |
50 | 2556 | 2557 | ||
53 | 2557 | :return: A tuple of the image's architecture, subarchitecture, release, | 2558 | :return: A tuple of the image's osystem, architecture, subarchitecture, |
54 | 2558 | label, and purpose. | 2559 | release, label, and purpose. |
55 | 2559 | """ | 2560 | """ |
56 | 2560 | return ( | 2561 | return ( |
57 | 2562 | image_dict['osystem'], | ||
58 | 2561 | image_dict['architecture'], | 2563 | image_dict['architecture'], |
59 | 2562 | image_dict.get('subarchitecture', 'generic'), | 2564 | image_dict.get('subarchitecture', 'generic'), |
60 | 2563 | image_dict['release'], | 2565 | image_dict['release'], |
61 | @@ -2600,10 +2602,11 @@ | |||
62 | 2600 | `summarise_stored_images`. | 2602 | `summarise_stored_images`. |
63 | 2601 | """ | 2603 | """ |
64 | 2602 | new_images = reported_images - stored_images | 2604 | new_images = reported_images - stored_images |
66 | 2603 | for arch, subarch, release, label, purpose in new_images: | 2605 | for osystem, arch, subarch, release, label, purpose in new_images: |
67 | 2604 | BootImage.objects.register_image( | 2606 | BootImage.objects.register_image( |
70 | 2605 | nodegroup=nodegroup, architecture=arch, subarchitecture=subarch, | 2607 | nodegroup=nodegroup, osystem=osystem, architecture=arch, |
71 | 2606 | release=release, purpose=purpose, label=label) | 2608 | subarchitecture=subarch, release=release, purpose=purpose, |
72 | 2609 | label=label) | ||
73 | 2607 | 2610 | ||
74 | 2608 | 2611 | ||
75 | 2609 | def prune_boot_images(nodegroup, reported_images, stored_images): | 2612 | def prune_boot_images(nodegroup, reported_images, stored_images): |
76 | @@ -2620,15 +2623,16 @@ | |||
77 | 2620 | `summarise_stored_images`. | 2623 | `summarise_stored_images`. |
78 | 2621 | """ | 2624 | """ |
79 | 2622 | removed_images = stored_images - reported_images | 2625 | removed_images = stored_images - reported_images |
81 | 2623 | for arch, subarch, release, label, purpose in removed_images: | 2626 | for osystem, arch, subarch, release, label, purpose in removed_images: |
82 | 2624 | db_images = BootImage.objects.filter( | 2627 | db_images = BootImage.objects.filter( |
84 | 2625 | architecture=arch, subarchitecture=subarch, | 2628 | osystem=osystem, architecture=arch, subarchitecture=subarch, |
85 | 2626 | release=release, label=label, purpose=purpose) | 2629 | release=release, label=label, purpose=purpose) |
86 | 2627 | db_images.delete() | 2630 | db_images.delete() |
87 | 2628 | 2631 | ||
88 | 2629 | 2632 | ||
89 | 2630 | DISPLAYED_BOOTIMAGE_FIELDS = ( | 2633 | DISPLAYED_BOOTIMAGE_FIELDS = ( |
90 | 2631 | 'id', | 2634 | 'id', |
91 | 2635 | 'osystem', | ||
92 | 2632 | 'release', | 2636 | 'release', |
93 | 2633 | 'architecture', | 2637 | 'architecture', |
94 | 2634 | 'subarchitecture', | 2638 | 'subarchitecture', |
95 | @@ -2694,10 +2698,10 @@ | |||
96 | 2694 | :param uuid: The UUID of the cluster for which the images are | 2698 | :param uuid: The UUID of the cluster for which the images are |
97 | 2695 | being reported. | 2699 | being reported. |
98 | 2696 | :param images: A list of dicts, each describing a boot image with | 2700 | :param images: A list of dicts, each describing a boot image with |
103 | 2697 | these properties: `architecture`, `subarchitecture`, `release`, | 2701 | these properties: `os`, `architecture`, `subarchitecture`, |
104 | 2698 | `purpose`, and optionally, `label` (which defaults to "release"). | 2702 | `release`, `purpose`, and optionally, `label` (which defaults |
105 | 2699 | These should match the code that determines TFTP paths for these | 2703 | to "release"). These should match the code that determines TFTP |
106 | 2700 | images. | 2704 | paths for these images. |
107 | 2701 | """ | 2705 | """ |
108 | 2702 | nodegroup = get_object_or_404(NodeGroup, uuid=uuid) | 2706 | nodegroup = get_object_or_404(NodeGroup, uuid=uuid) |
109 | 2703 | check_nodegroup_access(request, nodegroup) | 2707 | check_nodegroup_access(request, nodegroup) |
110 | 2704 | 2708 | ||
111 | === added file 'src/maasserver/migrations/0076_add_osystem_to_bootimage.py' | |||
112 | --- src/maasserver/migrations/0076_add_osystem_to_bootimage.py 1970-01-01 00:00:00 +0000 | |||
113 | +++ src/maasserver/migrations/0076_add_osystem_to_bootimage.py 2014-05-02 19:22:04 +0000 | |||
114 | @@ -0,0 +1,275 @@ | |||
115 | 1 | # -*- coding: utf-8 -*- | ||
116 | 2 | from south.utils import datetime_utils as datetime | ||
117 | 3 | from south.db import db | ||
118 | 4 | from south.v2 import SchemaMigration | ||
119 | 5 | from django.db import models | ||
120 | 6 | |||
121 | 7 | |||
122 | 8 | class Migration(SchemaMigration): | ||
123 | 9 | |||
124 | 10 | def forwards(self, orm): | ||
125 | 11 | # Removing unique constraint on 'BootImage', fields ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup', 'purpose'] | ||
126 | 12 | db.delete_unique(u'maasserver_bootimage', ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose']) | ||
127 | 13 | |||
128 | 14 | # Adding field 'BootImage.osystem' | ||
129 | 15 | db.add_column(u'maasserver_bootimage', 'osystem', | ||
130 | 16 | self.gf('django.db.models.fields.CharField')(default='ubuntu', max_length=255), | ||
131 | 17 | keep_default=False) | ||
132 | 18 | |||
133 | 19 | # Adding unique constraint on 'BootImage', fields ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup', 'purpose'] | ||
134 | 20 | db.create_unique(u'maasserver_bootimage', ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose']) | ||
135 | 21 | |||
136 | 22 | |||
137 | 23 | def backwards(self, orm): | ||
138 | 24 | # Removing unique constraint on 'BootImage', fields ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup', 'purpose'] | ||
139 | 25 | db.delete_unique(u'maasserver_bootimage', ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose']) | ||
140 | 26 | |||
141 | 27 | # Deleting field 'BootImage.osystem' | ||
142 | 28 | db.delete_column(u'maasserver_bootimage', 'osystem') | ||
143 | 29 | |||
144 | 30 | # Adding unique constraint on 'BootImage', fields ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup', 'purpose'] | ||
145 | 31 | db.create_unique(u'maasserver_bootimage', ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose']) | ||
146 | 32 | |||
147 | 33 | |||
148 | 34 | models = { | ||
149 | 35 | u'auth.group': { | ||
150 | 36 | 'Meta': {'object_name': 'Group'}, | ||
151 | 37 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
152 | 38 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
153 | 39 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
154 | 40 | }, | ||
155 | 41 | u'auth.permission': { | ||
156 | 42 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, | ||
157 | 43 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
158 | 44 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), | ||
159 | 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
160 | 46 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
161 | 47 | }, | ||
162 | 48 | u'auth.user': { | ||
163 | 49 | 'Meta': {'object_name': 'User'}, | ||
164 | 50 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
165 | 51 | 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
166 | 52 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
167 | 53 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), | ||
168 | 54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
169 | 55 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
170 | 56 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
171 | 57 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
172 | 58 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
173 | 59 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
174 | 60 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
175 | 61 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), | ||
176 | 62 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) | ||
177 | 63 | }, | ||
178 | 64 | u'contenttypes.contenttype': { | ||
179 | 65 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
180 | 66 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
181 | 67 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
182 | 68 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
183 | 69 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
184 | 70 | }, | ||
185 | 71 | u'maasserver.bootimage': { | ||
186 | 72 | 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'}, | ||
187 | 73 | 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
188 | 74 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
189 | 75 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
190 | 76 | 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}), | ||
191 | 77 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
192 | 78 | 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
193 | 79 | 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
194 | 80 | 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
195 | 81 | 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
196 | 82 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
197 | 83 | }, | ||
198 | 84 | u'maasserver.bootsource': { | ||
199 | 85 | 'Meta': {'object_name': 'BootSource'}, | ||
200 | 86 | 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), | ||
201 | 87 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
202 | 88 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
203 | 89 | 'keyring_data': ('django.db.models.fields.BinaryField', [], {'blank': 'True'}), | ||
204 | 90 | 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), | ||
205 | 91 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
206 | 92 | 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) | ||
207 | 93 | }, | ||
208 | 94 | u'maasserver.bootsourceselection': { | ||
209 | 95 | 'Meta': {'object_name': 'BootSourceSelection'}, | ||
210 | 96 | 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), | ||
211 | 97 | 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}), | ||
212 | 98 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
213 | 99 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
214 | 100 | 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), | ||
215 | 101 | 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}), | ||
216 | 102 | 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), | ||
217 | 103 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
218 | 104 | }, | ||
219 | 105 | u'maasserver.componenterror': { | ||
220 | 106 | 'Meta': {'object_name': 'ComponentError'}, | ||
221 | 107 | 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), | ||
222 | 108 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
223 | 109 | 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), | ||
224 | 110 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
225 | 111 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
226 | 112 | }, | ||
227 | 113 | u'maasserver.config': { | ||
228 | 114 | 'Meta': {'object_name': 'Config'}, | ||
229 | 115 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
230 | 116 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
231 | 117 | 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) | ||
232 | 118 | }, | ||
233 | 119 | u'maasserver.dhcplease': { | ||
234 | 120 | 'Meta': {'object_name': 'DHCPLease'}, | ||
235 | 121 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
236 | 122 | 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), | ||
237 | 123 | 'mac': ('maasserver.fields.MACAddressField', [], {}), | ||
238 | 124 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) | ||
239 | 125 | }, | ||
240 | 126 | u'maasserver.downloadprogress': { | ||
241 | 127 | 'Meta': {'object_name': 'DownloadProgress'}, | ||
242 | 128 | 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
243 | 129 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
244 | 130 | 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}), | ||
245 | 131 | 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
246 | 132 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
247 | 133 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
248 | 134 | 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
249 | 135 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
250 | 136 | }, | ||
251 | 137 | u'maasserver.filestorage': { | ||
252 | 138 | 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'}, | ||
253 | 139 | 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}), | ||
254 | 140 | 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
255 | 141 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
256 | 142 | 'key': ('django.db.models.fields.CharField', [], {'default': "u'26215e0a-cafa-11e3-8554-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}), | ||
257 | 143 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) | ||
258 | 144 | }, | ||
259 | 145 | u'maasserver.macaddress': { | ||
260 | 146 | 'Meta': {'object_name': 'MACAddress'}, | ||
261 | 147 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
262 | 148 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
263 | 149 | 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), | ||
264 | 150 | 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}), | ||
265 | 151 | 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), | ||
266 | 152 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
267 | 153 | }, | ||
268 | 154 | u'maasserver.network': { | ||
269 | 155 | 'Meta': {'object_name': 'Network'}, | ||
270 | 156 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
271 | 157 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
272 | 158 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}), | ||
273 | 159 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), | ||
274 | 160 | 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | ||
275 | 161 | 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) | ||
276 | 162 | }, | ||
277 | 163 | u'maasserver.node': { | ||
278 | 164 | 'Meta': {'object_name': 'Node'}, | ||
279 | 165 | 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
280 | 166 | 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}), | ||
281 | 167 | 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
282 | 168 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
283 | 169 | 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}), | ||
284 | 170 | 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
285 | 171 | 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}), | ||
286 | 172 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
287 | 173 | 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
288 | 174 | 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
289 | 175 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), | ||
290 | 176 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), | ||
291 | 177 | 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), | ||
292 | 178 | 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), | ||
293 | 179 | 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}), | ||
294 | 180 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), | ||
295 | 181 | 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
296 | 182 | 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-26226a84-cafa-11e3-8554-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}), | ||
297 | 183 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), | ||
298 | 184 | 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}), | ||
299 | 185 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
300 | 186 | 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'}) | ||
301 | 187 | }, | ||
302 | 188 | u'maasserver.nodegroup': { | ||
303 | 189 | 'Meta': {'object_name': 'NodeGroup'}, | ||
304 | 190 | 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), | ||
305 | 191 | 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}), | ||
306 | 192 | 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), | ||
307 | 193 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
308 | 194 | 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
309 | 195 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
310 | 196 | 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
311 | 197 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), | ||
312 | 198 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
313 | 199 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
314 | 200 | 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) | ||
315 | 201 | }, | ||
316 | 202 | u'maasserver.nodegroupinterface': { | ||
317 | 203 | 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, | ||
318 | 204 | 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
319 | 205 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
320 | 206 | 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
321 | 207 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
322 | 208 | 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
323 | 209 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | ||
324 | 210 | 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
325 | 211 | 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
326 | 212 | 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
327 | 213 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
328 | 214 | 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
329 | 215 | 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
330 | 216 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
331 | 217 | }, | ||
332 | 218 | u'maasserver.sshkey': { | ||
333 | 219 | 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, | ||
334 | 220 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
335 | 221 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
336 | 222 | 'key': ('django.db.models.fields.TextField', [], {}), | ||
337 | 223 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
338 | 224 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) | ||
339 | 225 | }, | ||
340 | 226 | u'maasserver.tag': { | ||
341 | 227 | 'Meta': {'object_name': 'Tag'}, | ||
342 | 228 | 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
343 | 229 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
344 | 230 | 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
345 | 231 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
346 | 232 | 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
347 | 233 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), | ||
348 | 234 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
349 | 235 | }, | ||
350 | 236 | u'maasserver.userprofile': { | ||
351 | 237 | 'Meta': {'object_name': 'UserProfile'}, | ||
352 | 238 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
353 | 239 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) | ||
354 | 240 | }, | ||
355 | 241 | u'maasserver.zone': { | ||
356 | 242 | 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'}, | ||
357 | 243 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
358 | 244 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
359 | 245 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
360 | 246 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), | ||
361 | 247 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
362 | 248 | }, | ||
363 | 249 | u'piston.consumer': { | ||
364 | 250 | 'Meta': {'object_name': 'Consumer'}, | ||
365 | 251 | 'description': ('django.db.models.fields.TextField', [], {}), | ||
366 | 252 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
367 | 253 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
368 | 254 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
369 | 255 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
370 | 256 | 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), | ||
371 | 257 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"}) | ||
372 | 258 | }, | ||
373 | 259 | u'piston.token': { | ||
374 | 260 | 'Meta': {'object_name': 'Token'}, | ||
375 | 261 | 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
376 | 262 | 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
377 | 263 | 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}), | ||
378 | 264 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
379 | 265 | 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
380 | 266 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
381 | 267 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
382 | 268 | 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1398266138L'}), | ||
383 | 269 | 'token_type': ('django.db.models.fields.IntegerField', [], {}), | ||
384 | 270 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}), | ||
385 | 271 | 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) | ||
386 | 272 | } | ||
387 | 273 | } | ||
388 | 274 | |||
389 | 275 | complete_apps = ['maasserver'] | ||
390 | 0 | \ No newline at end of file | 276 | \ No newline at end of file |
391 | 1 | 277 | ||
392 | === modified file 'src/maasserver/models/bootimage.py' | |||
393 | --- src/maasserver/models/bootimage.py 2014-03-26 16:01:34 +0000 | |||
394 | +++ src/maasserver/models/bootimage.py 2014-05-02 19:22:04 +0000 | |||
395 | @@ -34,44 +34,47 @@ | |||
396 | 34 | Don't import or instantiate this directly; access as `BootImage.objects`. | 34 | Don't import or instantiate this directly; access as `BootImage.objects`. |
397 | 35 | """ | 35 | """ |
398 | 36 | 36 | ||
401 | 37 | def get_by_natural_key(self, nodegroup, architecture, subarchitecture, | 37 | def get_by_natural_key(self, nodegroup, osystem, architecture, |
402 | 38 | release, purpose, label): | 38 | subarchitecture, release, purpose, label): |
403 | 39 | """Look up a specific image.""" | 39 | """Look up a specific image.""" |
404 | 40 | return self.get( | 40 | return self.get( |
406 | 41 | nodegroup=nodegroup, architecture=architecture, | 41 | nodegroup=nodegroup, osystem=osystem, architecture=architecture, |
407 | 42 | subarchitecture=subarchitecture, release=release, | 42 | subarchitecture=subarchitecture, release=release, |
408 | 43 | purpose=purpose, label=label) | 43 | purpose=purpose, label=label) |
409 | 44 | 44 | ||
411 | 45 | def register_image(self, nodegroup, architecture, subarchitecture, | 45 | def register_image(self, nodegroup, osystem, architecture, subarchitecture, |
412 | 46 | release, purpose, label): | 46 | release, purpose, label): |
413 | 47 | """Register an image if it wasn't already registered.""" | 47 | """Register an image if it wasn't already registered.""" |
414 | 48 | self.get_or_create( | 48 | self.get_or_create( |
416 | 49 | nodegroup=nodegroup, architecture=architecture, | 49 | nodegroup=nodegroup, osystem=osystem, architecture=architecture, |
417 | 50 | subarchitecture=subarchitecture, release=release, | 50 | subarchitecture=subarchitecture, release=release, |
418 | 51 | purpose=purpose, label=label) | 51 | purpose=purpose, label=label) |
419 | 52 | 52 | ||
422 | 53 | def have_image(self, nodegroup, architecture, subarchitecture, release, | 53 | def have_image(self, nodegroup, osystem, architecture, subarchitecture, |
423 | 54 | purpose, label=None): | 54 | release, purpose, label=None): |
424 | 55 | """Is an image for the given kind of boot available?""" | 55 | """Is an image for the given kind of boot available?""" |
425 | 56 | if label is None: | 56 | if label is None: |
426 | 57 | label = "release" | 57 | label = "release" |
427 | 58 | try: | 58 | try: |
428 | 59 | self.get_by_natural_key( | 59 | self.get_by_natural_key( |
432 | 60 | nodegroup=nodegroup, architecture=architecture, | 60 | nodegroup=nodegroup, osystem=osystem, |
433 | 61 | subarchitecture=subarchitecture, release=release, | 61 | architecture=architecture, subarchitecture=subarchitecture, |
434 | 62 | purpose=purpose, label=label) | 62 | release=release, purpose=purpose, label=label) |
435 | 63 | return True | 63 | return True |
436 | 64 | except BootImage.DoesNotExist: | 64 | except BootImage.DoesNotExist: |
437 | 65 | return False | 65 | return False |
438 | 66 | 66 | ||
441 | 67 | def get_default_arch_image_in_nodegroup(self, nodegroup, series, purpose): | 67 | def get_default_arch_image_in_nodegroup(self, nodegroup, osystem, series, |
442 | 68 | """Return any image for the given nodegroup, series, and purpose. | 68 | purpose): |
443 | 69 | """Return any image for the given nodegroup, osystem, series, | ||
444 | 70 | and purpose. | ||
445 | 69 | 71 | ||
446 | 70 | Prefers `i386` images if available. Returns `None` if no images match | 72 | Prefers `i386` images if available. Returns `None` if no images match |
447 | 71 | requirements. | 73 | requirements. |
448 | 72 | """ | 74 | """ |
449 | 73 | images = BootImage.objects.filter( | 75 | images = BootImage.objects.filter( |
451 | 74 | release=series, nodegroup=nodegroup, purpose=purpose) | 76 | osystem=osystem, release=series, nodegroup=nodegroup, |
452 | 77 | purpose=purpose) | ||
453 | 75 | for image in images: | 78 | for image in images: |
454 | 76 | # Prefer i386, any available subarchitecture (usually just | 79 | # Prefer i386, any available subarchitecture (usually just |
455 | 77 | # "generic"). It will work for most cases where we don't know | 80 | # "generic"). It will work for most cases where we don't know |
456 | @@ -106,14 +109,28 @@ | |||
457 | 106 | nodegroup, 'install') | 109 | nodegroup, 'install') |
458 | 107 | return arches_commissioning & arches_install | 110 | return arches_commissioning & arches_install |
459 | 108 | 111 | ||
462 | 109 | def get_latest_image(self, nodegroup, architecture, subarchitecture, | 112 | def get_latest_image(self, nodegroup, osystem, architecture, |
463 | 110 | release, purpose): | 113 | subarchitecture, release, purpose): |
464 | 111 | """Return the latest image for a set of criteria.""" | 114 | """Return the latest image for a set of criteria.""" |
465 | 112 | return BootImage.objects.filter( | 115 | return BootImage.objects.filter( |
467 | 113 | nodegroup=nodegroup, architecture=architecture, | 116 | nodegroup=nodegroup, osystem=osystem, architecture=architecture, |
468 | 114 | subarchitecture=subarchitecture, release=release, | 117 | subarchitecture=subarchitecture, release=release, |
469 | 115 | purpose=purpose).order_by('id').last() | 118 | purpose=purpose).order_by('id').last() |
470 | 116 | 119 | ||
471 | 120 | def get_usable_osystems(self, nodegroup): | ||
472 | 121 | """Return the list of usable operating systems for a nodegroup. | ||
473 | 122 | """ | ||
474 | 123 | query = BootImage.objects.filter(nodegroup=nodegroup) | ||
475 | 124 | return set(query.values_list('osystem', flat=True)) | ||
476 | 125 | |||
477 | 126 | def get_usable_releases(self, nodegroup, osystem): | ||
478 | 127 | """Return the list of usable releases for a nodegroup and | ||
479 | 128 | operating system. | ||
480 | 129 | """ | ||
481 | 130 | query = BootImage.objects.filter(nodegroup=nodegroup, osystem=osystem) | ||
482 | 131 | releases = query.values_list('release', flat=True) | ||
483 | 132 | return set(releases) | ||
484 | 133 | |||
485 | 117 | 134 | ||
486 | 118 | class BootImage(TimestampedModel): | 135 | class BootImage(TimestampedModel): |
487 | 119 | """Available boot image (i.e. kernel and initrd). | 136 | """Available boot image (i.e. kernel and initrd). |
488 | @@ -131,8 +148,8 @@ | |||
489 | 131 | 148 | ||
490 | 132 | class Meta(DefaultMeta): | 149 | class Meta(DefaultMeta): |
491 | 133 | unique_together = ( | 150 | unique_together = ( |
494 | 134 | ('nodegroup', 'architecture', 'subarchitecture', 'release', | 151 | ('nodegroup', 'osystem', 'architecture', 'subarchitecture', |
495 | 135 | 'purpose', 'label'), | 152 | 'release', 'purpose', 'label'), |
496 | 136 | ) | 153 | ) |
497 | 137 | 154 | ||
498 | 138 | objects = BootImageManager() | 155 | objects = BootImageManager() |
499 | @@ -140,6 +157,9 @@ | |||
500 | 140 | # Nodegroup (cluster controller) that has the images. | 157 | # Nodegroup (cluster controller) that has the images. |
501 | 141 | nodegroup = ForeignKey(NodeGroup, null=False, editable=False, unique=False) | 158 | nodegroup = ForeignKey(NodeGroup, null=False, editable=False, unique=False) |
502 | 142 | 159 | ||
503 | 160 | # Operating system (e.g. "ubuntu") that the image boots. | ||
504 | 161 | osystem = CharField(max_length=255, blank=False, editable=False) | ||
505 | 162 | |||
506 | 143 | # System architecture (e.g. "i386") that the image is for. | 163 | # System architecture (e.g. "i386") that the image is for. |
507 | 144 | architecture = CharField(max_length=255, blank=False, editable=False) | 164 | architecture = CharField(max_length=255, blank=False, editable=False) |
508 | 145 | 165 | ||
509 | @@ -148,7 +168,7 @@ | |||
510 | 148 | # such as i386 and amd64, we use "generic"). | 168 | # such as i386 and amd64, we use "generic"). |
511 | 149 | subarchitecture = CharField(max_length=255, blank=False, editable=False) | 169 | subarchitecture = CharField(max_length=255, blank=False, editable=False) |
512 | 150 | 170 | ||
514 | 151 | # Ubuntu release (e.g. "precise") that the image boots. | 171 | # OS release (e.g. "precise") that the image boots. |
515 | 152 | release = CharField(max_length=255, blank=False, editable=False) | 172 | release = CharField(max_length=255, blank=False, editable=False) |
516 | 153 | 173 | ||
517 | 154 | # Boot purpose (e.g. "commissioning" or "install") that the image is for. | 174 | # Boot purpose (e.g. "commissioning" or "install") that the image is for. |
518 | @@ -159,7 +179,8 @@ | |||
519 | 159 | max_length=255, blank=False, editable=False, default="release") | 179 | max_length=255, blank=False, editable=False, default="release") |
520 | 160 | 180 | ||
521 | 161 | def __repr__(self): | 181 | def __repr__(self): |
523 | 162 | return "<BootImage %s/%s-%s-%s-%s>" % ( | 182 | return "<BootImage %s-%s/%s-%s-%s-%s>" % ( |
524 | 183 | self.osystem, | ||
525 | 163 | self.architecture, | 184 | self.architecture, |
526 | 164 | self.subarchitecture, | 185 | self.subarchitecture, |
527 | 165 | self.release, | 186 | self.release, |
528 | 166 | 187 | ||
529 | === modified file 'src/maasserver/models/tests/test_bootimage.py' | |||
530 | --- src/maasserver/models/tests/test_bootimage.py 2014-03-18 14:39:11 +0000 | |||
531 | +++ src/maasserver/models/tests/test_bootimage.py 2014-05-02 19:22:04 +0000 | |||
532 | @@ -50,69 +50,79 @@ | |||
533 | 50 | self.assertTrue(BootImage.objects.have_image(nodegroup, **params)) | 50 | self.assertTrue(BootImage.objects.have_image(nodegroup, **params)) |
534 | 51 | 51 | ||
535 | 52 | def test_default_arch_image_returns_None_if_no_images_match(self): | 52 | def test_default_arch_image_returns_None_if_no_images_match(self): |
536 | 53 | osystem = Config.objects.get_config('commissioning_osystem') | ||
537 | 53 | series = Config.objects.get_config('commissioning_distro_series') | 54 | series = Config.objects.get_config('commissioning_distro_series') |
538 | 54 | result = BootImage.objects.get_default_arch_image_in_nodegroup( | 55 | result = BootImage.objects.get_default_arch_image_in_nodegroup( |
540 | 55 | factory.make_node_group(), series, factory.make_name('purpose')) | 56 | factory.make_node_group(), osystem, series, |
541 | 57 | factory.make_name('purpose')) | ||
542 | 56 | self.assertIsNone(result) | 58 | self.assertIsNone(result) |
543 | 57 | 59 | ||
544 | 58 | def test_default_arch_image_returns_only_matching_image(self): | 60 | def test_default_arch_image_returns_only_matching_image(self): |
545 | 59 | nodegroup = factory.make_node_group() | 61 | nodegroup = factory.make_node_group() |
546 | 62 | osystem = factory.make_name('os') | ||
547 | 60 | series = factory.make_name('series') | 63 | series = factory.make_name('series') |
548 | 61 | label = factory.make_name('label') | 64 | label = factory.make_name('label') |
549 | 62 | arch = factory.make_name('arch') | 65 | arch = factory.make_name('arch') |
550 | 63 | purpose = factory.make_name("purpose") | 66 | purpose = factory.make_name("purpose") |
551 | 64 | factory.make_boot_image( | 67 | factory.make_boot_image( |
553 | 65 | architecture=arch, release=series, label=label, | 68 | osystem=osystem, architecture=arch, |
554 | 69 | release=series, label=label, | ||
555 | 66 | nodegroup=nodegroup, purpose=purpose) | 70 | nodegroup=nodegroup, purpose=purpose) |
556 | 67 | result = BootImage.objects.get_default_arch_image_in_nodegroup( | 71 | result = BootImage.objects.get_default_arch_image_in_nodegroup( |
558 | 68 | nodegroup, series, purpose=purpose) | 72 | nodegroup, osystem, series, purpose=purpose) |
559 | 69 | self.assertEqual(result.architecture, arch) | 73 | self.assertEqual(result.architecture, arch) |
560 | 70 | 74 | ||
561 | 71 | def test_default_arch_image_prefers_i386(self): | 75 | def test_default_arch_image_prefers_i386(self): |
562 | 72 | nodegroup = factory.make_node_group() | 76 | nodegroup = factory.make_node_group() |
563 | 77 | osystem = factory.make_name('os') | ||
564 | 73 | series = factory.make_name('series') | 78 | series = factory.make_name('series') |
565 | 74 | label = factory.make_name('label') | 79 | label = factory.make_name('label') |
566 | 75 | purpose = factory.make_name("purpose") | 80 | purpose = factory.make_name("purpose") |
567 | 76 | for arch in ['amd64', 'axp', 'i386', 'm88k']: | 81 | for arch in ['amd64', 'axp', 'i386', 'm88k']: |
568 | 77 | factory.make_boot_image( | 82 | factory.make_boot_image( |
570 | 78 | architecture=arch, release=series, nodegroup=nodegroup, | 83 | osystem=osystem, architecture=arch, |
571 | 84 | release=series, nodegroup=nodegroup, | ||
572 | 79 | purpose=purpose, label=label) | 85 | purpose=purpose, label=label) |
573 | 80 | result = BootImage.objects.get_default_arch_image_in_nodegroup( | 86 | result = BootImage.objects.get_default_arch_image_in_nodegroup( |
575 | 81 | nodegroup, series, purpose=purpose) | 87 | nodegroup, osystem, series, purpose=purpose) |
576 | 82 | self.assertEqual(result.architecture, "i386") | 88 | self.assertEqual(result.architecture, "i386") |
577 | 83 | 89 | ||
578 | 84 | def test_default_arch_image_returns_arbitrary_pick_if_all_else_fails(self): | 90 | def test_default_arch_image_returns_arbitrary_pick_if_all_else_fails(self): |
579 | 85 | nodegroup = factory.make_node_group() | 91 | nodegroup = factory.make_node_group() |
580 | 92 | osystem = factory.make_name('os') | ||
581 | 86 | series = factory.make_name('series') | 93 | series = factory.make_name('series') |
582 | 87 | label = factory.make_name('label') | 94 | label = factory.make_name('label') |
583 | 88 | purpose = factory.make_name("purpose") | 95 | purpose = factory.make_name("purpose") |
584 | 89 | images = [ | 96 | images = [ |
585 | 90 | factory.make_boot_image( | 97 | factory.make_boot_image( |
588 | 91 | architecture=factory.make_name('arch'), release=series, | 98 | osystem=osystem, architecture=factory.make_name('arch'), |
589 | 92 | label=label, nodegroup=nodegroup, purpose=purpose) | 99 | release=series, label=label, nodegroup=nodegroup, |
590 | 100 | purpose=purpose) | ||
591 | 93 | for _ in range(3) | 101 | for _ in range(3) |
592 | 94 | ] | 102 | ] |
593 | 95 | self.assertIn( | 103 | self.assertIn( |
594 | 96 | BootImage.objects.get_default_arch_image_in_nodegroup( | 104 | BootImage.objects.get_default_arch_image_in_nodegroup( |
596 | 97 | nodegroup, series, purpose=purpose), | 105 | nodegroup, osystem, series, purpose=purpose), |
597 | 98 | images) | 106 | images) |
598 | 99 | 107 | ||
599 | 100 | def test_default_arch_image_copes_with_subarches(self): | 108 | def test_default_arch_image_copes_with_subarches(self): |
600 | 101 | nodegroup = factory.make_node_group() | 109 | nodegroup = factory.make_node_group() |
601 | 102 | arch = 'i386' | 110 | arch = 'i386' |
602 | 111 | osystem = factory.make_name('os') | ||
603 | 103 | series = factory.make_name('series') | 112 | series = factory.make_name('series') |
604 | 104 | label = factory.make_name('label') | 113 | label = factory.make_name('label') |
605 | 105 | purpose = factory.make_name("purpose") | 114 | purpose = factory.make_name("purpose") |
606 | 106 | images = [ | 115 | images = [ |
607 | 107 | factory.make_boot_image( | 116 | factory.make_boot_image( |
609 | 108 | architecture=arch, subarchitecture=factory.make_name('sub'), | 117 | osystem=osystem, architecture=arch, |
610 | 118 | subarchitecture=factory.make_name('sub'), | ||
611 | 109 | release=series, label=label, nodegroup=nodegroup, | 119 | release=series, label=label, nodegroup=nodegroup, |
612 | 110 | purpose=purpose) | 120 | purpose=purpose) |
613 | 111 | for _ in range(3) | 121 | for _ in range(3) |
614 | 112 | ] | 122 | ] |
615 | 113 | self.assertIn( | 123 | self.assertIn( |
616 | 114 | BootImage.objects.get_default_arch_image_in_nodegroup( | 124 | BootImage.objects.get_default_arch_image_in_nodegroup( |
618 | 115 | nodegroup, series, purpose=purpose), | 125 | nodegroup, osystem, series, purpose=purpose), |
619 | 116 | images) | 126 | images) |
620 | 117 | 127 | ||
621 | 118 | def test_get_usable_architectures_returns_supported_arches(self): | 128 | def test_get_usable_architectures_returns_supported_arches(self): |
622 | @@ -164,54 +174,62 @@ | |||
623 | 164 | BootImage.objects.get_usable_architectures(nodegroup)) | 174 | BootImage.objects.get_usable_architectures(nodegroup)) |
624 | 165 | 175 | ||
625 | 166 | def test_get_latest_image_returns_latest_image_for_criteria(self): | 176 | def test_get_latest_image_returns_latest_image_for_criteria(self): |
626 | 177 | osystem = factory.make_name('os') | ||
627 | 167 | arch = factory.make_name('arch') | 178 | arch = factory.make_name('arch') |
628 | 168 | subarch = factory.make_name('sub') | 179 | subarch = factory.make_name('sub') |
629 | 169 | release = factory.make_name('release') | 180 | release = factory.make_name('release') |
630 | 170 | nodegroup = factory.make_node_group() | 181 | nodegroup = factory.make_node_group() |
631 | 171 | purpose = factory.make_name("purpose") | 182 | purpose = factory.make_name("purpose") |
632 | 172 | boot_image = factory.make_boot_image( | 183 | boot_image = factory.make_boot_image( |
634 | 173 | nodegroup=nodegroup, architecture=arch, | 184 | nodegroup=nodegroup, osystem=osystem, architecture=arch, |
635 | 174 | subarchitecture=subarch, release=release, purpose=purpose, | 185 | subarchitecture=subarch, release=release, purpose=purpose, |
636 | 175 | label=factory.make_name('label')) | 186 | label=factory.make_name('label')) |
637 | 176 | self.assertEqual( | 187 | self.assertEqual( |
638 | 177 | boot_image, | 188 | boot_image, |
639 | 178 | BootImage.objects.get_latest_image( | 189 | BootImage.objects.get_latest_image( |
641 | 179 | nodegroup, arch, subarch, release, purpose)) | 190 | nodegroup, osystem, arch, subarch, release, purpose)) |
642 | 180 | 191 | ||
643 | 181 | def test_get_latest_image_doesnt_return_images_for_other_purposes(self): | 192 | def test_get_latest_image_doesnt_return_images_for_other_purposes(self): |
644 | 193 | osystem = factory.make_name('os') | ||
645 | 182 | arch = factory.make_name('arch') | 194 | arch = factory.make_name('arch') |
646 | 183 | subarch = factory.make_name('sub') | 195 | subarch = factory.make_name('sub') |
647 | 184 | release = factory.make_name('release') | 196 | release = factory.make_name('release') |
648 | 185 | nodegroup = factory.make_node_group() | 197 | nodegroup = factory.make_node_group() |
649 | 186 | purpose = factory.make_name("purpose") | 198 | purpose = factory.make_name("purpose") |
650 | 187 | relevant_image = factory.make_boot_image( | 199 | relevant_image = factory.make_boot_image( |
652 | 188 | nodegroup=nodegroup, architecture=arch, | 200 | nodegroup=nodegroup, osystem=osystem, architecture=arch, |
653 | 189 | subarchitecture=subarch, release=release, purpose=purpose, | 201 | subarchitecture=subarch, release=release, purpose=purpose, |
654 | 190 | label=factory.make_name('label')) | 202 | label=factory.make_name('label')) |
655 | 191 | 203 | ||
656 | 192 | # Create a bunch of more recent but irrelevant BootImages.. | 204 | # Create a bunch of more recent but irrelevant BootImages.. |
657 | 193 | factory.make_boot_image( | 205 | factory.make_boot_image( |
660 | 194 | nodegroup=factory.make_node_group(), architecture=arch, | 206 | nodegroup=factory.make_node_group(), osystem=osystem, |
661 | 195 | subarchitecture=subarch, release=release, | 207 | architecture=arch, subarchitecture=subarch, release=release, |
662 | 196 | purpose=purpose, label=factory.make_name('label')) | 208 | purpose=purpose, label=factory.make_name('label')) |
663 | 197 | factory.make_boot_image( | 209 | factory.make_boot_image( |
665 | 198 | nodegroup=nodegroup, | 210 | nodegroup=nodegroup, osystem=osystem, |
666 | 199 | architecture=factory.make_name('arch'), | 211 | architecture=factory.make_name('arch'), |
667 | 200 | subarchitecture=subarch, release=release, purpose=purpose, | 212 | subarchitecture=subarch, release=release, purpose=purpose, |
668 | 201 | label=factory.make_name('label')) | 213 | label=factory.make_name('label')) |
669 | 202 | factory.make_boot_image( | 214 | factory.make_boot_image( |
671 | 203 | nodegroup=nodegroup, architecture=arch, | 215 | nodegroup=nodegroup, osystem=osystem, architecture=arch, |
672 | 204 | subarchitecture=factory.make_name('subarch'), | 216 | subarchitecture=factory.make_name('subarch'), |
673 | 205 | release=release, purpose=purpose, | 217 | release=release, purpose=purpose, |
674 | 206 | label=factory.make_name('label')) | 218 | label=factory.make_name('label')) |
675 | 207 | factory.make_boot_image( | 219 | factory.make_boot_image( |
677 | 208 | nodegroup=nodegroup, | 220 | nodegroup=nodegroup, osystem=osystem, |
678 | 209 | architecture=factory.make_name('arch'), | 221 | architecture=factory.make_name('arch'), |
679 | 210 | subarchitecture=subarch, | 222 | subarchitecture=subarch, |
680 | 211 | release=factory.make_name('release'), purpose=purpose, | 223 | release=factory.make_name('release'), purpose=purpose, |
681 | 212 | label=factory.make_name('label')) | 224 | label=factory.make_name('label')) |
682 | 213 | factory.make_boot_image( | 225 | factory.make_boot_image( |
684 | 214 | nodegroup=nodegroup, | 226 | nodegroup=nodegroup, osystem=osystem, |
685 | 227 | architecture=factory.make_name('arch'), | ||
686 | 228 | subarchitecture=subarch, release=release, | ||
687 | 229 | purpose=factory.make_name('purpose'), | ||
688 | 230 | label=factory.make_name('label')) | ||
689 | 231 | factory.make_boot_image( | ||
690 | 232 | nodegroup=nodegroup, osystem=factory.make_name('os'), | ||
691 | 215 | architecture=factory.make_name('arch'), | 233 | architecture=factory.make_name('arch'), |
692 | 216 | subarchitecture=subarch, release=release, | 234 | subarchitecture=subarch, release=release, |
693 | 217 | purpose=factory.make_name('purpose'), | 235 | purpose=factory.make_name('purpose'), |
694 | @@ -220,4 +238,66 @@ | |||
695 | 220 | self.assertEqual( | 238 | self.assertEqual( |
696 | 221 | relevant_image, | 239 | relevant_image, |
697 | 222 | BootImage.objects.get_latest_image( | 240 | BootImage.objects.get_latest_image( |
699 | 223 | nodegroup, arch, subarch, release, purpose)) | 241 | nodegroup, osystem, arch, subarch, release, purpose)) |
700 | 242 | |||
701 | 243 | def test_get_usable_osystems_returns_supported_osystems(self): | ||
702 | 244 | nodegroup = factory.make_node_group() | ||
703 | 245 | osystems = [ | ||
704 | 246 | factory.make_name('os'), | ||
705 | 247 | factory.make_name('os'), | ||
706 | 248 | ] | ||
707 | 249 | for osystem in osystems: | ||
708 | 250 | factory.make_boot_image( | ||
709 | 251 | osystem=osystem, | ||
710 | 252 | nodegroup=nodegroup) | ||
711 | 253 | self.assertItemsEqual( | ||
712 | 254 | osystems, | ||
713 | 255 | BootImage.objects.get_usable_osystems(nodegroup)) | ||
714 | 256 | |||
715 | 257 | def test_get_usable_osystems_uses_given_nodegroup(self): | ||
716 | 258 | nodegroup = factory.make_node_group() | ||
717 | 259 | osystem = factory.make_name('os') | ||
718 | 260 | factory.make_boot_image( | ||
719 | 261 | osystem=osystem, nodegroup=nodegroup) | ||
720 | 262 | self.assertItemsEqual( | ||
721 | 263 | [], | ||
722 | 264 | BootImage.objects.get_usable_osystems( | ||
723 | 265 | factory.make_node_group())) | ||
724 | 266 | |||
725 | 267 | def test_get_usable_releases_returns_supported_releases(self): | ||
726 | 268 | nodegroup = factory.make_node_group() | ||
727 | 269 | osystem = factory.make_name('os') | ||
728 | 270 | releases = [ | ||
729 | 271 | factory.make_name('release'), | ||
730 | 272 | factory.make_name('release'), | ||
731 | 273 | ] | ||
732 | 274 | for release in releases: | ||
733 | 275 | factory.make_boot_image( | ||
734 | 276 | osystem=osystem, | ||
735 | 277 | release=release, | ||
736 | 278 | nodegroup=nodegroup) | ||
737 | 279 | self.assertItemsEqual( | ||
738 | 280 | releases, | ||
739 | 281 | BootImage.objects.get_usable_releases(nodegroup, osystem)) | ||
740 | 282 | |||
741 | 283 | def test_get_usable_releases_uses_given_nodegroup(self): | ||
742 | 284 | nodegroup = factory.make_node_group() | ||
743 | 285 | osystem = factory.make_name('os') | ||
744 | 286 | release = factory.make_name('release') | ||
745 | 287 | factory.make_boot_image( | ||
746 | 288 | osystem=osystem, release=release, nodegroup=nodegroup) | ||
747 | 289 | self.assertItemsEqual( | ||
748 | 290 | [], | ||
749 | 291 | BootImage.objects.get_usable_releases( | ||
750 | 292 | factory.make_node_group(), osystem)) | ||
751 | 293 | |||
752 | 294 | def test_get_usable_releases_uses_given_osystem(self): | ||
753 | 295 | nodegroup = factory.make_node_group() | ||
754 | 296 | osystem = factory.make_name('os') | ||
755 | 297 | release = factory.make_name('release') | ||
756 | 298 | factory.make_boot_image( | ||
757 | 299 | osystem=osystem, release=release, nodegroup=nodegroup) | ||
758 | 300 | self.assertItemsEqual( | ||
759 | 301 | [], | ||
760 | 302 | BootImage.objects.get_usable_releases( | ||
761 | 303 | factory.make_node_group(), factory.make_name('os'))) | ||
762 | 224 | 304 | ||
763 | === modified file 'src/maasserver/preseed.py' | |||
764 | --- src/maasserver/preseed.py 2014-04-10 13:43:33 +0000 | |||
765 | +++ src/maasserver/preseed.py 2014-05-02 19:22:04 +0000 | |||
766 | @@ -93,6 +93,7 @@ | |||
767 | 93 | 93 | ||
768 | 94 | def get_curtin_installer_url(node): | 94 | def get_curtin_installer_url(node): |
769 | 95 | """Return the URL where curtin on the node can download its installer.""" | 95 | """Return the URL where curtin on the node can download its installer.""" |
770 | 96 | osystem = 'ubuntu' | ||
771 | 96 | series = node.get_distro_series() | 97 | series = node.get_distro_series() |
772 | 97 | cluster_host = pick_cluster_controller_address(node) | 98 | cluster_host = pick_cluster_controller_address(node) |
773 | 98 | # XXX rvb(?): The path shouldn't be hardcoded like this, but rather synced | 99 | # XXX rvb(?): The path shouldn't be hardcoded like this, but rather synced |
774 | @@ -100,18 +101,20 @@ | |||
775 | 100 | arch, subarch = node.architecture.split('/') | 101 | arch, subarch = node.architecture.split('/') |
776 | 101 | purpose = 'xinstall' | 102 | purpose = 'xinstall' |
777 | 102 | image = BootImage.objects.get_latest_image( | 103 | image = BootImage.objects.get_latest_image( |
779 | 103 | node.nodegroup, arch, subarch, series, purpose) | 104 | node.nodegroup, osystem, arch, subarch, series, purpose) |
780 | 104 | if image is None: | 105 | if image is None: |
781 | 105 | raise MAASAPIException( | 106 | raise MAASAPIException( |
782 | 106 | "Error generating the URL of curtin's root-tgz file. " | 107 | "Error generating the URL of curtin's root-tgz file. " |
783 | 107 | "No image could be found for the given selection: " | 108 | "No image could be found for the given selection: " |
785 | 108 | "arch=%s, subarch=%s, series=%s, purpose=%s." % ( | 109 | "os=%s, arch=%s, subarch=%s, series=%s, purpose=%s." % ( |
786 | 110 | osystem, | ||
787 | 109 | arch, | 111 | arch, |
788 | 110 | subarch, | 112 | subarch, |
789 | 111 | series, | 113 | series, |
790 | 112 | purpose | 114 | purpose |
791 | 113 | )) | 115 | )) |
792 | 114 | dyn_uri = '/'.join([ | 116 | dyn_uri = '/'.join([ |
793 | 117 | osystem, | ||
794 | 115 | arch, | 118 | arch, |
795 | 116 | subarch, | 119 | subarch, |
796 | 117 | series, | 120 | series, |
797 | 118 | 121 | ||
798 | === modified file 'src/maasserver/templates/maasserver/bootimage-list.html' | |||
799 | --- src/maasserver/templates/maasserver/bootimage-list.html 2014-03-27 07:39:38 +0000 | |||
800 | +++ src/maasserver/templates/maasserver/bootimage-list.html 2014-05-02 19:22:04 +0000 | |||
801 | @@ -20,6 +20,7 @@ | |||
802 | 20 | <thead> | 20 | <thead> |
803 | 21 | <tr> | 21 | <tr> |
804 | 22 | <th>ID</th> | 22 | <th>ID</th> |
805 | 23 | <th>OS</th> | ||
806 | 23 | <th>Release</th> | 24 | <th>Release</th> |
807 | 24 | <th>Architecture</th> | 25 | <th>Architecture</th> |
808 | 25 | <th>Subarchitecture</th> | 26 | <th>Subarchitecture</th> |
809 | @@ -32,6 +33,7 @@ | |||
810 | 32 | {% for bootimage in bootimage_list %} | 33 | {% for bootimage in bootimage_list %} |
811 | 33 | <tr class="bootimage {% cycle 'even' 'odd' %}"> | 34 | <tr class="bootimage {% cycle 'even' 'odd' %}"> |
812 | 34 | <td>{{ bootimage.id }}</td> | 35 | <td>{{ bootimage.id }}</td> |
813 | 36 | <td>{{ bootimage.osystem }}</td> | ||
814 | 35 | <td>{{ bootimage.release }}</td> | 37 | <td>{{ bootimage.release }}</td> |
815 | 36 | <td>{{ bootimage.architecture }}</td> | 38 | <td>{{ bootimage.architecture }}</td> |
816 | 37 | <td>{{ bootimage.subarchitecture }}</td> | 39 | <td>{{ bootimage.subarchitecture }}</td> |
817 | 38 | 40 | ||
818 | === modified file 'src/maasserver/testing/factory.py' | |||
819 | --- src/maasserver/testing/factory.py 2014-04-04 06:46:05 +0000 | |||
820 | +++ src/maasserver/testing/factory.py 2014-05-02 19:22:04 +0000 | |||
821 | @@ -427,9 +427,11 @@ | |||
822 | 427 | return "OAuth " + ", ".join([ | 427 | return "OAuth " + ", ".join([ |
823 | 428 | '%s="%s"' % (key, value) for key, value in items.items()]) | 428 | '%s="%s"' % (key, value) for key, value in items.items()]) |
824 | 429 | 429 | ||
828 | 430 | def make_boot_image(self, architecture=None, subarchitecture=None, | 430 | def make_boot_image(self, osystem=None, architecture=None, |
829 | 431 | release=None, purpose=None, nodegroup=None, | 431 | subarchitecture=None, release=None, purpose=None, |
830 | 432 | label=None): | 432 | nodegroup=None, label=None): |
831 | 433 | if osystem is None: | ||
832 | 434 | osystem = self.make_name('os') | ||
833 | 433 | if architecture is None: | 435 | if architecture is None: |
834 | 434 | architecture = self.make_name('architecture') | 436 | architecture = self.make_name('architecture') |
835 | 435 | if subarchitecture is None: | 437 | if subarchitecture is None: |
836 | @@ -444,6 +446,7 @@ | |||
837 | 444 | label = self.make_name('label') | 446 | label = self.make_name('label') |
838 | 445 | return BootImage.objects.create( | 447 | return BootImage.objects.create( |
839 | 446 | nodegroup=nodegroup, | 448 | nodegroup=nodegroup, |
840 | 449 | osystem=osystem, | ||
841 | 447 | architecture=architecture, | 450 | architecture=architecture, |
842 | 448 | subarchitecture=subarchitecture, | 451 | subarchitecture=subarchitecture, |
843 | 449 | release=release, | 452 | release=release, |
844 | 450 | 453 | ||
845 | === modified file 'src/maasserver/tests/test_api_boot_images.py' | |||
846 | --- src/maasserver/tests/test_api_boot_images.py 2014-03-21 19:01:40 +0000 | |||
847 | +++ src/maasserver/tests/test_api_boot_images.py 2014-05-02 19:22:04 +0000 | |||
848 | @@ -134,6 +134,7 @@ | |||
849 | 134 | image = factory.make_boot_image() | 134 | image = factory.make_boot_image() |
850 | 135 | self.assertEqual( | 135 | self.assertEqual( |
851 | 136 | ( | 136 | ( |
852 | 137 | image.osystem, | ||
853 | 137 | image.architecture, | 138 | image.architecture, |
854 | 138 | image.subarchitecture, | 139 | image.subarchitecture, |
855 | 139 | image.release, | 140 | image.release, |
856 | @@ -146,6 +147,7 @@ | |||
857 | 146 | image = make_boot_image_params() | 147 | image = make_boot_image_params() |
858 | 147 | self.assertEqual( | 148 | self.assertEqual( |
859 | 148 | ( | 149 | ( |
860 | 150 | image['osystem'], | ||
861 | 149 | image['architecture'], | 151 | image['architecture'], |
862 | 150 | image['subarchitecture'], | 152 | image['subarchitecture'], |
863 | 151 | image['release'], | 153 | image['release'], |
864 | @@ -158,12 +160,13 @@ | |||
865 | 158 | image = make_boot_image_params() | 160 | image = make_boot_image_params() |
866 | 159 | del image['subarchitecture'] | 161 | del image['subarchitecture'] |
867 | 160 | del image['label'] | 162 | del image['label'] |
869 | 161 | _, subarchitecture, _, label, _ = summarise_boot_image_dict(image) | 163 | _, _, subarchitecture, _, label, _ = summarise_boot_image_dict(image) |
870 | 162 | self.assertEqual(('generic', 'release'), (subarchitecture, label)) | 164 | self.assertEqual(('generic', 'release'), (subarchitecture, label)) |
871 | 163 | 165 | ||
872 | 164 | def test_summarise_boot_image_functions_are_compatible(self): | 166 | def test_summarise_boot_image_functions_are_compatible(self): |
873 | 165 | image_dict = make_boot_image_params() | 167 | image_dict = make_boot_image_params() |
874 | 166 | image_obj = factory.make_boot_image( | 168 | image_obj = factory.make_boot_image( |
875 | 169 | osystem=image_dict['osystem'], | ||
876 | 167 | architecture=image_dict['architecture'], | 170 | architecture=image_dict['architecture'], |
877 | 168 | subarchitecture=image_dict['subarchitecture'], | 171 | subarchitecture=image_dict['subarchitecture'], |
878 | 169 | release=image_dict['release'], label=image_dict['label'], | 172 | release=image_dict['release'], label=image_dict['label'], |
879 | 170 | 173 | ||
880 | === modified file 'src/maasserver/tests/test_api_pxeconfig.py' | |||
881 | --- src/maasserver/tests/test_api_pxeconfig.py 2014-03-27 04:15:45 +0000 | |||
882 | +++ src/maasserver/tests/test_api_pxeconfig.py 2014-05-02 19:22:04 +0000 | |||
883 | @@ -133,9 +133,11 @@ | |||
884 | 133 | self.assertEqual(value, response_dict['extra_opts']) | 133 | self.assertEqual(value, response_dict['extra_opts']) |
885 | 134 | 134 | ||
886 | 135 | def test_pxeconfig_uses_present_boot_image(self): | 135 | def test_pxeconfig_uses_present_boot_image(self): |
887 | 136 | osystem = 'ubuntu' | ||
888 | 136 | release = Config.objects.get_config('commissioning_distro_series') | 137 | release = Config.objects.get_config('commissioning_distro_series') |
889 | 137 | nodegroup = factory.make_node_group() | 138 | nodegroup = factory.make_node_group() |
890 | 138 | factory.make_boot_image( | 139 | factory.make_boot_image( |
891 | 140 | osystem=osystem, | ||
892 | 139 | architecture="amd64", release=release, nodegroup=nodegroup, | 141 | architecture="amd64", release=release, nodegroup=nodegroup, |
893 | 140 | purpose="commissioning") | 142 | purpose="commissioning") |
894 | 141 | params = self.get_default_params() | 143 | params = self.get_default_params() |
895 | 142 | 144 | ||
896 | === modified file 'src/maasserver/tests/test_preseed.py' | |||
897 | --- src/maasserver/tests/test_preseed.py 2014-04-21 11:43:26 +0000 | |||
898 | +++ src/maasserver/tests/test_preseed.py 2014-05-02 19:22:04 +0000 | |||
899 | @@ -590,6 +590,7 @@ | |||
900 | 590 | node = factory.make_node() | 590 | node = factory.make_node() |
901 | 591 | arch, subarch = node.architecture.split('/') | 591 | arch, subarch = node.architecture.split('/') |
902 | 592 | factory.make_boot_image( | 592 | factory.make_boot_image( |
903 | 593 | osystem='ubuntu', | ||
904 | 593 | architecture=arch, subarchitecture=subarch, | 594 | architecture=arch, subarchitecture=subarch, |
905 | 594 | release=node.get_distro_series(), purpose='xinstall', | 595 | release=node.get_distro_series(), purpose='xinstall', |
906 | 595 | nodegroup=node.nodegroup) | 596 | nodegroup=node.nodegroup) |
907 | @@ -677,27 +678,32 @@ | |||
908 | 677 | self.assertIn('cloud-init', context['curtin_preseed']) | 678 | self.assertIn('cloud-init', context['curtin_preseed']) |
909 | 678 | 679 | ||
910 | 679 | def test_get_curtin_installer_url_returns_url(self): | 680 | def test_get_curtin_installer_url_returns_url(self): |
911 | 681 | osystem = 'ubuntu' | ||
912 | 680 | # Exclude DISTRO_SERIES.default. It's a special value that defers | 682 | # Exclude DISTRO_SERIES.default. It's a special value that defers |
913 | 681 | # to a run-time setting which we don't provide in this test. | 683 | # to a run-time setting which we don't provide in this test. |
914 | 682 | series = factory.getRandomEnum( | 684 | series = factory.getRandomEnum( |
915 | 683 | DISTRO_SERIES, but_not=DISTRO_SERIES.default) | 685 | DISTRO_SERIES, but_not=DISTRO_SERIES.default) |
916 | 684 | architecture = make_usable_architecture(self) | 686 | architecture = make_usable_architecture(self) |
917 | 685 | node = factory.make_node( | 687 | node = factory.make_node( |
919 | 686 | architecture=architecture, distro_series=series) | 688 | architecture=architecture, |
920 | 689 | distro_series=series) | ||
921 | 687 | arch, subarch = architecture.split('/') | 690 | arch, subarch = architecture.split('/') |
922 | 688 | boot_image = factory.make_boot_image( | 691 | boot_image = factory.make_boot_image( |
924 | 689 | architecture=arch, subarchitecture=subarch, release=series, | 692 | osystem=osystem, architecture=arch, |
925 | 693 | subarchitecture=subarch, release=series, | ||
926 | 690 | purpose='xinstall', nodegroup=node.nodegroup) | 694 | purpose='xinstall', nodegroup=node.nodegroup) |
927 | 691 | 695 | ||
928 | 692 | installer_url = get_curtin_installer_url(node) | 696 | installer_url = get_curtin_installer_url(node) |
929 | 693 | 697 | ||
930 | 694 | [interface] = node.nodegroup.get_managed_interfaces() | 698 | [interface] = node.nodegroup.get_managed_interfaces() |
931 | 695 | self.assertEqual( | 699 | self.assertEqual( |
934 | 696 | 'http://%s/MAAS/static/images/%s/%s/%s/%s/root-tgz' % ( | 700 | 'http://%s/MAAS/static/images/%s/%s/%s/%s/%s/root-tgz' % ( |
935 | 697 | interface.ip, arch, subarch, series, boot_image.label), | 701 | interface.ip, osystem, arch, subarch, |
936 | 702 | series, boot_image.label), | ||
937 | 698 | installer_url) | 703 | installer_url) |
938 | 699 | 704 | ||
939 | 700 | def test_get_curtin_installer_url_fails_if_no_boot_image(self): | 705 | def test_get_curtin_installer_url_fails_if_no_boot_image(self): |
940 | 706 | osystem = 'ubuntu' | ||
941 | 701 | series = factory.getRandomEnum( | 707 | series = factory.getRandomEnum( |
942 | 702 | DISTRO_SERIES, but_not=DISTRO_SERIES.default) | 708 | DISTRO_SERIES, but_not=DISTRO_SERIES.default) |
943 | 703 | architecture = make_usable_architecture(self) | 709 | architecture = make_usable_architecture(self) |
944 | @@ -705,6 +711,7 @@ | |||
945 | 705 | architecture=architecture, distro_series=series) | 711 | architecture=architecture, distro_series=series) |
946 | 706 | # Generate a boot image with a different arch/subarch. | 712 | # Generate a boot image with a different arch/subarch. |
947 | 707 | factory.make_boot_image( | 713 | factory.make_boot_image( |
948 | 714 | osystem=osystem, | ||
949 | 708 | architecture=factory.make_name('arch'), | 715 | architecture=factory.make_name('arch'), |
950 | 709 | subarchitecture=factory.make_name('subarch'), release=series, | 716 | subarchitecture=factory.make_name('subarch'), release=series, |
951 | 710 | purpose='xinstall', nodegroup=node.nodegroup) | 717 | purpose='xinstall', nodegroup=node.nodegroup) |
952 | @@ -714,7 +721,8 @@ | |||
953 | 714 | arch, subarch = architecture.split('/') | 721 | arch, subarch = architecture.split('/') |
954 | 715 | msg = ( | 722 | msg = ( |
955 | 716 | "No image could be found for the given selection: " | 723 | "No image could be found for the given selection: " |
957 | 717 | "arch=%s, subarch=%s, series=%s, purpose=xinstall." % ( | 724 | "os=%s, arch=%s, subarch=%s, series=%s, purpose=xinstall." % ( |
958 | 725 | osystem, | ||
959 | 718 | arch, | 726 | arch, |
960 | 719 | subarch, | 727 | subarch, |
961 | 720 | node.get_distro_series(), | 728 | node.get_distro_series(), |
962 | 721 | 729 | ||
963 | === modified file 'src/maasserver/views/clusters.py' | |||
964 | --- src/maasserver/views/clusters.py 2014-04-03 11:20:03 +0000 | |||
965 | +++ src/maasserver/views/clusters.py 2014-05-02 19:22:04 +0000 | |||
966 | @@ -280,5 +280,5 @@ | |||
967 | 280 | nodegroup = self.get_nodegroup() | 280 | nodegroup = self.get_nodegroup() |
968 | 281 | # A sorted bootimages list. | 281 | # A sorted bootimages list. |
969 | 282 | return nodegroup.bootimage_set.all().order_by( | 282 | return nodegroup.bootimage_set.all().order_by( |
972 | 283 | '-release', 'architecture', 'subarchitecture', 'purpose', | 283 | 'osystem', '-release', 'architecture', 'subarchitecture', |
973 | 284 | 'label') | 284 | 'purpose', 'label') |
974 | 285 | 285 | ||
975 | === modified file 'src/maasserver/views/tests/test_boot_image_list.py' | |||
976 | --- src/maasserver/views/tests/test_boot_image_list.py 2014-04-02 13:53:19 +0000 | |||
977 | +++ src/maasserver/views/tests/test_boot_image_list.py 2014-05-02 19:22:04 +0000 | |||
978 | @@ -54,8 +54,7 @@ | |||
979 | 54 | self.client_log_in(as_admin=True) | 54 | self.client_log_in(as_admin=True) |
980 | 55 | nodegroup = factory.make_node_group() | 55 | nodegroup = factory.make_node_group() |
981 | 56 | # Create 4 images. | 56 | # Create 4 images. |
984 | 57 | [ | 57 | [factory.make_boot_image(nodegroup=nodegroup) for _ in range(4)] |
983 | 58 | factory.make_boot_image(nodegroup=nodegroup) for _ in range(4)] | ||
985 | 59 | response = self.client.get( | 58 | response = self.client.get( |
986 | 60 | reverse('cluster-bootimages-list', args=[nodegroup.uuid])) | 59 | reverse('cluster-bootimages-list', args=[nodegroup.uuid])) |
987 | 61 | self.assertEqual(httplib.OK, response.status_code) | 60 | self.assertEqual(httplib.OK, response.status_code) |
988 | 62 | 61 | ||
989 | === modified file 'src/metadataserver/tests/test_api.py' | |||
990 | --- src/metadataserver/tests/test_api.py 2014-03-24 13:02:28 +0000 | |||
991 | +++ src/metadataserver/tests/test_api.py 2014-05-02 19:22:04 +0000 | |||
992 | @@ -394,6 +394,7 @@ | |||
993 | 394 | node = factory.make_node() | 394 | node = factory.make_node() |
994 | 395 | arch, subarch = node.architecture.split('/') | 395 | arch, subarch = node.architecture.split('/') |
995 | 396 | factory.make_boot_image( | 396 | factory.make_boot_image( |
996 | 397 | osystem='ubuntu', | ||
997 | 397 | architecture=arch, subarchitecture=subarch, | 398 | architecture=arch, subarchitecture=subarch, |
998 | 398 | release=node.get_distro_series(), purpose='xinstall', | 399 | release=node.get_distro_series(), purpose='xinstall', |
999 | 399 | nodegroup=node.nodegroup) | 400 | nodegroup=node.nodegroup) |
1000 | 400 | 401 | ||
1001 | === modified file 'src/provisioningserver/boot/__init__.py' | |||
1002 | --- src/provisioningserver/boot/__init__.py 2014-03-28 16:46:55 +0000 | |||
1003 | +++ src/provisioningserver/boot/__init__.py 2014-05-02 19:22:04 +0000 | |||
1004 | @@ -168,7 +168,7 @@ | |||
1005 | 168 | """ | 168 | """ |
1006 | 169 | def image_dir(params): | 169 | def image_dir(params): |
1007 | 170 | return compose_image_path( | 170 | return compose_image_path( |
1009 | 171 | params.arch, params.subarch, | 171 | 'ubuntu', params.arch, params.subarch, |
1010 | 172 | params.release, params.label) | 172 | params.release, params.label) |
1011 | 173 | 173 | ||
1012 | 174 | def initrd_path(params): | 174 | def initrd_path(params): |
1013 | 175 | 175 | ||
1014 | === modified file 'src/provisioningserver/boot/tests/test_pxe.py' | |||
1015 | --- src/provisioningserver/boot/tests/test_pxe.py 2014-03-28 04:31:32 +0000 | |||
1016 | +++ src/provisioningserver/boot/tests/test_pxe.py 2014-05-02 19:22:04 +0000 | |||
1017 | @@ -163,7 +163,7 @@ | |||
1018 | 163 | self.assertThat(output, StartsWith("DEFAULT ")) | 163 | self.assertThat(output, StartsWith("DEFAULT ")) |
1019 | 164 | # The PXE parameters are all set according to the options. | 164 | # The PXE parameters are all set according to the options. |
1020 | 165 | image_dir = compose_image_path( | 165 | image_dir = compose_image_path( |
1022 | 166 | arch=params.arch, subarch=params.subarch, | 166 | osystem='ubuntu', arch=params.arch, subarch=params.subarch, |
1023 | 167 | release=params.release, label=params.label) | 167 | release=params.release, label=params.label) |
1024 | 168 | self.assertThat( | 168 | self.assertThat( |
1025 | 169 | output, MatchesAll( | 169 | output, MatchesAll( |
1026 | @@ -268,7 +268,8 @@ | |||
1027 | 268 | section = config[section_label] | 268 | section = config[section_label] |
1028 | 269 | self.assertThat( | 269 | self.assertThat( |
1029 | 270 | section, ContainsAll(("KERNEL", "INITRD", "APPEND"))) | 270 | section, ContainsAll(("KERNEL", "INITRD", "APPEND"))) |
1031 | 271 | contains_arch_path = StartsWith("%s/" % section_label) | 271 | contains_arch_path = StartsWith( |
1032 | 272 | "ubuntu/%s/" % section_label) | ||
1033 | 272 | self.assertThat(section["KERNEL"], contains_arch_path) | 273 | self.assertThat(section["KERNEL"], contains_arch_path) |
1034 | 273 | self.assertThat(section["INITRD"], contains_arch_path) | 274 | self.assertThat(section["INITRD"], contains_arch_path) |
1035 | 274 | self.assertIn("APPEND", section) | 275 | self.assertIn("APPEND", section) |
1036 | 275 | 276 | ||
1037 | === modified file 'src/provisioningserver/boot/tests/test_tftppath.py' | |||
1038 | --- src/provisioningserver/boot/tests/test_tftppath.py 2014-04-03 09:26:31 +0000 | |||
1039 | +++ src/provisioningserver/boot/tests/test_tftppath.py 2014-05-02 19:22:04 +0000 | |||
1040 | @@ -62,6 +62,7 @@ | |||
1041 | 62 | """Fake a boot image matching `image_params` under `tftproot`.""" | 62 | """Fake a boot image matching `image_params` under `tftproot`.""" |
1042 | 63 | image_dir = locate_tftp_path( | 63 | image_dir = locate_tftp_path( |
1043 | 64 | compose_image_path( | 64 | compose_image_path( |
1044 | 65 | osystem=image_params['osystem'], | ||
1045 | 65 | arch=image_params['architecture'], | 66 | arch=image_params['architecture'], |
1046 | 66 | subarch=image_params['subarchitecture'], | 67 | subarch=image_params['subarchitecture'], |
1047 | 67 | release=image_params['release'], | 68 | release=image_params['release'], |
1048 | @@ -72,21 +73,23 @@ | |||
1049 | 72 | factory.make_file(image_dir, 'initrd.gz') | 73 | factory.make_file(image_dir, 'initrd.gz') |
1050 | 73 | 74 | ||
1051 | 74 | def test_compose_image_path_follows_storage_directory_layout(self): | 75 | def test_compose_image_path_follows_storage_directory_layout(self): |
1052 | 76 | osystem = factory.make_name('osystem') | ||
1053 | 75 | arch = factory.make_name('arch') | 77 | arch = factory.make_name('arch') |
1054 | 76 | subarch = factory.make_name('subarch') | 78 | subarch = factory.make_name('subarch') |
1055 | 77 | release = factory.make_name('release') | 79 | release = factory.make_name('release') |
1056 | 78 | label = factory.make_name('label') | 80 | label = factory.make_name('label') |
1057 | 79 | self.assertEqual( | 81 | self.assertEqual( |
1060 | 80 | '%s/%s/%s/%s' % (arch, subarch, release, label), | 82 | '%s/%s/%s/%s/%s' % (osystem, arch, subarch, release, label), |
1061 | 81 | compose_image_path(arch, subarch, release, label)) | 83 | compose_image_path(osystem, arch, subarch, release, label)) |
1062 | 82 | 84 | ||
1063 | 83 | def test_compose_image_path_does_not_include_tftp_root(self): | 85 | def test_compose_image_path_does_not_include_tftp_root(self): |
1064 | 86 | osystem = factory.make_name('osystem') | ||
1065 | 84 | arch = factory.make_name('arch') | 87 | arch = factory.make_name('arch') |
1066 | 85 | subarch = factory.make_name('subarch') | 88 | subarch = factory.make_name('subarch') |
1067 | 86 | release = factory.make_name('release') | 89 | release = factory.make_name('release') |
1068 | 87 | label = factory.make_name('label') | 90 | label = factory.make_name('label') |
1069 | 88 | self.assertThat( | 91 | self.assertThat( |
1071 | 89 | compose_image_path(arch, subarch, release, label), | 92 | compose_image_path(osystem, arch, subarch, release, label), |
1072 | 90 | Not(StartsWith(self.tftproot))) | 93 | Not(StartsWith(self.tftproot))) |
1073 | 91 | 94 | ||
1074 | 92 | def test_locate_tftp_path_prefixes_tftp_root(self): | 95 | def test_locate_tftp_path_prefixes_tftp_root(self): |
1075 | 93 | 96 | ||
1076 | === modified file 'src/provisioningserver/boot/tests/test_uefi.py' | |||
1077 | --- src/provisioningserver/boot/tests/test_uefi.py 2014-03-28 19:03:46 +0000 | |||
1078 | +++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-02 19:22:04 +0000 | |||
1079 | @@ -73,7 +73,7 @@ | |||
1080 | 73 | self.assertThat(output, StartsWith("set default=\"0\"")) | 73 | self.assertThat(output, StartsWith("set default=\"0\"")) |
1081 | 74 | # The UEFI parameters are all set according to the options. | 74 | # The UEFI parameters are all set according to the options. |
1082 | 75 | image_dir = compose_image_path( | 75 | image_dir = compose_image_path( |
1084 | 76 | arch=params.arch, subarch=params.subarch, | 76 | osystem='ubuntu', arch=params.arch, subarch=params.subarch, |
1085 | 77 | release=params.release, label=params.label) | 77 | release=params.release, label=params.label) |
1086 | 78 | 78 | ||
1087 | 79 | self.assertThat( | 79 | self.assertThat( |
1088 | 80 | 80 | ||
1089 | === modified file 'src/provisioningserver/boot/tftppath.py' | |||
1090 | --- src/provisioningserver/boot/tftppath.py 2014-04-03 16:36:15 +0000 | |||
1091 | +++ src/provisioningserver/boot/tftppath.py 2014-05-02 19:22:04 +0000 | |||
1092 | @@ -29,12 +29,13 @@ | |||
1093 | 29 | logger = getLogger(__name__) | 29 | logger = getLogger(__name__) |
1094 | 30 | 30 | ||
1095 | 31 | 31 | ||
1097 | 32 | def compose_image_path(arch, subarch, release, label): | 32 | def compose_image_path(osystem, arch, subarch, release, label): |
1098 | 33 | """Compose the TFTP path for a PXE kernel/initrd directory. | 33 | """Compose the TFTP path for a PXE kernel/initrd directory. |
1099 | 34 | 34 | ||
1100 | 35 | The path returned is relative to the TFTP root, as it would be | 35 | The path returned is relative to the TFTP root, as it would be |
1101 | 36 | identified by clients on the network. | 36 | identified by clients on the network. |
1102 | 37 | 37 | ||
1103 | 38 | :param osystem: Operating system. | ||
1104 | 38 | :param arch: Main machine architecture. | 39 | :param arch: Main machine architecture. |
1105 | 39 | :param subarch: Sub-architecture, or "generic" if there is none. | 40 | :param subarch: Sub-architecture, or "generic" if there is none. |
1106 | 40 | :param release: Operating system release, e.g. "precise". | 41 | :param release: Operating system release, e.g. "precise". |
1107 | @@ -43,7 +44,7 @@ | |||
1108 | 43 | kernel and initrd) as exposed over TFTP. | 44 | kernel and initrd) as exposed over TFTP. |
1109 | 44 | """ | 45 | """ |
1110 | 45 | # This is a TFTP path, not a local filesystem path, so hard-code the slash. | 46 | # This is a TFTP path, not a local filesystem path, so hard-code the slash. |
1112 | 46 | return '/'.join([arch, subarch, release, label]) | 47 | return '/'.join([osystem, arch, subarch, release, label]) |
1113 | 47 | 48 | ||
1114 | 48 | 49 | ||
1115 | 49 | def locate_tftp_path(path, tftproot): | 50 | def locate_tftp_path(path, tftproot): |
1116 | @@ -116,7 +117,7 @@ | |||
1117 | 116 | The path must consist of a full [architecture, subarchitecture, release] | 117 | The path must consist of a full [architecture, subarchitecture, release] |
1118 | 117 | that identify a kind of boot that we may need an image for. | 118 | that identify a kind of boot that we may need an image for. |
1119 | 118 | """ | 119 | """ |
1121 | 119 | arch, subarch, release, label = path | 120 | osystem, arch, subarch, release, label = path |
1122 | 120 | # XXX: rvb 2014-03-24: The images import script currently imports all the | 121 | # XXX: rvb 2014-03-24: The images import script currently imports all the |
1123 | 121 | # images for the configured selections (where a selection is an | 122 | # images for the configured selections (where a selection is an |
1124 | 122 | # arch/subarch/series/label combination). When the import script grows the | 123 | # arch/subarch/series/label combination). When the import script grows the |
1125 | @@ -125,7 +126,7 @@ | |||
1126 | 125 | purposes = ['commissioning', 'install', 'xinstall'] | 126 | purposes = ['commissioning', 'install', 'xinstall'] |
1127 | 126 | return [ | 127 | return [ |
1128 | 127 | dict( | 128 | dict( |
1130 | 128 | architecture=arch, subarchitecture=subarch, | 129 | osystem=osystem, architecture=arch, subarchitecture=subarch, |
1131 | 129 | release=release, label=label, purpose=purpose) | 130 | release=release, label=label, purpose=purpose) |
1132 | 130 | for purpose in purposes | 131 | for purpose in purposes |
1133 | 131 | ] | 132 | ] |
1134 | @@ -139,9 +140,9 @@ | |||
1135 | 139 | `report_boot_images` API call. | 140 | `report_boot_images` API call. |
1136 | 140 | """ | 141 | """ |
1137 | 141 | # The sub-directories directly under tftproot, if they contain | 142 | # The sub-directories directly under tftproot, if they contain |
1139 | 142 | # images, represent architectures. | 143 | # images, represent operating systems. |
1140 | 143 | try: | 144 | try: |
1142 | 144 | potential_archs = list_subdirs(tftproot) | 145 | potential_osystems = list_subdirs(tftproot) |
1143 | 145 | except OSError as exception: | 146 | except OSError as exception: |
1144 | 146 | if exception.errno == errno.ENOENT: | 147 | if exception.errno == errno.ENOENT: |
1145 | 147 | # Directory does not exist, so return empty list. | 148 | # Directory does not exist, so return empty list. |
1146 | @@ -153,12 +154,12 @@ | |||
1147 | 153 | 154 | ||
1148 | 154 | # Starting point for iteration: paths that contain only the | 155 | # Starting point for iteration: paths that contain only the |
1149 | 155 | # top-level subdirectory of tftproot, i.e. the architecture name. | 156 | # top-level subdirectory of tftproot, i.e. the architecture name. |
1151 | 156 | paths = [[subdir] for subdir in potential_archs] | 157 | paths = [[subdir] for subdir in potential_osystems] |
1152 | 157 | 158 | ||
1153 | 158 | # Extend paths deeper into the filesystem, through the levels that | 159 | # Extend paths deeper into the filesystem, through the levels that |
1157 | 159 | # represent sub-architecture, release, and label. Any directory | 160 | # represent architecture, sub-architecture, release, and label. |
1158 | 160 | # that doesn't extend this deep isn't a boot image. | 161 | # Any directory that doesn't extend this deep isn't a boot image. |
1159 | 161 | for level in ['subarch', 'release', 'label']: | 162 | for level in ['arch', 'subarch', 'release', 'label']: |
1160 | 162 | paths = drill_down(tftproot, paths) | 163 | paths = drill_down(tftproot, paths) |
1161 | 163 | 164 | ||
1162 | 164 | # Each path we find this way should be a boot image. | 165 | # Each path we find this way should be a boot image. |
1163 | 165 | 166 | ||
1164 | === modified file 'src/provisioningserver/import_images/boot_resources.py' | |||
1165 | --- src/provisioningserver/import_images/boot_resources.py 2014-04-25 10:42:10 +0000 | |||
1166 | +++ src/provisioningserver/import_images/boot_resources.py 2014-05-02 19:22:04 +0000 | |||
1167 | @@ -45,7 +45,7 @@ | |||
1168 | 45 | """Raised when the config file for the script doesn't exist.""" | 45 | """Raised when the config file for the script doesn't exist.""" |
1169 | 46 | 46 | ||
1170 | 47 | 47 | ||
1172 | 48 | def tgt_entry(arch, subarch, release, label, image): | 48 | def tgt_entry(osystem, arch, subarch, release, label, image): |
1173 | 49 | """Generate tgt target used to commission arch/subarch with release | 49 | """Generate tgt target used to commission arch/subarch with release |
1174 | 50 | 50 | ||
1175 | 51 | Tgt target used to commission arch/subarch machine with a specific Ubuntu | 51 | Tgt target used to commission arch/subarch machine with a specific Ubuntu |
1176 | @@ -59,6 +59,7 @@ | |||
1177 | 59 | use the same inode for different tgt targets (even read-only targets which | 59 | use the same inode for different tgt targets (even read-only targets which |
1178 | 60 | looks like a bug to me) without this option enabled. | 60 | looks like a bug to me) without this option enabled. |
1179 | 61 | 61 | ||
1180 | 62 | :param osystem: Operating System name we generate tgt target for | ||
1181 | 62 | :param arch: Architecture name we generate tgt target for | 63 | :param arch: Architecture name we generate tgt target for |
1182 | 63 | :param subarch: Subarchitecture name we generate tgt target for | 64 | :param subarch: Subarchitecture name we generate tgt target for |
1183 | 64 | :param release: Ubuntu release we generate tgt target for | 65 | :param release: Ubuntu release we generate tgt target for |
1184 | @@ -67,7 +68,13 @@ | |||
1185 | 67 | :return Tgt entry which can be written to tgt-admin configuration file | 68 | :return Tgt entry which can be written to tgt-admin configuration file |
1186 | 68 | """ | 69 | """ |
1187 | 69 | prefix = 'iqn.2004-05.com.ubuntu:maas' | 70 | prefix = 'iqn.2004-05.com.ubuntu:maas' |
1189 | 70 | target_name = 'ephemeral-%s-%s-%s-%s' % (arch, subarch, release, label) | 71 | target_name = 'ephemeral-%s-%s-%s-%s-%s' % ( |
1190 | 72 | osystem, | ||
1191 | 73 | arch, | ||
1192 | 74 | subarch, | ||
1193 | 75 | release, | ||
1194 | 76 | label | ||
1195 | 77 | ) | ||
1196 | 71 | entry = dedent("""\ | 78 | entry = dedent("""\ |
1197 | 72 | <target {prefix}:{target_name}> | 79 | <target {prefix}:{target_name}> |
1198 | 73 | readonly 1 | 80 | readonly 1 |
1199 | @@ -110,17 +117,20 @@ | |||
1200 | 110 | # Use a set to make sure we don't register duplicate entries in tgt. | 117 | # Use a set to make sure we don't register duplicate entries in tgt. |
1201 | 111 | entries = set() | 118 | entries = set() |
1202 | 112 | for item in list_boot_images(snapshot_path): | 119 | for item in list_boot_images(snapshot_path): |
1203 | 120 | osystem = item['osystem'] | ||
1204 | 113 | arch = item['architecture'] | 121 | arch = item['architecture'] |
1205 | 114 | subarch = item['subarchitecture'] | 122 | subarch = item['subarchitecture'] |
1206 | 115 | release = item['release'] | 123 | release = item['release'] |
1207 | 116 | label = item['label'] | 124 | label = item['label'] |
1209 | 117 | entries.add((arch, subarch, release, label)) | 125 | entries.add((osystem, arch, subarch, release, label)) |
1210 | 118 | tgt_entries = [] | 126 | tgt_entries = [] |
1212 | 119 | for arch, subarch, release, label in sorted(entries): | 127 | for osystem, arch, subarch, release, label in sorted(entries): |
1213 | 120 | root_image = os.path.join( | 128 | root_image = os.path.join( |
1215 | 121 | snapshot_path, arch, subarch, release, label, 'root-image') | 129 | snapshot_path, osystem, arch, subarch, |
1216 | 130 | release, label, 'root-image') | ||
1217 | 122 | if os.path.isfile(root_image): | 131 | if os.path.isfile(root_image): |
1219 | 123 | entry = tgt_entry(arch, subarch, release, label, root_image) | 132 | entry = tgt_entry( |
1220 | 133 | osystem, arch, subarch, release, label, root_image) | ||
1221 | 124 | tgt_entries.append(entry) | 134 | tgt_entries.append(entry) |
1222 | 125 | text = ''.join(tgt_entries) | 135 | text = ''.join(tgt_entries) |
1223 | 126 | return text.encode('utf-8') | 136 | return text.encode('utf-8') |
1224 | 127 | 137 | ||
1225 | === modified file 'src/provisioningserver/import_images/download_resources.py' | |||
1226 | --- src/provisioningserver/import_images/download_resources.py 2014-04-28 09:16:32 +0000 | |||
1227 | +++ src/provisioningserver/import_images/download_resources.py 2014-05-02 19:22:04 +0000 | |||
1228 | @@ -251,6 +251,7 @@ | |||
1229 | 251 | """ | 251 | """ |
1230 | 252 | storage_path = os.path.abspath(storage_path) | 252 | storage_path = os.path.abspath(storage_path) |
1231 | 253 | snapshot_path = compose_snapshot_path(storage_path) | 253 | snapshot_path = compose_snapshot_path(storage_path) |
1232 | 254 | ubuntu_path = os.path.join(snapshot_path, 'ubuntu') | ||
1233 | 254 | # Use a FileStore as our ObjectStore implementation. It will write to the | 255 | # Use a FileStore as our ObjectStore implementation. It will write to the |
1234 | 255 | # cache directory. | 256 | # cache directory. |
1235 | 256 | cache_path = os.path.join(storage_path, 'cache') | 257 | cache_path = os.path.join(storage_path, 'cache') |
1236 | @@ -260,7 +261,7 @@ | |||
1237 | 260 | 261 | ||
1238 | 261 | for source in sources: | 262 | for source in sources: |
1239 | 262 | download_boot_resources( | 263 | download_boot_resources( |
1241 | 263 | source['path'], store, snapshot_path, product_mapping, | 264 | source['path'], store, ubuntu_path, product_mapping, |
1242 | 264 | keyring=source['keyring']) | 265 | keyring=source['keyring']) |
1243 | 265 | 266 | ||
1244 | 266 | return snapshot_path | 267 | return snapshot_path |
1245 | 267 | 268 | ||
1246 | === modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py' | |||
1247 | --- src/provisioningserver/import_images/tests/test_boot_resources.py 2014-04-25 10:42:10 +0000 | |||
1248 | +++ src/provisioningserver/import_images/tests/test_boot_resources.py 2014-05-02 19:22:04 +0000 | |||
1249 | @@ -51,9 +51,10 @@ | |||
1250 | 51 | 51 | ||
1251 | 52 | def test_generates_one_target(self): | 52 | def test_generates_one_target(self): |
1252 | 53 | spec = make_image_spec() | 53 | spec = make_image_spec() |
1253 | 54 | osystem = factory.make_name('osystem') | ||
1254 | 54 | image = self.make_file() | 55 | image = self.make_file() |
1255 | 55 | entry = boot_resources.tgt_entry( | 56 | entry = boot_resources.tgt_entry( |
1257 | 56 | spec.arch, spec.subarch, spec.release, spec.label, image) | 57 | osystem, spec.arch, spec.subarch, spec.release, spec.label, image) |
1258 | 57 | # The entry looks a bit like XML, but isn't well-formed. So don't try | 58 | # The entry looks a bit like XML, but isn't well-formed. So don't try |
1259 | 58 | # to parse it as such! | 59 | # to parse it as such! |
1260 | 59 | self.assertIn('<target iqn.2004-05.com.ubuntu:maas:', entry) | 60 | self.assertIn('<target iqn.2004-05.com.ubuntu:maas:', entry) |
1261 | @@ -63,8 +64,9 @@ | |||
1262 | 63 | def test_produces_suitable_output_for_tgt_admin(self): | 64 | def test_produces_suitable_output_for_tgt_admin(self): |
1263 | 64 | spec = make_image_spec() | 65 | spec = make_image_spec() |
1264 | 65 | image = self.make_file() | 66 | image = self.make_file() |
1265 | 67 | osystem = factory.make_name('osystem') | ||
1266 | 66 | entry = boot_resources.tgt_entry( | 68 | entry = boot_resources.tgt_entry( |
1268 | 67 | spec.arch, spec.subarch, spec.release, spec.label, image) | 69 | osystem, spec.arch, spec.subarch, spec.release, spec.label, image) |
1269 | 68 | config = self.make_file(contents=entry) | 70 | config = self.make_file(contents=entry) |
1270 | 69 | # Pretend to be root, but without requiring the actual privileges and | 71 | # Pretend to be root, but without requiring the actual privileges and |
1271 | 70 | # without prompting for a password. In that state, run tgt-admin. | 72 | # without prompting for a password. In that state, run tgt-admin. |
1272 | @@ -276,7 +278,7 @@ | |||
1273 | 276 | self.assertThat(os.path.join(current, 'maas.meta'), FileExists()) | 278 | self.assertThat(os.path.join(current, 'maas.meta'), FileExists()) |
1274 | 277 | self.assertThat(os.path.join(current, 'maas.tgt'), FileExists()) | 279 | self.assertThat(os.path.join(current, 'maas.tgt'), FileExists()) |
1275 | 278 | self.assertThat( | 280 | self.assertThat( |
1277 | 279 | os.path.join(current, arch, subarch, release, label), | 281 | os.path.join(current, 'ubuntu', arch, subarch, release, label), |
1278 | 280 | DirExists()) | 282 | DirExists()) |
1279 | 281 | 283 | ||
1280 | 282 | # Verify the contents of the "meta" file. | 284 | # Verify the contents of the "meta" file. |
1281 | 283 | 285 | ||
1282 | === modified file 'src/provisioningserver/kernel_opts.py' | |||
1283 | --- src/provisioningserver/kernel_opts.py 2014-03-20 10:44:56 +0000 | |||
1284 | +++ src/provisioningserver/kernel_opts.py 2014-05-02 19:22:04 +0000 | |||
1285 | @@ -30,9 +30,10 @@ | |||
1286 | 30 | 30 | ||
1287 | 31 | KernelParametersBase = namedtuple( | 31 | KernelParametersBase = namedtuple( |
1288 | 32 | "KernelParametersBase", ( | 32 | "KernelParametersBase", ( |
1289 | 33 | "osystem", # Operating system, e.g. "ubuntu" | ||
1290 | 33 | "arch", # Machine architecture, e.g. "i386" | 34 | "arch", # Machine architecture, e.g. "i386" |
1291 | 34 | "subarch", # Machine subarchitecture, e.g. "generic" | 35 | "subarch", # Machine subarchitecture, e.g. "generic" |
1293 | 35 | "release", # Ubuntu release, e.g. "precise" | 36 | "release", # OS release, e.g. "precise" |
1294 | 36 | "label", # Image label, e.g. "release" | 37 | "label", # Image label, e.g. "release" |
1295 | 37 | "purpose", # Boot purpose, e.g. "commissioning" | 38 | "purpose", # Boot purpose, e.g. "commissioning" |
1296 | 38 | "hostname", # Machine hostname, e.g. "coleman" | 39 | "hostname", # Machine hostname, e.g. "coleman" |
1297 | @@ -90,9 +91,15 @@ | |||
1298 | 90 | ISCSI_TARGET_NAME_PREFIX = "iqn.2004-05.com.ubuntu:maas" | 91 | ISCSI_TARGET_NAME_PREFIX = "iqn.2004-05.com.ubuntu:maas" |
1299 | 91 | 92 | ||
1300 | 92 | 93 | ||
1302 | 93 | def get_ephemeral_name(arch, subarch, release, label): | 94 | def get_ephemeral_name(osystem, arch, subarch, release, label): |
1303 | 94 | """Return the name of the most recent ephemeral image.""" | 95 | """Return the name of the most recent ephemeral image.""" |
1305 | 95 | return "ephemeral-%s-%s-%s-%s" % (arch, subarch, release, label) | 96 | return "ephemeral-%s-%s-%s-%s-%s" % ( |
1306 | 97 | osystem, | ||
1307 | 98 | arch, | ||
1308 | 99 | subarch, | ||
1309 | 100 | release, | ||
1310 | 101 | label | ||
1311 | 102 | ) | ||
1312 | 96 | 103 | ||
1313 | 97 | 104 | ||
1314 | 98 | def compose_hostname_opts(params): | 105 | def compose_hostname_opts(params): |
1315 | @@ -119,7 +126,8 @@ | |||
1316 | 119 | # These are kernel parameters read by the ephemeral environment. | 126 | # These are kernel parameters read by the ephemeral environment. |
1317 | 120 | tname = prefix_target_name( | 127 | tname = prefix_target_name( |
1318 | 121 | get_ephemeral_name( | 128 | get_ephemeral_name( |
1320 | 122 | params.arch, params.subarch, params.release, params.label)) | 129 | params.osystem, params.arch, params.subarch, |
1321 | 130 | params.release, params.label)) | ||
1322 | 123 | kernel_params = [ | 131 | kernel_params = [ |
1323 | 124 | # Read by the open-iscsi initramfs code. | 132 | # Read by the open-iscsi initramfs code. |
1324 | 125 | "iscsi_target_name=%s" % tname, | 133 | "iscsi_target_name=%s" % tname, |
1325 | 126 | 134 | ||
1326 | === modified file 'src/provisioningserver/rpc/cluster.py' | |||
1327 | --- src/provisioningserver/rpc/cluster.py 2014-03-20 22:36:32 +0000 | |||
1328 | +++ src/provisioningserver/rpc/cluster.py 2014-05-02 19:22:04 +0000 | |||
1329 | @@ -33,7 +33,8 @@ | |||
1330 | 33 | arguments = [] | 33 | arguments = [] |
1331 | 34 | response = [ | 34 | response = [ |
1332 | 35 | (b"images", amp.AmpList( | 35 | (b"images", amp.AmpList( |
1334 | 36 | [(b"architecture", amp.Unicode()), | 36 | [(b"osystem", amp.Unicode()), |
1335 | 37 | (b"architecture", amp.Unicode()), | ||
1336 | 37 | (b"subarchitecture", amp.Unicode()), | 38 | (b"subarchitecture", amp.Unicode()), |
1337 | 38 | (b"release", amp.Unicode()), | 39 | (b"release", amp.Unicode()), |
1338 | 39 | (b"label", amp.Unicode()), | 40 | (b"label", amp.Unicode()), |
1339 | 40 | 41 | ||
1340 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' | |||
1341 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2014-03-28 04:31:32 +0000 | |||
1342 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2014-05-02 19:22:04 +0000 | |||
1343 | @@ -171,6 +171,7 @@ | |||
1344 | 171 | # serialised correctly. | 171 | # serialised correctly. |
1345 | 172 | 172 | ||
1346 | 173 | # Example boot image definitions. | 173 | # Example boot image definitions. |
1347 | 174 | osystems = "ubuntu", "centos" | ||
1348 | 174 | archs = "i386", "amd64" | 175 | archs = "i386", "amd64" |
1349 | 175 | subarchs = "generic", "special" | 176 | subarchs = "generic", "special" |
1350 | 176 | releases = "precise", "trusty" | 177 | releases = "precise", "trusty" |
1351 | @@ -179,7 +180,7 @@ | |||
1352 | 179 | 180 | ||
1353 | 180 | # Create a TFTP file tree with a variety of subdirectories. | 181 | # Create a TFTP file tree with a variety of subdirectories. |
1354 | 181 | tftpdir = self.make_dir() | 182 | tftpdir = self.make_dir() |
1356 | 182 | for options in product(archs, subarchs, releases, labels): | 183 | for options in product(osystems, archs, subarchs, releases, labels): |
1357 | 183 | os.makedirs(os.path.join(tftpdir, *options)) | 184 | os.makedirs(os.path.join(tftpdir, *options)) |
1358 | 184 | 185 | ||
1359 | 185 | # Ensure that list_boot_images() uses the above TFTP file tree. | 186 | # Ensure that list_boot_images() uses the above TFTP file tree. |
1360 | @@ -187,14 +188,15 @@ | |||
1361 | 187 | 188 | ||
1362 | 188 | expected_images = [ | 189 | expected_images = [ |
1363 | 189 | { | 190 | { |
1364 | 191 | "osystem": osystem, | ||
1365 | 190 | "architecture": arch, | 192 | "architecture": arch, |
1366 | 191 | "subarchitecture": subarch, | 193 | "subarchitecture": subarch, |
1367 | 192 | "release": release, | 194 | "release": release, |
1368 | 193 | "label": label, | 195 | "label": label, |
1369 | 194 | "purpose": purpose, | 196 | "purpose": purpose, |
1370 | 195 | } | 197 | } |
1373 | 196 | for arch, subarch, release, label, purpose in product( | 198 | for osystem, arch, subarch, release, label, purpose in product( |
1374 | 197 | archs, subarchs, releases, labels, purposes) | 199 | osystems, archs, subarchs, releases, labels, purposes) |
1375 | 198 | ] | 200 | ] |
1376 | 199 | 201 | ||
1377 | 200 | response = yield call_responder(Cluster(), cluster.ListBootImages, {}) | 202 | response = yield call_responder(Cluster(), cluster.ListBootImages, {}) |
1378 | 201 | 203 | ||
1379 | === modified file 'src/provisioningserver/testing/boot_images.py' | |||
1380 | --- src/provisioningserver/testing/boot_images.py 2014-03-21 03:21:57 +0000 | |||
1381 | +++ src/provisioningserver/testing/boot_images.py 2014-05-02 19:22:04 +0000 | |||
1382 | @@ -23,10 +23,11 @@ | |||
1383 | 23 | """Create an arbitrary dict of boot-image parameters. | 23 | """Create an arbitrary dict of boot-image parameters. |
1384 | 24 | 24 | ||
1385 | 25 | These are the parameters that together describe a kind of boot for | 25 | These are the parameters that together describe a kind of boot for |
1387 | 26 | which we may need a kernel and initrd: architecture, | 26 | which we may need a kernel and initrd: operating system, architecture, |
1388 | 27 | sub-architecture, Ubuntu release, boot purpose, and release label. | 27 | sub-architecture, Ubuntu release, boot purpose, and release label. |
1389 | 28 | """ | 28 | """ |
1390 | 29 | return dict( | 29 | return dict( |
1391 | 30 | osystem=factory.make_name('osystem'), | ||
1392 | 30 | architecture=factory.make_name('architecture'), | 31 | architecture=factory.make_name('architecture'), |
1393 | 31 | subarchitecture=factory.make_name('subarchitecture'), | 32 | subarchitecture=factory.make_name('subarchitecture'), |
1394 | 32 | release=factory.make_name('release'), | 33 | release=factory.make_name('release'), |
1395 | @@ -39,9 +40,11 @@ | |||
1396 | 39 | """Create a dict of boot-image parameters as used to store the image. | 40 | """Create a dict of boot-image parameters as used to store the image. |
1397 | 40 | 41 | ||
1398 | 41 | These are the parameters that together describe a path to store a boot | 42 | These are the parameters that together describe a path to store a boot |
1400 | 42 | image: architecture, sub-architecture, Ubuntu release, and release label. | 43 | image: operating system, architecture, sub-architecture, Ubuntu release, |
1401 | 44 | and release label. | ||
1402 | 43 | """ | 45 | """ |
1403 | 44 | return dict( | 46 | return dict( |
1404 | 47 | osystem=factory.make_name('osystem'), | ||
1405 | 45 | architecture=factory.make_name('architecture'), | 48 | architecture=factory.make_name('architecture'), |
1406 | 46 | subarchitecture=factory.make_name('subarchitecture'), | 49 | subarchitecture=factory.make_name('subarchitecture'), |
1407 | 47 | release=factory.make_name('release'), | 50 | release=factory.make_name('release'), |
1408 | 48 | 51 | ||
1409 | === modified file 'src/provisioningserver/tests/test_kernel_opts.py' | |||
1410 | --- src/provisioningserver/tests/test_kernel_opts.py 2014-03-28 16:46:55 +0000 | |||
1411 | +++ src/provisioningserver/tests/test_kernel_opts.py 2014-05-02 19:22:04 +0000 | |||
1412 | @@ -228,7 +228,8 @@ | |||
1413 | 228 | # options for a "xinstall" node. | 228 | # options for a "xinstall" node. |
1414 | 229 | params = self.make_kernel_parameters(purpose="xinstall") | 229 | params = self.make_kernel_parameters(purpose="xinstall") |
1415 | 230 | ephemeral_name = get_ephemeral_name( | 230 | ephemeral_name = get_ephemeral_name( |
1417 | 231 | params.arch, params.subarch, params.release, params.label) | 231 | params.osystem, params.arch, params.subarch, |
1418 | 232 | params.release, params.label) | ||
1419 | 232 | self.assertThat( | 233 | self.assertThat( |
1420 | 233 | compose_kernel_command_line(params), | 234 | compose_kernel_command_line(params), |
1421 | 234 | ContainsAll([ | 235 | ContainsAll([ |
1422 | @@ -243,7 +244,8 @@ | |||
1423 | 243 | # options for a "commissioning" node. | 244 | # options for a "commissioning" node. |
1424 | 244 | params = self.make_kernel_parameters(purpose="commissioning") | 245 | params = self.make_kernel_parameters(purpose="commissioning") |
1425 | 245 | ephemeral_name = get_ephemeral_name( | 246 | ephemeral_name = get_ephemeral_name( |
1427 | 246 | params.arch, params.subarch, params.release, params.label) | 247 | params.osystem, params.arch, params.subarch, |
1428 | 248 | params.release, params.label) | ||
1429 | 247 | self.assertThat( | 249 | self.assertThat( |
1430 | 248 | compose_kernel_command_line(params), | 250 | compose_kernel_command_line(params), |
1431 | 249 | ContainsAll([ | 251 | ContainsAll([ |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
review: approve
Wow, adding something as simple as an OS name was quite a lot of
changes! Thanks for doing this, it's a very nice branch.
I have a couple of niggles but it looks mostly OK to me. Please land
it once these are addressed.
[1]
> + def get_usable_ osystems( self, nodegroup): + """Return objects. filter( nodegroup= nodegroup) + values_ list('osystem' , flat=True)) + + def releases( self, nodegroup, osystem): + """Return objects. filter( nodegroup= nodegroup, osystem=osystem) + list('release' , flat=True) + return
> the list of usable operating systems for a nodegroup. + """
> + query = BootImage.
> return set(query.
> get_usable_
> the list of usable releases for a nodegroup and + operating
> system. + """ + query =
> BootImage.
> releases = query.values_
> set(releases)
Can you add tests for these new functions please. Also they are not
actually used anywhere, I presume a follow up branch makes use of them?
[2]
Please run "make lint" and fix all the errors it finds.
-----BEGIN PGP SIGNATURE----- www.enigmail. net/
YfxYACgkQWhGlTF 8G/HefwwCfWwiom UOBgXKO3Z4mf/ YGEfpL MpfihFXHwJBKOek qD2KGOp
Version: GnuPG v1
Comment: Using GnuPG with Thunderbird - http://
iEYEARECAAYFAlN
VaEAniGAr/
=VxoG
-----END PGP SIGNATURE-----