Merge lp:~blake-rouse/maas/add-osystem-to-bootimage into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
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
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/release/label, the new structure puts those folders inside a folder with the operating system name. So the new structure is osystem/arch/subarch/release/label (e.g. ubuntu/amd64/generic/trusty/release).

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.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

-----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
> the list of usable operating systems for a nodegroup. + """
> + query = BootImage.objects.filter(nodegroup=nodegroup) +
> return set(query.values_list('osystem', flat=True)) + + def
> get_usable_releases(self, nodegroup, osystem): + """Return
> the list of usable releases for a nodegroup and + operating
> system. + """ + query =
> BootImage.objects.filter(nodegroup=nodegroup, osystem=osystem) +
> releases = query.values_list('release', flat=True) + return
> 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://www.enigmail.net/

iEYEARECAAYFAlNYfxYACgkQWhGlTF8G/HefwwCfWwiomUOBgXKO3Z4mf/YGEfpL
VaEAniGAr/MpfihFXHwJBKOekqD2KGOp
=VxoG
-----END PGP SIGNATURE-----

review: Approve
Revision history for this message
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_osystems(self, nodegroup): + """Return
> > the list of usable operating systems for a nodegroup. + """
> > + query = BootImage.objects.filter(nodegroup=nodegroup) +
> > return set(query.values_list('osystem', flat=True)) + + def
> > get_usable_releases(self, nodegroup, osystem): + """Return
> > the list of usable releases for a nodegroup and + operating
> > system. + """ + query =
> > BootImage.objects.filter(nodegroup=nodegroup, osystem=osystem) +
> > releases = query.values_list('release', flat=True) + return
> > 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://www.enigmail.net/
>
> iEYEARECAAYFAlNYfxYACgkQWhGlTF8G/HefwwCfWwiomUOBgXKO3Z4mf/YGEfpL
> VaEAniGAr/MpfihFXHwJBKOekqD2KGOp
> =VxoG
> -----END PGP SIGNATURE-----
>
> --
>
> https://code.launchpad.net/~blake-rouse/maas/add-osystem-to-bootimage/+merge/216923
> You are subscribed to branch lp:maas.
>

Revision history for this message
Blake Rouse (blake-rouse) wrote :

I added test for get_usable_osystems and get_useable_releases, these methods get used in the following branches.

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.

Revision history for this message
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?

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2014-04-28 07:41:51 +0000
+++ src/maasserver/api.py 2014-05-02 19:22:04 +0000
@@ -2440,7 +2440,7 @@
2440 # current series. If nothing is found, fall back to i386 like2440 # current series. If nothing is found, fall back to i386 like
2441 # we used to. LP #11813342441 # we used to. LP #1181334
2442 image = BootImage.objects.get_default_arch_image_in_nodegroup(2442 image = BootImage.objects.get_default_arch_image_in_nodegroup(
2443 nodegroup, series, purpose=purpose)2443 nodegroup, 'ubuntu', series, purpose=purpose)
2444 if image is None:2444 if image is None:
2445 arch = 'i386'2445 arch = 'i386'
2446 else:2446 else:
@@ -2453,7 +2453,7 @@
2453 # (which should never happen in reality but may happen in tests), we2453 # (which should never happen in reality but may happen in tests), we
2454 # fall back to using 'no-such-image' as our default.2454 # fall back to using 'no-such-image' as our default.
2455 latest_image = BootImage.objects.get_latest_image(2455 latest_image = BootImage.objects.get_latest_image(
2456 nodegroup, arch, subarch, series, purpose)2456 nodegroup, 'ubuntu', arch, subarch, series, purpose)
2457 if latest_image is None:2457 if latest_image is None:
2458 # XXX 2014-03-18 gmb bug=1294131:2458 # XXX 2014-03-18 gmb bug=1294131:
2459 # We really ought to raise an exception here so that client2459 # We really ought to raise an exception here so that client
@@ -2494,8 +2494,8 @@
2494 cluster_address = get_mandatory_param(request.GET, "local")2494 cluster_address = get_mandatory_param(request.GET, "local")
24952495
2496 params = KernelParameters(2496 params = KernelParameters(
2497 arch=arch, subarch=subarch, release=series, label=label,2497 osystem='ubuntu', arch=arch, subarch=subarch, release=series,
2498 purpose=purpose, hostname=hostname, domain=domain,2498 label=label, purpose=purpose, hostname=hostname, domain=domain,
2499 preseed_url=preseed_url, log_host=server_address,2499 preseed_url=preseed_url, log_host=server_address,
2500 fs_host=cluster_address, extra_opts=extra_kernel_opts)2500 fs_host=cluster_address, extra_opts=extra_kernel_opts)
25012501
@@ -2536,10 +2536,11 @@
2536 This function has a counterpart, `summarise_boot_image_dict`. The two2536 This function has a counterpart, `summarise_boot_image_dict`. The two
2537 return the same value for the same boot image.2537 return the same value for the same boot image.
25382538
2539 :return: A tuple of the image's architecture, subarchitecture, release,2539 :return: A tuple of the image's osystem, architecture, subarchitecture,
2540 label, and purpose.2540 release, label, and purpose.
2541 """2541 """
2542 return (2542 return (
2543 image_object.osystem,
2543 image_object.architecture,2544 image_object.architecture,
2544 image_object.subarchitecture,2545 image_object.subarchitecture,
2545 image_object.release,2546 image_object.release,
@@ -2554,10 +2555,11 @@
2554 This is the counterpart to `summarise_boot_image_object`. The two return2555 This is the counterpart to `summarise_boot_image_object`. The two return
2555 the same value for the same boot image.2556 the same value for the same boot image.
25562557
2557 :return: A tuple of the image's architecture, subarchitecture, release,2558 :return: A tuple of the image's osystem, architecture, subarchitecture,
2558 label, and purpose.2559 release, label, and purpose.
2559 """2560 """
2560 return (2561 return (
2562 image_dict['osystem'],
2561 image_dict['architecture'],2563 image_dict['architecture'],
2562 image_dict.get('subarchitecture', 'generic'),2564 image_dict.get('subarchitecture', 'generic'),
2563 image_dict['release'],2565 image_dict['release'],
@@ -2600,10 +2602,11 @@
2600 `summarise_stored_images`.2602 `summarise_stored_images`.
2601 """2603 """
2602 new_images = reported_images - stored_images2604 new_images = reported_images - stored_images
2603 for arch, subarch, release, label, purpose in new_images:2605 for osystem, arch, subarch, release, label, purpose in new_images:
2604 BootImage.objects.register_image(2606 BootImage.objects.register_image(
2605 nodegroup=nodegroup, architecture=arch, subarchitecture=subarch,2607 nodegroup=nodegroup, osystem=osystem, architecture=arch,
2606 release=release, purpose=purpose, label=label)2608 subarchitecture=subarch, release=release, purpose=purpose,
2609 label=label)
26072610
26082611
2609def prune_boot_images(nodegroup, reported_images, stored_images):2612def prune_boot_images(nodegroup, reported_images, stored_images):
@@ -2620,15 +2623,16 @@
2620 `summarise_stored_images`.2623 `summarise_stored_images`.
2621 """2624 """
2622 removed_images = stored_images - reported_images2625 removed_images = stored_images - reported_images
2623 for arch, subarch, release, label, purpose in removed_images:2626 for osystem, arch, subarch, release, label, purpose in removed_images:
2624 db_images = BootImage.objects.filter(2627 db_images = BootImage.objects.filter(
2625 architecture=arch, subarchitecture=subarch,2628 osystem=osystem, architecture=arch, subarchitecture=subarch,
2626 release=release, label=label, purpose=purpose)2629 release=release, label=label, purpose=purpose)
2627 db_images.delete()2630 db_images.delete()
26282631
26292632
2630DISPLAYED_BOOTIMAGE_FIELDS = (2633DISPLAYED_BOOTIMAGE_FIELDS = (
2631 'id',2634 'id',
2635 'osystem',
2632 'release',2636 'release',
2633 'architecture',2637 'architecture',
2634 'subarchitecture',2638 'subarchitecture',
@@ -2694,10 +2698,10 @@
2694 :param uuid: The UUID of the cluster for which the images are2698 :param uuid: The UUID of the cluster for which the images are
2695 being reported.2699 being reported.
2696 :param images: A list of dicts, each describing a boot image with2700 :param images: A list of dicts, each describing a boot image with
2697 these properties: `architecture`, `subarchitecture`, `release`,2701 these properties: `os`, `architecture`, `subarchitecture`,
2698 `purpose`, and optionally, `label` (which defaults to "release").2702 `release`, `purpose`, and optionally, `label` (which defaults
2699 These should match the code that determines TFTP paths for these2703 to "release"). These should match the code that determines TFTP
2700 images.2704 paths for these images.
2701 """2705 """
2702 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)2706 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
2703 check_nodegroup_access(request, nodegroup)2707 check_nodegroup_access(request, nodegroup)
27042708
=== added file 'src/maasserver/migrations/0076_add_osystem_to_bootimage.py'
--- src/maasserver/migrations/0076_add_osystem_to_bootimage.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0076_add_osystem_to_bootimage.py 2014-05-02 19:22:04 +0000
@@ -0,0 +1,275 @@
1# -*- coding: utf-8 -*-
2from south.utils import datetime_utils as datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Removing unique constraint on 'BootImage', fields ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup', 'purpose']
12 db.delete_unique(u'maasserver_bootimage', ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose'])
13
14 # Adding field 'BootImage.osystem'
15 db.add_column(u'maasserver_bootimage', 'osystem',
16 self.gf('django.db.models.fields.CharField')(default='ubuntu', max_length=255),
17 keep_default=False)
18
19 # Adding unique constraint on 'BootImage', fields ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup', 'purpose']
20 db.create_unique(u'maasserver_bootimage', ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose'])
21
22
23 def backwards(self, orm):
24 # Removing unique constraint on 'BootImage', fields ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup', 'purpose']
25 db.delete_unique(u'maasserver_bootimage', ['subarchitecture', 'osystem', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose'])
26
27 # Deleting field 'BootImage.osystem'
28 db.delete_column(u'maasserver_bootimage', 'osystem')
29
30 # Adding unique constraint on 'BootImage', fields ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup', 'purpose']
31 db.create_unique(u'maasserver_bootimage', ['subarchitecture', 'label', 'architecture', 'release', 'nodegroup_id', 'purpose'])
32
33
34 models = {
35 u'auth.group': {
36 'Meta': {'object_name': 'Group'},
37 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
39 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
40 },
41 u'auth.permission': {
42 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
43 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
44 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
45 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
47 },
48 u'auth.user': {
49 'Meta': {'object_name': 'User'},
50 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
52 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
53 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
54 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
56 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
57 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
58 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
59 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
60 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
61 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
62 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
63 },
64 u'contenttypes.contenttype': {
65 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
66 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
67 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
68 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
69 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
70 },
71 u'maasserver.bootimage': {
72 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
73 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
74 'created': ('django.db.models.fields.DateTimeField', [], {}),
75 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
77 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
78 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
79 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
80 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
81 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
82 'updated': ('django.db.models.fields.DateTimeField', [], {})
83 },
84 u'maasserver.bootsource': {
85 'Meta': {'object_name': 'BootSource'},
86 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
87 'created': ('django.db.models.fields.DateTimeField', [], {}),
88 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
89 'keyring_data': ('django.db.models.fields.BinaryField', [], {'blank': 'True'}),
90 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
91 'updated': ('django.db.models.fields.DateTimeField', [], {}),
92 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
93 },
94 u'maasserver.bootsourceselection': {
95 'Meta': {'object_name': 'BootSourceSelection'},
96 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
97 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
98 'created': ('django.db.models.fields.DateTimeField', [], {}),
99 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
101 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
102 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
103 'updated': ('django.db.models.fields.DateTimeField', [], {})
104 },
105 u'maasserver.componenterror': {
106 'Meta': {'object_name': 'ComponentError'},
107 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
108 'created': ('django.db.models.fields.DateTimeField', [], {}),
109 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
110 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
111 'updated': ('django.db.models.fields.DateTimeField', [], {})
112 },
113 u'maasserver.config': {
114 'Meta': {'object_name': 'Config'},
115 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
116 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
117 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
118 },
119 u'maasserver.dhcplease': {
120 'Meta': {'object_name': 'DHCPLease'},
121 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
123 'mac': ('maasserver.fields.MACAddressField', [], {}),
124 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
125 },
126 u'maasserver.downloadprogress': {
127 'Meta': {'object_name': 'DownloadProgress'},
128 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
129 'created': ('django.db.models.fields.DateTimeField', [], {}),
130 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
131 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
132 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
134 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
135 'updated': ('django.db.models.fields.DateTimeField', [], {})
136 },
137 u'maasserver.filestorage': {
138 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
139 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
140 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
141 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
142 'key': ('django.db.models.fields.CharField', [], {'default': "u'26215e0a-cafa-11e3-8554-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}),
143 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
144 },
145 u'maasserver.macaddress': {
146 'Meta': {'object_name': 'MACAddress'},
147 'created': ('django.db.models.fields.DateTimeField', [], {}),
148 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
149 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
150 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
151 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
152 'updated': ('django.db.models.fields.DateTimeField', [], {})
153 },
154 u'maasserver.network': {
155 'Meta': {'object_name': 'Network'},
156 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
157 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
158 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
159 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
160 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
161 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
162 },
163 u'maasserver.node': {
164 'Meta': {'object_name': 'Node'},
165 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
166 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
167 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
168 'created': ('django.db.models.fields.DateTimeField', [], {}),
169 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
170 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
171 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
172 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
173 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
174 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
175 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
176 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
177 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
178 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
179 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
180 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
181 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
182 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-26226a84-cafa-11e3-8554-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}),
183 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
184 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
185 'updated': ('django.db.models.fields.DateTimeField', [], {}),
186 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
187 },
188 u'maasserver.nodegroup': {
189 'Meta': {'object_name': 'NodeGroup'},
190 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
191 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
192 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
193 'created': ('django.db.models.fields.DateTimeField', [], {}),
194 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
195 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
196 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
197 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
198 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
199 'updated': ('django.db.models.fields.DateTimeField', [], {}),
200 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
201 },
202 u'maasserver.nodegroupinterface': {
203 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
204 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
205 'created': ('django.db.models.fields.DateTimeField', [], {}),
206 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
207 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
209 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
210 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
211 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
212 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
213 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
214 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
215 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
216 'updated': ('django.db.models.fields.DateTimeField', [], {})
217 },
218 u'maasserver.sshkey': {
219 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
220 'created': ('django.db.models.fields.DateTimeField', [], {}),
221 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
222 'key': ('django.db.models.fields.TextField', [], {}),
223 'updated': ('django.db.models.fields.DateTimeField', [], {}),
224 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
225 },
226 u'maasserver.tag': {
227 'Meta': {'object_name': 'Tag'},
228 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
229 'created': ('django.db.models.fields.DateTimeField', [], {}),
230 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
231 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
232 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
233 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
234 'updated': ('django.db.models.fields.DateTimeField', [], {})
235 },
236 u'maasserver.userprofile': {
237 'Meta': {'object_name': 'UserProfile'},
238 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
239 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
240 },
241 u'maasserver.zone': {
242 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
243 'created': ('django.db.models.fields.DateTimeField', [], {}),
244 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
245 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
246 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
247 'updated': ('django.db.models.fields.DateTimeField', [], {})
248 },
249 u'piston.consumer': {
250 'Meta': {'object_name': 'Consumer'},
251 'description': ('django.db.models.fields.TextField', [], {}),
252 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
253 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
254 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
255 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
256 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
257 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
258 },
259 u'piston.token': {
260 'Meta': {'object_name': 'Token'},
261 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
262 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
263 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
264 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
265 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
266 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
267 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
268 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1398266138L'}),
269 'token_type': ('django.db.models.fields.IntegerField', [], {}),
270 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
271 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
272 }
273 }
274
275 complete_apps = ['maasserver']
0\ No newline at end of file276\ No newline at end of file
1277
=== modified file 'src/maasserver/models/bootimage.py'
--- src/maasserver/models/bootimage.py 2014-03-26 16:01:34 +0000
+++ src/maasserver/models/bootimage.py 2014-05-02 19:22:04 +0000
@@ -34,44 +34,47 @@
34 Don't import or instantiate this directly; access as `BootImage.objects`.34 Don't import or instantiate this directly; access as `BootImage.objects`.
35 """35 """
3636
37 def get_by_natural_key(self, nodegroup, architecture, subarchitecture,37 def get_by_natural_key(self, nodegroup, osystem, architecture,
38 release, purpose, label):38 subarchitecture, release, purpose, label):
39 """Look up a specific image."""39 """Look up a specific image."""
40 return self.get(40 return self.get(
41 nodegroup=nodegroup, architecture=architecture,41 nodegroup=nodegroup, osystem=osystem, architecture=architecture,
42 subarchitecture=subarchitecture, release=release,42 subarchitecture=subarchitecture, release=release,
43 purpose=purpose, label=label)43 purpose=purpose, label=label)
4444
45 def register_image(self, nodegroup, architecture, subarchitecture,45 def register_image(self, nodegroup, osystem, architecture, subarchitecture,
46 release, purpose, label):46 release, purpose, label):
47 """Register an image if it wasn't already registered."""47 """Register an image if it wasn't already registered."""
48 self.get_or_create(48 self.get_or_create(
49 nodegroup=nodegroup, architecture=architecture,49 nodegroup=nodegroup, osystem=osystem, architecture=architecture,
50 subarchitecture=subarchitecture, release=release,50 subarchitecture=subarchitecture, release=release,
51 purpose=purpose, label=label)51 purpose=purpose, label=label)
5252
53 def have_image(self, nodegroup, architecture, subarchitecture, release,53 def have_image(self, nodegroup, osystem, architecture, subarchitecture,
54 purpose, label=None):54 release, purpose, label=None):
55 """Is an image for the given kind of boot available?"""55 """Is an image for the given kind of boot available?"""
56 if label is None:56 if label is None:
57 label = "release"57 label = "release"
58 try:58 try:
59 self.get_by_natural_key(59 self.get_by_natural_key(
60 nodegroup=nodegroup, architecture=architecture,60 nodegroup=nodegroup, osystem=osystem,
61 subarchitecture=subarchitecture, release=release,61 architecture=architecture, subarchitecture=subarchitecture,
62 purpose=purpose, label=label)62 release=release, purpose=purpose, label=label)
63 return True63 return True
64 except BootImage.DoesNotExist:64 except BootImage.DoesNotExist:
65 return False65 return False
6666
67 def get_default_arch_image_in_nodegroup(self, nodegroup, series, purpose):67 def get_default_arch_image_in_nodegroup(self, nodegroup, osystem, series,
68 """Return any image for the given nodegroup, series, and purpose.68 purpose):
69 """Return any image for the given nodegroup, osystem, series,
70 and purpose.
6971
70 Prefers `i386` images if available. Returns `None` if no images match72 Prefers `i386` images if available. Returns `None` if no images match
71 requirements.73 requirements.
72 """74 """
73 images = BootImage.objects.filter(75 images = BootImage.objects.filter(
74 release=series, nodegroup=nodegroup, purpose=purpose)76 osystem=osystem, release=series, nodegroup=nodegroup,
77 purpose=purpose)
75 for image in images:78 for image in images:
76 # Prefer i386, any available subarchitecture (usually just79 # Prefer i386, any available subarchitecture (usually just
77 # "generic"). It will work for most cases where we don't know80 # "generic"). It will work for most cases where we don't know
@@ -106,14 +109,28 @@
106 nodegroup, 'install')109 nodegroup, 'install')
107 return arches_commissioning & arches_install110 return arches_commissioning & arches_install
108111
109 def get_latest_image(self, nodegroup, architecture, subarchitecture,112 def get_latest_image(self, nodegroup, osystem, architecture,
110 release, purpose):113 subarchitecture, release, purpose):
111 """Return the latest image for a set of criteria."""114 """Return the latest image for a set of criteria."""
112 return BootImage.objects.filter(115 return BootImage.objects.filter(
113 nodegroup=nodegroup, architecture=architecture,116 nodegroup=nodegroup, osystem=osystem, architecture=architecture,
114 subarchitecture=subarchitecture, release=release,117 subarchitecture=subarchitecture, release=release,
115 purpose=purpose).order_by('id').last()118 purpose=purpose).order_by('id').last()
116119
120 def get_usable_osystems(self, nodegroup):
121 """Return the list of usable operating systems for a nodegroup.
122 """
123 query = BootImage.objects.filter(nodegroup=nodegroup)
124 return set(query.values_list('osystem', flat=True))
125
126 def get_usable_releases(self, nodegroup, osystem):
127 """Return the list of usable releases for a nodegroup and
128 operating system.
129 """
130 query = BootImage.objects.filter(nodegroup=nodegroup, osystem=osystem)
131 releases = query.values_list('release', flat=True)
132 return set(releases)
133
117134
118class BootImage(TimestampedModel):135class BootImage(TimestampedModel):
119 """Available boot image (i.e. kernel and initrd).136 """Available boot image (i.e. kernel and initrd).
@@ -131,8 +148,8 @@
131148
132 class Meta(DefaultMeta):149 class Meta(DefaultMeta):
133 unique_together = (150 unique_together = (
134 ('nodegroup', 'architecture', 'subarchitecture', 'release',151 ('nodegroup', 'osystem', 'architecture', 'subarchitecture',
135 'purpose', 'label'),152 'release', 'purpose', 'label'),
136 )153 )
137154
138 objects = BootImageManager()155 objects = BootImageManager()
@@ -140,6 +157,9 @@
140 # Nodegroup (cluster controller) that has the images.157 # Nodegroup (cluster controller) that has the images.
141 nodegroup = ForeignKey(NodeGroup, null=False, editable=False, unique=False)158 nodegroup = ForeignKey(NodeGroup, null=False, editable=False, unique=False)
142159
160 # Operating system (e.g. "ubuntu") that the image boots.
161 osystem = CharField(max_length=255, blank=False, editable=False)
162
143 # System architecture (e.g. "i386") that the image is for.163 # System architecture (e.g. "i386") that the image is for.
144 architecture = CharField(max_length=255, blank=False, editable=False)164 architecture = CharField(max_length=255, blank=False, editable=False)
145165
@@ -148,7 +168,7 @@
148 # such as i386 and amd64, we use "generic").168 # such as i386 and amd64, we use "generic").
149 subarchitecture = CharField(max_length=255, blank=False, editable=False)169 subarchitecture = CharField(max_length=255, blank=False, editable=False)
150170
151 # Ubuntu release (e.g. "precise") that the image boots.171 # OS release (e.g. "precise") that the image boots.
152 release = CharField(max_length=255, blank=False, editable=False)172 release = CharField(max_length=255, blank=False, editable=False)
153173
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.
@@ -159,7 +179,8 @@
159 max_length=255, blank=False, editable=False, default="release")179 max_length=255, blank=False, editable=False, default="release")
160180
161 def __repr__(self):181 def __repr__(self):
162 return "<BootImage %s/%s-%s-%s-%s>" % (182 return "<BootImage %s-%s/%s-%s-%s-%s>" % (
183 self.osystem,
163 self.architecture,184 self.architecture,
164 self.subarchitecture,185 self.subarchitecture,
165 self.release,186 self.release,
166187
=== modified file 'src/maasserver/models/tests/test_bootimage.py'
--- src/maasserver/models/tests/test_bootimage.py 2014-03-18 14:39:11 +0000
+++ src/maasserver/models/tests/test_bootimage.py 2014-05-02 19:22:04 +0000
@@ -50,69 +50,79 @@
50 self.assertTrue(BootImage.objects.have_image(nodegroup, **params))50 self.assertTrue(BootImage.objects.have_image(nodegroup, **params))
5151
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):
53 osystem = Config.objects.get_config('commissioning_osystem')
53 series = Config.objects.get_config('commissioning_distro_series')54 series = Config.objects.get_config('commissioning_distro_series')
54 result = BootImage.objects.get_default_arch_image_in_nodegroup(55 result = BootImage.objects.get_default_arch_image_in_nodegroup(
55 factory.make_node_group(), series, factory.make_name('purpose'))56 factory.make_node_group(), osystem, series,
57 factory.make_name('purpose'))
56 self.assertIsNone(result)58 self.assertIsNone(result)
5759
58 def test_default_arch_image_returns_only_matching_image(self):60 def test_default_arch_image_returns_only_matching_image(self):
59 nodegroup = factory.make_node_group()61 nodegroup = factory.make_node_group()
62 osystem = factory.make_name('os')
60 series = factory.make_name('series')63 series = factory.make_name('series')
61 label = factory.make_name('label')64 label = factory.make_name('label')
62 arch = factory.make_name('arch')65 arch = factory.make_name('arch')
63 purpose = factory.make_name("purpose")66 purpose = factory.make_name("purpose")
64 factory.make_boot_image(67 factory.make_boot_image(
65 architecture=arch, release=series, label=label,68 osystem=osystem, architecture=arch,
69 release=series, label=label,
66 nodegroup=nodegroup, purpose=purpose)70 nodegroup=nodegroup, purpose=purpose)
67 result = BootImage.objects.get_default_arch_image_in_nodegroup(71 result = BootImage.objects.get_default_arch_image_in_nodegroup(
68 nodegroup, series, purpose=purpose)72 nodegroup, osystem, series, purpose=purpose)
69 self.assertEqual(result.architecture, arch)73 self.assertEqual(result.architecture, arch)
7074
71 def test_default_arch_image_prefers_i386(self):75 def test_default_arch_image_prefers_i386(self):
72 nodegroup = factory.make_node_group()76 nodegroup = factory.make_node_group()
77 osystem = factory.make_name('os')
73 series = factory.make_name('series')78 series = factory.make_name('series')
74 label = factory.make_name('label')79 label = factory.make_name('label')
75 purpose = factory.make_name("purpose")80 purpose = factory.make_name("purpose")
76 for arch in ['amd64', 'axp', 'i386', 'm88k']:81 for arch in ['amd64', 'axp', 'i386', 'm88k']:
77 factory.make_boot_image(82 factory.make_boot_image(
78 architecture=arch, release=series, nodegroup=nodegroup,83 osystem=osystem, architecture=arch,
84 release=series, nodegroup=nodegroup,
79 purpose=purpose, label=label)85 purpose=purpose, label=label)
80 result = BootImage.objects.get_default_arch_image_in_nodegroup(86 result = BootImage.objects.get_default_arch_image_in_nodegroup(
81 nodegroup, series, purpose=purpose)87 nodegroup, osystem, series, purpose=purpose)
82 self.assertEqual(result.architecture, "i386")88 self.assertEqual(result.architecture, "i386")
8389
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):
85 nodegroup = factory.make_node_group()91 nodegroup = factory.make_node_group()
92 osystem = factory.make_name('os')
86 series = factory.make_name('series')93 series = factory.make_name('series')
87 label = factory.make_name('label')94 label = factory.make_name('label')
88 purpose = factory.make_name("purpose")95 purpose = factory.make_name("purpose")
89 images = [96 images = [
90 factory.make_boot_image(97 factory.make_boot_image(
91 architecture=factory.make_name('arch'), release=series,98 osystem=osystem, architecture=factory.make_name('arch'),
92 label=label, nodegroup=nodegroup, purpose=purpose)99 release=series, label=label, nodegroup=nodegroup,
100 purpose=purpose)
93 for _ in range(3)101 for _ in range(3)
94 ]102 ]
95 self.assertIn(103 self.assertIn(
96 BootImage.objects.get_default_arch_image_in_nodegroup(104 BootImage.objects.get_default_arch_image_in_nodegroup(
97 nodegroup, series, purpose=purpose),105 nodegroup, osystem, series, purpose=purpose),
98 images)106 images)
99107
100 def test_default_arch_image_copes_with_subarches(self):108 def test_default_arch_image_copes_with_subarches(self):
101 nodegroup = factory.make_node_group()109 nodegroup = factory.make_node_group()
102 arch = 'i386'110 arch = 'i386'
111 osystem = factory.make_name('os')
103 series = factory.make_name('series')112 series = factory.make_name('series')
104 label = factory.make_name('label')113 label = factory.make_name('label')
105 purpose = factory.make_name("purpose")114 purpose = factory.make_name("purpose")
106 images = [115 images = [
107 factory.make_boot_image(116 factory.make_boot_image(
108 architecture=arch, subarchitecture=factory.make_name('sub'),117 osystem=osystem, architecture=arch,
118 subarchitecture=factory.make_name('sub'),
109 release=series, label=label, nodegroup=nodegroup,119 release=series, label=label, nodegroup=nodegroup,
110 purpose=purpose)120 purpose=purpose)
111 for _ in range(3)121 for _ in range(3)
112 ]122 ]
113 self.assertIn(123 self.assertIn(
114 BootImage.objects.get_default_arch_image_in_nodegroup(124 BootImage.objects.get_default_arch_image_in_nodegroup(
115 nodegroup, series, purpose=purpose),125 nodegroup, osystem, series, purpose=purpose),
116 images)126 images)
117127
118 def test_get_usable_architectures_returns_supported_arches(self):128 def test_get_usable_architectures_returns_supported_arches(self):
@@ -164,54 +174,62 @@
164 BootImage.objects.get_usable_architectures(nodegroup))174 BootImage.objects.get_usable_architectures(nodegroup))
165175
166 def test_get_latest_image_returns_latest_image_for_criteria(self):176 def test_get_latest_image_returns_latest_image_for_criteria(self):
177 osystem = factory.make_name('os')
167 arch = factory.make_name('arch')178 arch = factory.make_name('arch')
168 subarch = factory.make_name('sub')179 subarch = factory.make_name('sub')
169 release = factory.make_name('release')180 release = factory.make_name('release')
170 nodegroup = factory.make_node_group()181 nodegroup = factory.make_node_group()
171 purpose = factory.make_name("purpose")182 purpose = factory.make_name("purpose")
172 boot_image = factory.make_boot_image(183 boot_image = factory.make_boot_image(
173 nodegroup=nodegroup, architecture=arch,184 nodegroup=nodegroup, osystem=osystem, architecture=arch,
174 subarchitecture=subarch, release=release, purpose=purpose,185 subarchitecture=subarch, release=release, purpose=purpose,
175 label=factory.make_name('label'))186 label=factory.make_name('label'))
176 self.assertEqual(187 self.assertEqual(
177 boot_image,188 boot_image,
178 BootImage.objects.get_latest_image(189 BootImage.objects.get_latest_image(
179 nodegroup, arch, subarch, release, purpose))190 nodegroup, osystem, arch, subarch, release, purpose))
180191
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):
193 osystem = factory.make_name('os')
182 arch = factory.make_name('arch')194 arch = factory.make_name('arch')
183 subarch = factory.make_name('sub')195 subarch = factory.make_name('sub')
184 release = factory.make_name('release')196 release = factory.make_name('release')
185 nodegroup = factory.make_node_group()197 nodegroup = factory.make_node_group()
186 purpose = factory.make_name("purpose")198 purpose = factory.make_name("purpose")
187 relevant_image = factory.make_boot_image(199 relevant_image = factory.make_boot_image(
188 nodegroup=nodegroup, architecture=arch,200 nodegroup=nodegroup, osystem=osystem, architecture=arch,
189 subarchitecture=subarch, release=release, purpose=purpose,201 subarchitecture=subarch, release=release, purpose=purpose,
190 label=factory.make_name('label'))202 label=factory.make_name('label'))
191203
192 # Create a bunch of more recent but irrelevant BootImages..204 # Create a bunch of more recent but irrelevant BootImages..
193 factory.make_boot_image(205 factory.make_boot_image(
194 nodegroup=factory.make_node_group(), architecture=arch,206 nodegroup=factory.make_node_group(), osystem=osystem,
195 subarchitecture=subarch, release=release,207 architecture=arch, subarchitecture=subarch, release=release,
196 purpose=purpose, label=factory.make_name('label'))208 purpose=purpose, label=factory.make_name('label'))
197 factory.make_boot_image(209 factory.make_boot_image(
198 nodegroup=nodegroup,210 nodegroup=nodegroup, osystem=osystem,
199 architecture=factory.make_name('arch'),211 architecture=factory.make_name('arch'),
200 subarchitecture=subarch, release=release, purpose=purpose,212 subarchitecture=subarch, release=release, purpose=purpose,
201 label=factory.make_name('label'))213 label=factory.make_name('label'))
202 factory.make_boot_image(214 factory.make_boot_image(
203 nodegroup=nodegroup, architecture=arch,215 nodegroup=nodegroup, osystem=osystem, architecture=arch,
204 subarchitecture=factory.make_name('subarch'),216 subarchitecture=factory.make_name('subarch'),
205 release=release, purpose=purpose,217 release=release, purpose=purpose,
206 label=factory.make_name('label'))218 label=factory.make_name('label'))
207 factory.make_boot_image(219 factory.make_boot_image(
208 nodegroup=nodegroup,220 nodegroup=nodegroup, osystem=osystem,
209 architecture=factory.make_name('arch'),221 architecture=factory.make_name('arch'),
210 subarchitecture=subarch,222 subarchitecture=subarch,
211 release=factory.make_name('release'), purpose=purpose,223 release=factory.make_name('release'), purpose=purpose,
212 label=factory.make_name('label'))224 label=factory.make_name('label'))
213 factory.make_boot_image(225 factory.make_boot_image(
214 nodegroup=nodegroup,226 nodegroup=nodegroup, osystem=osystem,
227 architecture=factory.make_name('arch'),
228 subarchitecture=subarch, release=release,
229 purpose=factory.make_name('purpose'),
230 label=factory.make_name('label'))
231 factory.make_boot_image(
232 nodegroup=nodegroup, osystem=factory.make_name('os'),
215 architecture=factory.make_name('arch'),233 architecture=factory.make_name('arch'),
216 subarchitecture=subarch, release=release,234 subarchitecture=subarch, release=release,
217 purpose=factory.make_name('purpose'),235 purpose=factory.make_name('purpose'),
@@ -220,4 +238,66 @@
220 self.assertEqual(238 self.assertEqual(
221 relevant_image,239 relevant_image,
222 BootImage.objects.get_latest_image(240 BootImage.objects.get_latest_image(
223 nodegroup, arch, subarch, release, purpose))241 nodegroup, osystem, arch, subarch, release, purpose))
242
243 def test_get_usable_osystems_returns_supported_osystems(self):
244 nodegroup = factory.make_node_group()
245 osystems = [
246 factory.make_name('os'),
247 factory.make_name('os'),
248 ]
249 for osystem in osystems:
250 factory.make_boot_image(
251 osystem=osystem,
252 nodegroup=nodegroup)
253 self.assertItemsEqual(
254 osystems,
255 BootImage.objects.get_usable_osystems(nodegroup))
256
257 def test_get_usable_osystems_uses_given_nodegroup(self):
258 nodegroup = factory.make_node_group()
259 osystem = factory.make_name('os')
260 factory.make_boot_image(
261 osystem=osystem, nodegroup=nodegroup)
262 self.assertItemsEqual(
263 [],
264 BootImage.objects.get_usable_osystems(
265 factory.make_node_group()))
266
267 def test_get_usable_releases_returns_supported_releases(self):
268 nodegroup = factory.make_node_group()
269 osystem = factory.make_name('os')
270 releases = [
271 factory.make_name('release'),
272 factory.make_name('release'),
273 ]
274 for release in releases:
275 factory.make_boot_image(
276 osystem=osystem,
277 release=release,
278 nodegroup=nodegroup)
279 self.assertItemsEqual(
280 releases,
281 BootImage.objects.get_usable_releases(nodegroup, osystem))
282
283 def test_get_usable_releases_uses_given_nodegroup(self):
284 nodegroup = factory.make_node_group()
285 osystem = factory.make_name('os')
286 release = factory.make_name('release')
287 factory.make_boot_image(
288 osystem=osystem, release=release, nodegroup=nodegroup)
289 self.assertItemsEqual(
290 [],
291 BootImage.objects.get_usable_releases(
292 factory.make_node_group(), osystem))
293
294 def test_get_usable_releases_uses_given_osystem(self):
295 nodegroup = factory.make_node_group()
296 osystem = factory.make_name('os')
297 release = factory.make_name('release')
298 factory.make_boot_image(
299 osystem=osystem, release=release, nodegroup=nodegroup)
300 self.assertItemsEqual(
301 [],
302 BootImage.objects.get_usable_releases(
303 factory.make_node_group(), factory.make_name('os')))
224304
=== modified file 'src/maasserver/preseed.py'
--- src/maasserver/preseed.py 2014-04-10 13:43:33 +0000
+++ src/maasserver/preseed.py 2014-05-02 19:22:04 +0000
@@ -93,6 +93,7 @@
9393
94def get_curtin_installer_url(node):94def get_curtin_installer_url(node):
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."""
96 osystem = 'ubuntu'
96 series = node.get_distro_series()97 series = node.get_distro_series()
97 cluster_host = pick_cluster_controller_address(node)98 cluster_host = pick_cluster_controller_address(node)
98 # XXX rvb(?): The path shouldn't be hardcoded like this, but rather synced99 # XXX rvb(?): The path shouldn't be hardcoded like this, but rather synced
@@ -100,18 +101,20 @@
100 arch, subarch = node.architecture.split('/')101 arch, subarch = node.architecture.split('/')
101 purpose = 'xinstall'102 purpose = 'xinstall'
102 image = BootImage.objects.get_latest_image(103 image = BootImage.objects.get_latest_image(
103 node.nodegroup, arch, subarch, series, purpose)104 node.nodegroup, osystem, arch, subarch, series, purpose)
104 if image is None:105 if image is None:
105 raise MAASAPIException(106 raise MAASAPIException(
106 "Error generating the URL of curtin's root-tgz file. "107 "Error generating the URL of curtin's root-tgz file. "
107 "No image could be found for the given selection: "108 "No image could be found for the given selection: "
108 "arch=%s, subarch=%s, series=%s, purpose=%s." % (109 "os=%s, arch=%s, subarch=%s, series=%s, purpose=%s." % (
110 osystem,
109 arch,111 arch,
110 subarch,112 subarch,
111 series,113 series,
112 purpose114 purpose
113 ))115 ))
114 dyn_uri = '/'.join([116 dyn_uri = '/'.join([
117 osystem,
115 arch,118 arch,
116 subarch,119 subarch,
117 series,120 series,
118121
=== modified file 'src/maasserver/templates/maasserver/bootimage-list.html'
--- src/maasserver/templates/maasserver/bootimage-list.html 2014-03-27 07:39:38 +0000
+++ src/maasserver/templates/maasserver/bootimage-list.html 2014-05-02 19:22:04 +0000
@@ -20,6 +20,7 @@
20 <thead>20 <thead>
21 <tr>21 <tr>
22 <th>ID</th>22 <th>ID</th>
23 <th>OS</th>
23 <th>Release</th>24 <th>Release</th>
24 <th>Architecture</th>25 <th>Architecture</th>
25 <th>Subarchitecture</th>26 <th>Subarchitecture</th>
@@ -32,6 +33,7 @@
32 {% for bootimage in bootimage_list %}33 {% for bootimage in bootimage_list %}
33 <tr class="bootimage {% cycle 'even' 'odd' %}">34 <tr class="bootimage {% cycle 'even' 'odd' %}">
34 <td>{{ bootimage.id }}</td>35 <td>{{ bootimage.id }}</td>
36 <td>{{ bootimage.osystem }}</td>
35 <td>{{ bootimage.release }}</td>37 <td>{{ bootimage.release }}</td>
36 <td>{{ bootimage.architecture }}</td>38 <td>{{ bootimage.architecture }}</td>
37 <td>{{ bootimage.subarchitecture }}</td>39 <td>{{ bootimage.subarchitecture }}</td>
3840
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2014-04-04 06:46:05 +0000
+++ src/maasserver/testing/factory.py 2014-05-02 19:22:04 +0000
@@ -427,9 +427,11 @@
427 return "OAuth " + ", ".join([427 return "OAuth " + ", ".join([
428 '%s="%s"' % (key, value) for key, value in items.items()])428 '%s="%s"' % (key, value) for key, value in items.items()])
429429
430 def make_boot_image(self, architecture=None, subarchitecture=None,430 def make_boot_image(self, osystem=None, architecture=None,
431 release=None, purpose=None, nodegroup=None,431 subarchitecture=None, release=None, purpose=None,
432 label=None):432 nodegroup=None, label=None):
433 if osystem is None:
434 osystem = self.make_name('os')
433 if architecture is None:435 if architecture is None:
434 architecture = self.make_name('architecture')436 architecture = self.make_name('architecture')
435 if subarchitecture is None:437 if subarchitecture is None:
@@ -444,6 +446,7 @@
444 label = self.make_name('label')446 label = self.make_name('label')
445 return BootImage.objects.create(447 return BootImage.objects.create(
446 nodegroup=nodegroup,448 nodegroup=nodegroup,
449 osystem=osystem,
447 architecture=architecture,450 architecture=architecture,
448 subarchitecture=subarchitecture,451 subarchitecture=subarchitecture,
449 release=release,452 release=release,
450453
=== modified file 'src/maasserver/tests/test_api_boot_images.py'
--- src/maasserver/tests/test_api_boot_images.py 2014-03-21 19:01:40 +0000
+++ src/maasserver/tests/test_api_boot_images.py 2014-05-02 19:22:04 +0000
@@ -134,6 +134,7 @@
134 image = factory.make_boot_image()134 image = factory.make_boot_image()
135 self.assertEqual(135 self.assertEqual(
136 (136 (
137 image.osystem,
137 image.architecture,138 image.architecture,
138 image.subarchitecture,139 image.subarchitecture,
139 image.release,140 image.release,
@@ -146,6 +147,7 @@
146 image = make_boot_image_params()147 image = make_boot_image_params()
147 self.assertEqual(148 self.assertEqual(
148 (149 (
150 image['osystem'],
149 image['architecture'],151 image['architecture'],
150 image['subarchitecture'],152 image['subarchitecture'],
151 image['release'],153 image['release'],
@@ -158,12 +160,13 @@
158 image = make_boot_image_params()160 image = make_boot_image_params()
159 del image['subarchitecture']161 del image['subarchitecture']
160 del image['label']162 del image['label']
161 _, subarchitecture, _, label, _ = summarise_boot_image_dict(image)163 _, _, subarchitecture, _, label, _ = summarise_boot_image_dict(image)
162 self.assertEqual(('generic', 'release'), (subarchitecture, label))164 self.assertEqual(('generic', 'release'), (subarchitecture, label))
163165
164 def test_summarise_boot_image_functions_are_compatible(self):166 def test_summarise_boot_image_functions_are_compatible(self):
165 image_dict = make_boot_image_params()167 image_dict = make_boot_image_params()
166 image_obj = factory.make_boot_image(168 image_obj = factory.make_boot_image(
169 osystem=image_dict['osystem'],
167 architecture=image_dict['architecture'],170 architecture=image_dict['architecture'],
168 subarchitecture=image_dict['subarchitecture'],171 subarchitecture=image_dict['subarchitecture'],
169 release=image_dict['release'], label=image_dict['label'],172 release=image_dict['release'], label=image_dict['label'],
170173
=== modified file 'src/maasserver/tests/test_api_pxeconfig.py'
--- src/maasserver/tests/test_api_pxeconfig.py 2014-03-27 04:15:45 +0000
+++ src/maasserver/tests/test_api_pxeconfig.py 2014-05-02 19:22:04 +0000
@@ -133,9 +133,11 @@
133 self.assertEqual(value, response_dict['extra_opts'])133 self.assertEqual(value, response_dict['extra_opts'])
134134
135 def test_pxeconfig_uses_present_boot_image(self):135 def test_pxeconfig_uses_present_boot_image(self):
136 osystem = 'ubuntu'
136 release = Config.objects.get_config('commissioning_distro_series')137 release = Config.objects.get_config('commissioning_distro_series')
137 nodegroup = factory.make_node_group()138 nodegroup = factory.make_node_group()
138 factory.make_boot_image(139 factory.make_boot_image(
140 osystem=osystem,
139 architecture="amd64", release=release, nodegroup=nodegroup,141 architecture="amd64", release=release, nodegroup=nodegroup,
140 purpose="commissioning")142 purpose="commissioning")
141 params = self.get_default_params()143 params = self.get_default_params()
142144
=== modified file 'src/maasserver/tests/test_preseed.py'
--- src/maasserver/tests/test_preseed.py 2014-04-21 11:43:26 +0000
+++ src/maasserver/tests/test_preseed.py 2014-05-02 19:22:04 +0000
@@ -590,6 +590,7 @@
590 node = factory.make_node()590 node = factory.make_node()
591 arch, subarch = node.architecture.split('/')591 arch, subarch = node.architecture.split('/')
592 factory.make_boot_image(592 factory.make_boot_image(
593 osystem='ubuntu',
593 architecture=arch, subarchitecture=subarch,594 architecture=arch, subarchitecture=subarch,
594 release=node.get_distro_series(), purpose='xinstall',595 release=node.get_distro_series(), purpose='xinstall',
595 nodegroup=node.nodegroup)596 nodegroup=node.nodegroup)
@@ -677,27 +678,32 @@
677 self.assertIn('cloud-init', context['curtin_preseed'])678 self.assertIn('cloud-init', context['curtin_preseed'])
678679
679 def test_get_curtin_installer_url_returns_url(self):680 def test_get_curtin_installer_url_returns_url(self):
681 osystem = 'ubuntu'
680 # Exclude DISTRO_SERIES.default. It's a special value that defers682 # Exclude DISTRO_SERIES.default. It's a special value that defers
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.
682 series = factory.getRandomEnum(684 series = factory.getRandomEnum(
683 DISTRO_SERIES, but_not=DISTRO_SERIES.default)685 DISTRO_SERIES, but_not=DISTRO_SERIES.default)
684 architecture = make_usable_architecture(self)686 architecture = make_usable_architecture(self)
685 node = factory.make_node(687 node = factory.make_node(
686 architecture=architecture, distro_series=series)688 architecture=architecture,
689 distro_series=series)
687 arch, subarch = architecture.split('/')690 arch, subarch = architecture.split('/')
688 boot_image = factory.make_boot_image(691 boot_image = factory.make_boot_image(
689 architecture=arch, subarchitecture=subarch, release=series,692 osystem=osystem, architecture=arch,
693 subarchitecture=subarch, release=series,
690 purpose='xinstall', nodegroup=node.nodegroup)694 purpose='xinstall', nodegroup=node.nodegroup)
691695
692 installer_url = get_curtin_installer_url(node)696 installer_url = get_curtin_installer_url(node)
693697
694 [interface] = node.nodegroup.get_managed_interfaces()698 [interface] = node.nodegroup.get_managed_interfaces()
695 self.assertEqual(699 self.assertEqual(
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' % (
697 interface.ip, arch, subarch, series, boot_image.label),701 interface.ip, osystem, arch, subarch,
702 series, boot_image.label),
698 installer_url)703 installer_url)
699704
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):
706 osystem = 'ubuntu'
701 series = factory.getRandomEnum(707 series = factory.getRandomEnum(
702 DISTRO_SERIES, but_not=DISTRO_SERIES.default)708 DISTRO_SERIES, but_not=DISTRO_SERIES.default)
703 architecture = make_usable_architecture(self)709 architecture = make_usable_architecture(self)
@@ -705,6 +711,7 @@
705 architecture=architecture, distro_series=series)711 architecture=architecture, distro_series=series)
706 # Generate a boot image with a different arch/subarch.712 # Generate a boot image with a different arch/subarch.
707 factory.make_boot_image(713 factory.make_boot_image(
714 osystem=osystem,
708 architecture=factory.make_name('arch'),715 architecture=factory.make_name('arch'),
709 subarchitecture=factory.make_name('subarch'), release=series,716 subarchitecture=factory.make_name('subarch'), release=series,
710 purpose='xinstall', nodegroup=node.nodegroup)717 purpose='xinstall', nodegroup=node.nodegroup)
@@ -714,7 +721,8 @@
714 arch, subarch = architecture.split('/')721 arch, subarch = architecture.split('/')
715 msg = (722 msg = (
716 "No image could be found for the given selection: "723 "No image could be found for the given selection: "
717 "arch=%s, subarch=%s, series=%s, purpose=xinstall." % (724 "os=%s, arch=%s, subarch=%s, series=%s, purpose=xinstall." % (
725 osystem,
718 arch,726 arch,
719 subarch,727 subarch,
720 node.get_distro_series(),728 node.get_distro_series(),
721729
=== modified file 'src/maasserver/views/clusters.py'
--- src/maasserver/views/clusters.py 2014-04-03 11:20:03 +0000
+++ src/maasserver/views/clusters.py 2014-05-02 19:22:04 +0000
@@ -280,5 +280,5 @@
280 nodegroup = self.get_nodegroup()280 nodegroup = self.get_nodegroup()
281 # A sorted bootimages list.281 # A sorted bootimages list.
282 return nodegroup.bootimage_set.all().order_by(282 return nodegroup.bootimage_set.all().order_by(
283 '-release', 'architecture', 'subarchitecture', 'purpose',283 'osystem', '-release', 'architecture', 'subarchitecture',
284 'label')284 'purpose', 'label')
285285
=== modified file 'src/maasserver/views/tests/test_boot_image_list.py'
--- src/maasserver/views/tests/test_boot_image_list.py 2014-04-02 13:53:19 +0000
+++ src/maasserver/views/tests/test_boot_image_list.py 2014-05-02 19:22:04 +0000
@@ -54,8 +54,7 @@
54 self.client_log_in(as_admin=True)54 self.client_log_in(as_admin=True)
55 nodegroup = factory.make_node_group()55 nodegroup = factory.make_node_group()
56 # Create 4 images.56 # Create 4 images.
57 [57 [factory.make_boot_image(nodegroup=nodegroup) for _ in range(4)]
58 factory.make_boot_image(nodegroup=nodegroup) for _ in range(4)]
59 response = self.client.get(58 response = self.client.get(
60 reverse('cluster-bootimages-list', args=[nodegroup.uuid]))59 reverse('cluster-bootimages-list', args=[nodegroup.uuid]))
61 self.assertEqual(httplib.OK, response.status_code)60 self.assertEqual(httplib.OK, response.status_code)
6261
=== modified file 'src/metadataserver/tests/test_api.py'
--- src/metadataserver/tests/test_api.py 2014-03-24 13:02:28 +0000
+++ src/metadataserver/tests/test_api.py 2014-05-02 19:22:04 +0000
@@ -394,6 +394,7 @@
394 node = factory.make_node()394 node = factory.make_node()
395 arch, subarch = node.architecture.split('/')395 arch, subarch = node.architecture.split('/')
396 factory.make_boot_image(396 factory.make_boot_image(
397 osystem='ubuntu',
397 architecture=arch, subarchitecture=subarch,398 architecture=arch, subarchitecture=subarch,
398 release=node.get_distro_series(), purpose='xinstall',399 release=node.get_distro_series(), purpose='xinstall',
399 nodegroup=node.nodegroup)400 nodegroup=node.nodegroup)
400401
=== modified file 'src/provisioningserver/boot/__init__.py'
--- src/provisioningserver/boot/__init__.py 2014-03-28 16:46:55 +0000
+++ src/provisioningserver/boot/__init__.py 2014-05-02 19:22:04 +0000
@@ -168,7 +168,7 @@
168 """168 """
169 def image_dir(params):169 def image_dir(params):
170 return compose_image_path(170 return compose_image_path(
171 params.arch, params.subarch,171 'ubuntu', params.arch, params.subarch,
172 params.release, params.label)172 params.release, params.label)
173173
174 def initrd_path(params):174 def initrd_path(params):
175175
=== modified file 'src/provisioningserver/boot/tests/test_pxe.py'
--- src/provisioningserver/boot/tests/test_pxe.py 2014-03-28 04:31:32 +0000
+++ src/provisioningserver/boot/tests/test_pxe.py 2014-05-02 19:22:04 +0000
@@ -163,7 +163,7 @@
163 self.assertThat(output, StartsWith("DEFAULT "))163 self.assertThat(output, StartsWith("DEFAULT "))
164 # The PXE parameters are all set according to the options.164 # The PXE parameters are all set according to the options.
165 image_dir = compose_image_path(165 image_dir = compose_image_path(
166 arch=params.arch, subarch=params.subarch,166 osystem='ubuntu', arch=params.arch, subarch=params.subarch,
167 release=params.release, label=params.label)167 release=params.release, label=params.label)
168 self.assertThat(168 self.assertThat(
169 output, MatchesAll(169 output, MatchesAll(
@@ -268,7 +268,8 @@
268 section = config[section_label]268 section = config[section_label]
269 self.assertThat(269 self.assertThat(
270 section, ContainsAll(("KERNEL", "INITRD", "APPEND")))270 section, ContainsAll(("KERNEL", "INITRD", "APPEND")))
271 contains_arch_path = StartsWith("%s/" % section_label)271 contains_arch_path = StartsWith(
272 "ubuntu/%s/" % section_label)
272 self.assertThat(section["KERNEL"], contains_arch_path)273 self.assertThat(section["KERNEL"], contains_arch_path)
273 self.assertThat(section["INITRD"], contains_arch_path)274 self.assertThat(section["INITRD"], contains_arch_path)
274 self.assertIn("APPEND", section)275 self.assertIn("APPEND", section)
275276
=== modified file 'src/provisioningserver/boot/tests/test_tftppath.py'
--- src/provisioningserver/boot/tests/test_tftppath.py 2014-04-03 09:26:31 +0000
+++ src/provisioningserver/boot/tests/test_tftppath.py 2014-05-02 19:22:04 +0000
@@ -62,6 +62,7 @@
62 """Fake a boot image matching `image_params` under `tftproot`."""62 """Fake a boot image matching `image_params` under `tftproot`."""
63 image_dir = locate_tftp_path(63 image_dir = locate_tftp_path(
64 compose_image_path(64 compose_image_path(
65 osystem=image_params['osystem'],
65 arch=image_params['architecture'],66 arch=image_params['architecture'],
66 subarch=image_params['subarchitecture'],67 subarch=image_params['subarchitecture'],
67 release=image_params['release'],68 release=image_params['release'],
@@ -72,21 +73,23 @@
72 factory.make_file(image_dir, 'initrd.gz')73 factory.make_file(image_dir, 'initrd.gz')
7374
74 def test_compose_image_path_follows_storage_directory_layout(self):75 def test_compose_image_path_follows_storage_directory_layout(self):
76 osystem = factory.make_name('osystem')
75 arch = factory.make_name('arch')77 arch = factory.make_name('arch')
76 subarch = factory.make_name('subarch')78 subarch = factory.make_name('subarch')
77 release = factory.make_name('release')79 release = factory.make_name('release')
78 label = factory.make_name('label')80 label = factory.make_name('label')
79 self.assertEqual(81 self.assertEqual(
80 '%s/%s/%s/%s' % (arch, subarch, release, label),82 '%s/%s/%s/%s/%s' % (osystem, arch, subarch, release, label),
81 compose_image_path(arch, subarch, release, label))83 compose_image_path(osystem, arch, subarch, release, label))
8284
83 def test_compose_image_path_does_not_include_tftp_root(self):85 def test_compose_image_path_does_not_include_tftp_root(self):
86 osystem = factory.make_name('osystem')
84 arch = factory.make_name('arch')87 arch = factory.make_name('arch')
85 subarch = factory.make_name('subarch')88 subarch = factory.make_name('subarch')
86 release = factory.make_name('release')89 release = factory.make_name('release')
87 label = factory.make_name('label')90 label = factory.make_name('label')
88 self.assertThat(91 self.assertThat(
89 compose_image_path(arch, subarch, release, label),92 compose_image_path(osystem, arch, subarch, release, label),
90 Not(StartsWith(self.tftproot)))93 Not(StartsWith(self.tftproot)))
9194
92 def test_locate_tftp_path_prefixes_tftp_root(self):95 def test_locate_tftp_path_prefixes_tftp_root(self):
9396
=== modified file 'src/provisioningserver/boot/tests/test_uefi.py'
--- src/provisioningserver/boot/tests/test_uefi.py 2014-03-28 19:03:46 +0000
+++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-02 19:22:04 +0000
@@ -73,7 +73,7 @@
73 self.assertThat(output, StartsWith("set default=\"0\""))73 self.assertThat(output, StartsWith("set default=\"0\""))
74 # The UEFI parameters are all set according to the options.74 # The UEFI parameters are all set according to the options.
75 image_dir = compose_image_path(75 image_dir = compose_image_path(
76 arch=params.arch, subarch=params.subarch,76 osystem='ubuntu', arch=params.arch, subarch=params.subarch,
77 release=params.release, label=params.label)77 release=params.release, label=params.label)
7878
79 self.assertThat(79 self.assertThat(
8080
=== modified file 'src/provisioningserver/boot/tftppath.py'
--- src/provisioningserver/boot/tftppath.py 2014-04-03 16:36:15 +0000
+++ src/provisioningserver/boot/tftppath.py 2014-05-02 19:22:04 +0000
@@ -29,12 +29,13 @@
29logger = getLogger(__name__)29logger = getLogger(__name__)
3030
3131
32def compose_image_path(arch, subarch, release, label):32def compose_image_path(osystem, arch, subarch, release, label):
33 """Compose the TFTP path for a PXE kernel/initrd directory.33 """Compose the TFTP path for a PXE kernel/initrd directory.
3434
35 The path returned is relative to the TFTP root, as it would be35 The path returned is relative to the TFTP root, as it would be
36 identified by clients on the network.36 identified by clients on the network.
3737
38 :param osystem: Operating system.
38 :param arch: Main machine architecture.39 :param arch: Main machine architecture.
39 :param subarch: Sub-architecture, or "generic" if there is none.40 :param subarch: Sub-architecture, or "generic" if there is none.
40 :param release: Operating system release, e.g. "precise".41 :param release: Operating system release, e.g. "precise".
@@ -43,7 +44,7 @@
43 kernel and initrd) as exposed over TFTP.44 kernel and initrd) as exposed over TFTP.
44 """45 """
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.
46 return '/'.join([arch, subarch, release, label])47 return '/'.join([osystem, arch, subarch, release, label])
4748
4849
49def locate_tftp_path(path, tftproot):50def locate_tftp_path(path, tftproot):
@@ -116,7 +117,7 @@
116 The path must consist of a full [architecture, subarchitecture, release]117 The path must consist of a full [architecture, subarchitecture, release]
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.
118 """119 """
119 arch, subarch, release, label = path120 osystem, arch, subarch, release, label = path
120 # XXX: rvb 2014-03-24: The images import script currently imports all the121 # XXX: rvb 2014-03-24: The images import script currently imports all the
121 # images for the configured selections (where a selection is an122 # images for the configured selections (where a selection is an
122 # arch/subarch/series/label combination). When the import script grows the123 # arch/subarch/series/label combination). When the import script grows the
@@ -125,7 +126,7 @@
125 purposes = ['commissioning', 'install', 'xinstall']126 purposes = ['commissioning', 'install', 'xinstall']
126 return [127 return [
127 dict(128 dict(
128 architecture=arch, subarchitecture=subarch,129 osystem=osystem, architecture=arch, subarchitecture=subarch,
129 release=release, label=label, purpose=purpose)130 release=release, label=label, purpose=purpose)
130 for purpose in purposes131 for purpose in purposes
131 ]132 ]
@@ -139,9 +140,9 @@
139 `report_boot_images` API call.140 `report_boot_images` API call.
140 """141 """
141 # The sub-directories directly under tftproot, if they contain142 # The sub-directories directly under tftproot, if they contain
142 # images, represent architectures.143 # images, represent operating systems.
143 try:144 try:
144 potential_archs = list_subdirs(tftproot)145 potential_osystems = list_subdirs(tftproot)
145 except OSError as exception:146 except OSError as exception:
146 if exception.errno == errno.ENOENT:147 if exception.errno == errno.ENOENT:
147 # Directory does not exist, so return empty list.148 # Directory does not exist, so return empty list.
@@ -153,12 +154,12 @@
153154
154 # Starting point for iteration: paths that contain only the155 # Starting point for iteration: paths that contain only the
155 # top-level subdirectory of tftproot, i.e. the architecture name.156 # top-level subdirectory of tftproot, i.e. the architecture name.
156 paths = [[subdir] for subdir in potential_archs]157 paths = [[subdir] for subdir in potential_osystems]
157158
158 # Extend paths deeper into the filesystem, through the levels that159 # Extend paths deeper into the filesystem, through the levels that
159 # represent sub-architecture, release, and label. Any directory160 # represent architecture, sub-architecture, release, and label.
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.
161 for level in ['subarch', 'release', 'label']:162 for level in ['arch', 'subarch', 'release', 'label']:
162 paths = drill_down(tftproot, paths)163 paths = drill_down(tftproot, paths)
163164
164 # Each path we find this way should be a boot image.165 # Each path we find this way should be a boot image.
165166
=== modified file 'src/provisioningserver/import_images/boot_resources.py'
--- src/provisioningserver/import_images/boot_resources.py 2014-04-25 10:42:10 +0000
+++ src/provisioningserver/import_images/boot_resources.py 2014-05-02 19:22:04 +0000
@@ -45,7 +45,7 @@
45 """Raised when the config file for the script doesn't exist."""45 """Raised when the config file for the script doesn't exist."""
4646
4747
48def tgt_entry(arch, subarch, release, label, image):48def tgt_entry(osystem, arch, subarch, release, label, image):
49 """Generate tgt target used to commission arch/subarch with release49 """Generate tgt target used to commission arch/subarch with release
5050
51 Tgt target used to commission arch/subarch machine with a specific Ubuntu51 Tgt target used to commission arch/subarch machine with a specific Ubuntu
@@ -59,6 +59,7 @@
59 use the same inode for different tgt targets (even read-only targets which59 use the same inode for different tgt targets (even read-only targets which
60 looks like a bug to me) without this option enabled.60 looks like a bug to me) without this option enabled.
6161
62 :param osystem: Operating System name we generate tgt target for
62 :param arch: Architecture name we generate tgt target for63 :param arch: Architecture name we generate tgt target for
63 :param subarch: Subarchitecture name we generate tgt target for64 :param subarch: Subarchitecture name we generate tgt target for
64 :param release: Ubuntu release we generate tgt target for65 :param release: Ubuntu release we generate tgt target for
@@ -67,7 +68,13 @@
67 :return Tgt entry which can be written to tgt-admin configuration file68 :return Tgt entry which can be written to tgt-admin configuration file
68 """69 """
69 prefix = 'iqn.2004-05.com.ubuntu:maas'70 prefix = 'iqn.2004-05.com.ubuntu:maas'
70 target_name = 'ephemeral-%s-%s-%s-%s' % (arch, subarch, release, label)71 target_name = 'ephemeral-%s-%s-%s-%s-%s' % (
72 osystem,
73 arch,
74 subarch,
75 release,
76 label
77 )
71 entry = dedent("""\78 entry = dedent("""\
72 <target {prefix}:{target_name}>79 <target {prefix}:{target_name}>
73 readonly 180 readonly 1
@@ -110,17 +117,20 @@
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.
111 entries = set()118 entries = set()
112 for item in list_boot_images(snapshot_path):119 for item in list_boot_images(snapshot_path):
120 osystem = item['osystem']
113 arch = item['architecture']121 arch = item['architecture']
114 subarch = item['subarchitecture']122 subarch = item['subarchitecture']
115 release = item['release']123 release = item['release']
116 label = item['label']124 label = item['label']
117 entries.add((arch, subarch, release, label))125 entries.add((osystem, arch, subarch, release, label))
118 tgt_entries = []126 tgt_entries = []
119 for arch, subarch, release, label in sorted(entries):127 for osystem, arch, subarch, release, label in sorted(entries):
120 root_image = os.path.join(128 root_image = os.path.join(
121 snapshot_path, arch, subarch, release, label, 'root-image')129 snapshot_path, osystem, arch, subarch,
130 release, label, 'root-image')
122 if os.path.isfile(root_image):131 if os.path.isfile(root_image):
123 entry = tgt_entry(arch, subarch, release, label, root_image)132 entry = tgt_entry(
133 osystem, arch, subarch, release, label, root_image)
124 tgt_entries.append(entry)134 tgt_entries.append(entry)
125 text = ''.join(tgt_entries)135 text = ''.join(tgt_entries)
126 return text.encode('utf-8')136 return text.encode('utf-8')
127137
=== modified file 'src/provisioningserver/import_images/download_resources.py'
--- src/provisioningserver/import_images/download_resources.py 2014-04-28 09:16:32 +0000
+++ src/provisioningserver/import_images/download_resources.py 2014-05-02 19:22:04 +0000
@@ -251,6 +251,7 @@
251 """251 """
252 storage_path = os.path.abspath(storage_path)252 storage_path = os.path.abspath(storage_path)
253 snapshot_path = compose_snapshot_path(storage_path)253 snapshot_path = compose_snapshot_path(storage_path)
254 ubuntu_path = os.path.join(snapshot_path, 'ubuntu')
254 # Use a FileStore as our ObjectStore implementation. It will write to the255 # Use a FileStore as our ObjectStore implementation. It will write to the
255 # cache directory.256 # cache directory.
256 cache_path = os.path.join(storage_path, 'cache')257 cache_path = os.path.join(storage_path, 'cache')
@@ -260,7 +261,7 @@
260261
261 for source in sources:262 for source in sources:
262 download_boot_resources(263 download_boot_resources(
263 source['path'], store, snapshot_path, product_mapping,264 source['path'], store, ubuntu_path, product_mapping,
264 keyring=source['keyring'])265 keyring=source['keyring'])
265266
266 return snapshot_path267 return snapshot_path
267268
=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
--- src/provisioningserver/import_images/tests/test_boot_resources.py 2014-04-25 10:42:10 +0000
+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2014-05-02 19:22:04 +0000
@@ -51,9 +51,10 @@
5151
52 def test_generates_one_target(self):52 def test_generates_one_target(self):
53 spec = make_image_spec()53 spec = make_image_spec()
54 osystem = factory.make_name('osystem')
54 image = self.make_file()55 image = self.make_file()
55 entry = boot_resources.tgt_entry(56 entry = boot_resources.tgt_entry(
56 spec.arch, spec.subarch, spec.release, spec.label, image)57 osystem, spec.arch, spec.subarch, spec.release, spec.label, image)
57 # The entry looks a bit like XML, but isn't well-formed. So don't try58 # The entry looks a bit like XML, but isn't well-formed. So don't try
58 # to parse it as such!59 # to parse it as such!
59 self.assertIn('<target iqn.2004-05.com.ubuntu:maas:', entry)60 self.assertIn('<target iqn.2004-05.com.ubuntu:maas:', entry)
@@ -63,8 +64,9 @@
63 def test_produces_suitable_output_for_tgt_admin(self):64 def test_produces_suitable_output_for_tgt_admin(self):
64 spec = make_image_spec()65 spec = make_image_spec()
65 image = self.make_file()66 image = self.make_file()
67 osystem = factory.make_name('osystem')
66 entry = boot_resources.tgt_entry(68 entry = boot_resources.tgt_entry(
67 spec.arch, spec.subarch, spec.release, spec.label, image)69 osystem, spec.arch, spec.subarch, spec.release, spec.label, image)
68 config = self.make_file(contents=entry)70 config = self.make_file(contents=entry)
69 # Pretend to be root, but without requiring the actual privileges and71 # Pretend to be root, but without requiring the actual privileges and
70 # without prompting for a password. In that state, run tgt-admin.72 # without prompting for a password. In that state, run tgt-admin.
@@ -276,7 +278,7 @@
276 self.assertThat(os.path.join(current, 'maas.meta'), FileExists())278 self.assertThat(os.path.join(current, 'maas.meta'), FileExists())
277 self.assertThat(os.path.join(current, 'maas.tgt'), FileExists())279 self.assertThat(os.path.join(current, 'maas.tgt'), FileExists())
278 self.assertThat(280 self.assertThat(
279 os.path.join(current, arch, subarch, release, label),281 os.path.join(current, 'ubuntu', arch, subarch, release, label),
280 DirExists())282 DirExists())
281283
282 # Verify the contents of the "meta" file.284 # Verify the contents of the "meta" file.
283285
=== modified file 'src/provisioningserver/kernel_opts.py'
--- src/provisioningserver/kernel_opts.py 2014-03-20 10:44:56 +0000
+++ src/provisioningserver/kernel_opts.py 2014-05-02 19:22:04 +0000
@@ -30,9 +30,10 @@
3030
31KernelParametersBase = namedtuple(31KernelParametersBase = namedtuple(
32 "KernelParametersBase", (32 "KernelParametersBase", (
33 "osystem", # Operating system, e.g. "ubuntu"
33 "arch", # Machine architecture, e.g. "i386"34 "arch", # Machine architecture, e.g. "i386"
34 "subarch", # Machine subarchitecture, e.g. "generic"35 "subarch", # Machine subarchitecture, e.g. "generic"
35 "release", # Ubuntu release, e.g. "precise"36 "release", # OS release, e.g. "precise"
36 "label", # Image label, e.g. "release"37 "label", # Image label, e.g. "release"
37 "purpose", # Boot purpose, e.g. "commissioning"38 "purpose", # Boot purpose, e.g. "commissioning"
38 "hostname", # Machine hostname, e.g. "coleman"39 "hostname", # Machine hostname, e.g. "coleman"
@@ -90,9 +91,15 @@
90ISCSI_TARGET_NAME_PREFIX = "iqn.2004-05.com.ubuntu:maas"91ISCSI_TARGET_NAME_PREFIX = "iqn.2004-05.com.ubuntu:maas"
9192
9293
93def get_ephemeral_name(arch, subarch, release, label):94def get_ephemeral_name(osystem, arch, subarch, release, label):
94 """Return the name of the most recent ephemeral image."""95 """Return the name of the most recent ephemeral image."""
95 return "ephemeral-%s-%s-%s-%s" % (arch, subarch, release, label)96 return "ephemeral-%s-%s-%s-%s-%s" % (
97 osystem,
98 arch,
99 subarch,
100 release,
101 label
102 )
96103
97104
98def compose_hostname_opts(params):105def compose_hostname_opts(params):
@@ -119,7 +126,8 @@
119 # These are kernel parameters read by the ephemeral environment.126 # These are kernel parameters read by the ephemeral environment.
120 tname = prefix_target_name(127 tname = prefix_target_name(
121 get_ephemeral_name(128 get_ephemeral_name(
122 params.arch, params.subarch, params.release, params.label))129 params.osystem, params.arch, params.subarch,
130 params.release, params.label))
123 kernel_params = [131 kernel_params = [
124 # Read by the open-iscsi initramfs code.132 # Read by the open-iscsi initramfs code.
125 "iscsi_target_name=%s" % tname,133 "iscsi_target_name=%s" % tname,
126134
=== modified file 'src/provisioningserver/rpc/cluster.py'
--- src/provisioningserver/rpc/cluster.py 2014-03-20 22:36:32 +0000
+++ src/provisioningserver/rpc/cluster.py 2014-05-02 19:22:04 +0000
@@ -33,7 +33,8 @@
33 arguments = []33 arguments = []
34 response = [34 response = [
35 (b"images", amp.AmpList(35 (b"images", amp.AmpList(
36 [(b"architecture", amp.Unicode()),36 [(b"osystem", amp.Unicode()),
37 (b"architecture", amp.Unicode()),
37 (b"subarchitecture", amp.Unicode()),38 (b"subarchitecture", amp.Unicode()),
38 (b"release", amp.Unicode()),39 (b"release", amp.Unicode()),
39 (b"label", amp.Unicode()),40 (b"label", amp.Unicode()),
4041
=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
--- src/provisioningserver/rpc/tests/test_clusterservice.py 2014-03-28 04:31:32 +0000
+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2014-05-02 19:22:04 +0000
@@ -171,6 +171,7 @@
171 # serialised correctly.171 # serialised correctly.
172172
173 # Example boot image definitions.173 # Example boot image definitions.
174 osystems = "ubuntu", "centos"
174 archs = "i386", "amd64"175 archs = "i386", "amd64"
175 subarchs = "generic", "special"176 subarchs = "generic", "special"
176 releases = "precise", "trusty"177 releases = "precise", "trusty"
@@ -179,7 +180,7 @@
179180
180 # Create a TFTP file tree with a variety of subdirectories.181 # Create a TFTP file tree with a variety of subdirectories.
181 tftpdir = self.make_dir()182 tftpdir = self.make_dir()
182 for options in product(archs, subarchs, releases, labels):183 for options in product(osystems, archs, subarchs, releases, labels):
183 os.makedirs(os.path.join(tftpdir, *options))184 os.makedirs(os.path.join(tftpdir, *options))
184185
185 # Ensure that list_boot_images() uses the above TFTP file tree.186 # Ensure that list_boot_images() uses the above TFTP file tree.
@@ -187,14 +188,15 @@
187188
188 expected_images = [189 expected_images = [
189 {190 {
191 "osystem": osystem,
190 "architecture": arch,192 "architecture": arch,
191 "subarchitecture": subarch,193 "subarchitecture": subarch,
192 "release": release,194 "release": release,
193 "label": label,195 "label": label,
194 "purpose": purpose,196 "purpose": purpose,
195 }197 }
196 for arch, subarch, release, label, purpose in product(198 for osystem, arch, subarch, release, label, purpose in product(
197 archs, subarchs, releases, labels, purposes)199 osystems, archs, subarchs, releases, labels, purposes)
198 ]200 ]
199201
200 response = yield call_responder(Cluster(), cluster.ListBootImages, {})202 response = yield call_responder(Cluster(), cluster.ListBootImages, {})
201203
=== modified file 'src/provisioningserver/testing/boot_images.py'
--- src/provisioningserver/testing/boot_images.py 2014-03-21 03:21:57 +0000
+++ src/provisioningserver/testing/boot_images.py 2014-05-02 19:22:04 +0000
@@ -23,10 +23,11 @@
23 """Create an arbitrary dict of boot-image parameters.23 """Create an arbitrary dict of boot-image parameters.
2424
25 These are the parameters that together describe a kind of boot for25 These are the parameters that together describe a kind of boot for
26 which we may need a kernel and initrd: architecture,26 which we may need a kernel and initrd: operating system, architecture,
27 sub-architecture, Ubuntu release, boot purpose, and release label.27 sub-architecture, Ubuntu release, boot purpose, and release label.
28 """28 """
29 return dict(29 return dict(
30 osystem=factory.make_name('osystem'),
30 architecture=factory.make_name('architecture'),31 architecture=factory.make_name('architecture'),
31 subarchitecture=factory.make_name('subarchitecture'),32 subarchitecture=factory.make_name('subarchitecture'),
32 release=factory.make_name('release'),33 release=factory.make_name('release'),
@@ -39,9 +40,11 @@
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.
4041
41 These are the parameters that together describe a path to store a boot42 These are the parameters that together describe a path to store a boot
42 image: architecture, sub-architecture, Ubuntu release, and release label.43 image: operating system, architecture, sub-architecture, Ubuntu release,
44 and release label.
43 """45 """
44 return dict(46 return dict(
47 osystem=factory.make_name('osystem'),
45 architecture=factory.make_name('architecture'),48 architecture=factory.make_name('architecture'),
46 subarchitecture=factory.make_name('subarchitecture'),49 subarchitecture=factory.make_name('subarchitecture'),
47 release=factory.make_name('release'),50 release=factory.make_name('release'),
4851
=== modified file 'src/provisioningserver/tests/test_kernel_opts.py'
--- src/provisioningserver/tests/test_kernel_opts.py 2014-03-28 16:46:55 +0000
+++ src/provisioningserver/tests/test_kernel_opts.py 2014-05-02 19:22:04 +0000
@@ -228,7 +228,8 @@
228 # options for a "xinstall" node.228 # options for a "xinstall" node.
229 params = self.make_kernel_parameters(purpose="xinstall")229 params = self.make_kernel_parameters(purpose="xinstall")
230 ephemeral_name = get_ephemeral_name(230 ephemeral_name = get_ephemeral_name(
231 params.arch, params.subarch, params.release, params.label)231 params.osystem, params.arch, params.subarch,
232 params.release, params.label)
232 self.assertThat(233 self.assertThat(
233 compose_kernel_command_line(params),234 compose_kernel_command_line(params),
234 ContainsAll([235 ContainsAll([
@@ -243,7 +244,8 @@
243 # options for a "commissioning" node.244 # options for a "commissioning" node.
244 params = self.make_kernel_parameters(purpose="commissioning")245 params = self.make_kernel_parameters(purpose="commissioning")
245 ephemeral_name = get_ephemeral_name(246 ephemeral_name = get_ephemeral_name(
246 params.arch, params.subarch, params.release, params.label)247 params.osystem, params.arch, params.subarch,
248 params.release, params.label)
247 self.assertThat(249 self.assertThat(
248 compose_kernel_command_line(params),250 compose_kernel_command_line(params),
249 ContainsAll([251 ContainsAll([