Merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:~maas-committers/maas/trunk

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 2414
Proposed branch: lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 709 lines (+328/-189)
9 files modified
src/maasserver/forms.py (+17/-97)
src/maasserver/forms_settings.py (+46/-42)
src/maasserver/models/bootsourceselection.py (+1/-10)
src/maasserver/testing/factory.py (+17/-1)
src/maasserver/tests/test_forms.py (+60/-0)
src/maasserver/utils/os.py (+126/-0)
src/maasserver/views/tests/test_settings.py (+6/-4)
src/provisioningserver/boot/tests/test_tftppath.py (+1/-35)
src/provisioningserver/testing/os.py (+54/-0)
To merge this branch: bzr merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Gavin Panella (community) Approve
Review via email: mp+222010@code.launchpad.net

Commit message

The "deploy" section of the settings page will now no longer show operating systems and releases for which MAAS doesn't have boot resources. Previously, the page listed options that couldn't be used due to a lack of boot resources.

Description of the change

The logic that I've added / tweaked works as follows:

OSes and releases will appear if:
 - There are boot images for the OS / release
 - The OS and release are supported according the OperatingSystemRegistry.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0. Got: 1 Pending.

Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, but I have several comments.

Also, there probably ought to be a test to demonstrate that DeployForm has fields that reflect the current state of the system, rather than that as import time.

Rapheël mentioned using callables for parts of Django's form machinery. Did you investigate that?

Revision history for this message
Gavin Panella (allenap) :
review: Approve
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (6.2 KiB)

Thanks for the review and for sanity-checking me :)

On 4 June 2014 15:19, Gavin Panella <email address hidden> wrote:
> Looks good, but I have several comments.
>
> Also, there probably ought to be a test to demonstrate that DeployForm has fields that reflect the current state of the system, rather than that as import time.

Good point. I'll work on that.

> Rapheël mentioned using callables for parts of Django's form machinery. Did you investigate that?

Yeah, doesn't work (there's an open ticket for it in the Django Trac).
Well, it does for `initial` values, but not for `choices`. Because,
why would you want something that useful.

> Diff comments:
>
>> === modified file 'src/maasserver/forms.py'
>> --- src/maasserver/forms.py 2014-05-29 18:20:58 +0000
>> +++ src/maasserver/forms.py 2014-06-04 09:49:20 +0000
>> @@ -230,7 +230,10 @@
>> for nodegroup in NodeGroup.objects.all():
>> osystems = osystems.union(
>> BootImage.objects.get_usable_osystems(nodegroup))
>> - osystems = [OperatingSystemRegistry[osystem] for osystem in osystems]
>> + osystems = [
>> + OperatingSystemRegistry[osystem] for osystem in osystems
>> + if osystem in OperatingSystemRegistry
>
> I suspect this needs to be changed to query the cluster. All information on what OS/release combos could be supported are in the simplestream that describes boot images. The information on what OS/release combos that *can be supported right now* is in the list of boot resources downloaded. Both of those originate in the cluster, though the latter is also cached in BootImage.
>
> This doesn't need to be changed in this branch, but there ought to be a plan to address it.

Right. I'll file a bug once this is landed.

>> + ]
>> return sorted(osystems, key=lambda osystem: osystem.title)
>>
>>
>> @@ -1023,9 +1026,33 @@
>>
>> class DeployForm(ConfigForm):
>> """Settings page, Deploy section."""
>> +
>> default_osystem = get_config_field('default_osystem')
>> default_distro_series = get_config_field('default_distro_series')
>>
>> + def __init__(self, *args, **kwargs):
>> + super(DeployForm, self).__init__(*args, **kwargs)
>> + # There's a race condition of some descrption that causes an
>> + # error when walking the BootImage table at import time (as
>> + # get_usable{os|release}_choices ultimately do). Setting the
>> + # choices here is less expensive than trying to find the
>> + # underlying race, which might be a Django thing.
>> + usable_oses = list_all_usable_osystems()
>> + os_choices = list_osystem_choices(usable_oses)
>> + self.fields['default_osystem'].choices = os_choices
>> + self.fields['default_osystem'].error_messages = {
>> + 'invalid_choice': compose_invalid_choice_text(
>> + 'osystem', os_choices)
>> + }
>> +
>> + release_choices = list_release_choices(
>> + list_all_usable_releases(usable_oses))
>> + self.fields['default_distro_series'].choices = release_choices
>> + self.fields['default_distro_series'].error_messages = {
>> + 'invalid_choice':...

Read more...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (257.6 KiB)

The attempt to merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [17.0 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [4,212 B]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [53.3 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [17.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [52.4 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [29.7 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [136 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [79.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 509 kB in 0s (1,897 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httpli...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 05/06/14 00:19, Gavin Panella wrote:
> I suspect this needs to be changed to query the cluster. All information on what OS/release combos could be supported are in the simplestream that describes boot images. The information on what OS/release combos that *can be supported right now* is in the list of boot resources downloaded. Both of those originate in the cluster, though the latter is also cached in BootImage.
>
> This doesn't need to be changed in this branch, but there ought to be a plan to address it.

I have previously raised this with the original author (Blason!) and we
deferred it since the branch at the time was urgent.

So I would check with those guys to see if there's any plan in place. I
suspect not :)

But this is top of my tech-debt list for sure.

Revision history for this message
Raphaël Badin (rvb) wrote :

I've added a couple of inline comments… please wait before landing this, I need to check something first…

Revision history for this message
Raphaël Badin (rvb) wrote :

The OperatingSystem class has moved apparently, you need to do something like: http://paste.ubuntu.com/7593412/.

Revision history for this message
Raphaël Badin (rvb) wrote :

There is a problem with moving the fields's attributes into DeployForm's init method.

These fields (default_osystem and default_distro_series) are also used by the API *without* the DeployForm. This means that, with the change from this branch, you can't set default_osystem and default_distro_series through the API, because the list of choices for these fields is empty.

There is a (generic) test for this in src/maasserver/tests/test_api.py:MAASAPITest.test_admin_set_config. It's generic in the sense that it is not testing default_osystem or default_distro_series specifically but it's a good starting point if you want to recreate the problem I'm describing.

The code path I'm describing here is:
- API:set_config
- src/maasserver/api.py:MaasHandler.set_config()
- src/maasserver/forms_settings.py:get_config_form() (Creates an ad-hoc form with just one field, built from CONFIG_ITEMS)
- src/maasserver/forms_settings.py:get_config_field()

review: Needs Fixing
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good but I've got a couple of suggestions (see inline comments).

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (53.2 KiB)

The attempt to merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [17.1 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [4,212 B]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [55.0 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [17.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [57.8 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [30.1 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [142 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [80.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 524 kB in 0s (1,814 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httpli...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.7 KiB)

The attempt to merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [17.1 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [4,212 B]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [55.0 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [17.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [57.8 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [30.1 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [142 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [80.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 524 kB in 0s (1,808 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httpli...

Revision history for this message
Graham Binns (gmb) wrote :

On 11 June 2014 07:52, MaaS Lander <email address hidden> wrote:
> FAIL: maasserver.tests.test_forms.TestDeployForm.test_uses_live_data

I can't get this to fail locally, annoyingly… Will
run-once-until-failure -it for a bit.

Revision history for this message
Raphaël Badin (rvb) wrote :

I also think it would be worth having an end-to-end API test to make sure one of the config settings you're changing here can still be set using the API. Don't need to test them all since the testing at the form-level covers that.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.7 KiB)

The attempt to merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [17.1 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [4,212 B]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [55.0 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [17.9 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [57.8 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [30.1 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [142 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [80.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 524 kB in 0s (1,913 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httpli...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (16.7 KiB)

The attempt to merge lp:~gmb/maas/fix-commissioning-page-distro-list-bug-1312844 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [17.1 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [4,212 B]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [55.0 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [17.9 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [57.8 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [30.1 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [142 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [80.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 524 kB in 0s (1,742 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httpli...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/forms.py'
2--- src/maasserver/forms.py 2014-06-11 04:38:41 +0000
3+++ src/maasserver/forms.py 2014-06-11 09:53:56 +0000
4@@ -112,6 +112,14 @@
5 from maasserver.utils import strip_domain
6 from maasserver.utils.forms import compose_invalid_choice_text
7 from maasserver.utils.network import make_network
8+from maasserver.utils.os import (
9+ get_distro_series_inital,
10+ get_release_requires_key,
11+ list_all_usable_osystems,
12+ list_all_usable_releases,
13+ list_osystem_choices,
14+ list_release_choices,
15+ )
16 from metadataserver.fields import Bin
17 from metadataserver.models import CommissioningScript
18 from provisioningserver.drivers.osystem import OperatingSystemRegistry
19@@ -215,99 +223,6 @@
20 return all_architectures[0]
21
22
23-def list_all_usable_osystems():
24- """Return all operating systems that can be used for nodes.
25-
26- These are the operating systems for which any nodegroup has the boot images
27- required to boot the node.
28- """
29- # The Node edit form offers all usable operating systems as options for the
30- # osystem field. Not all of these may be available in the node's
31- # nodegroup, but to represent that accurately in the UI would depend on
32- # the currently selected nodegroup. Narrowing the options down further
33- # would have to happen browser-side.
34- osystems = set()
35- for nodegroup in NodeGroup.objects.all():
36- osystems = osystems.union(
37- BootImage.objects.get_usable_osystems(nodegroup))
38- osystems = [OperatingSystemRegistry[osystem] for osystem in osystems]
39- return sorted(osystems, key=lambda osystem: osystem.title)
40-
41-
42-def list_osystem_choices(osystems):
43- """Return Django "choices" list for `osystem`."""
44- choices = [('', 'Default OS')]
45- choices += [
46- (osystem.name, osystem.title)
47- for osystem in osystems
48- ]
49- return choices
50-
51-
52-def list_all_usable_releases(osystems):
53- """Return dictionary of usable `releases` for each opertaing system."""
54- distro_series = {}
55- nodegroups = list(NodeGroup.objects.all())
56- for osystem in osystems:
57- releases = set()
58- for nodegroup in nodegroups:
59- releases = releases.union(
60- BootImage.objects.get_usable_releases(nodegroup, osystem.name))
61- distro_series[osystem.name] = sorted(releases)
62- return distro_series
63-
64-
65-def get_release_requires_key(osystem, release):
66- """Return astrisk for any release that requires
67- a license key.
68-
69- This is used by the JS, to display the licese_key field.
70- """
71- if osystem.requires_license_key(release):
72- return '*'
73- return ''
74-
75-
76-def list_release_choices(releases):
77- """Return Django "choices" list for `releases`."""
78- choices = [('', 'Default OS Release')]
79- for key, value in releases.items():
80- osystem = OperatingSystemRegistry[key]
81- options = osystem.format_release_choices(value)
82- requires_key = get_release_requires_key(osystem, '')
83- choices += [(
84- '%s/%s' % (osystem.name, requires_key),
85- 'Latest %s Release' % osystem.title
86- )]
87- choices += [(
88- '%s/%s%s' % (
89- osystem.name,
90- name,
91- get_release_requires_key(osystem, name)
92- ),
93- title
94- )
95- for name, title in options
96- ]
97- return choices
98-
99-
100-def get_distro_series_inital(instance):
101- """Returns the distro_series initial value for the instance."""
102- osystem = instance.osystem
103- series = instance.distro_series
104- os_obj = OperatingSystemRegistry.get_item(osystem)
105- if os_obj is not None:
106- key_required = get_release_requires_key(os_obj, series)
107- else:
108- key_required = ''
109- if osystem is not None and osystem != '':
110- if series is None:
111- series = ''
112- return '%s/%s%s' % (osystem, series, key_required)
113- return None
114-
115-
116 def clean_distro_series_field(form, field, os_field):
117 """Cleans the distro_series field in the form. Validating that
118 the selected operating system matches the distro_series.
119@@ -1080,13 +995,18 @@
120
121 class DeployForm(ConfigForm):
122 """Settings page, Deploy section."""
123- default_osystem = get_config_field('default_osystem')
124- default_distro_series = get_config_field('default_distro_series')
125+
126+ def __init__(self, *args, **kwargs):
127+ Form.__init__(self, *args, **kwargs)
128+ self.fields['default_osystem'] = get_config_field('default_osystem')
129+ self.fields['default_distro_series'] = get_config_field(
130+ 'default_distro_series')
131+ self._load_initials()
132
133 def _load_initials(self):
134 super(DeployForm, self)._load_initials()
135- initial_os = self.initial['default_osystem']
136- initial_series = self.initial['default_distro_series']
137+ initial_os = self.fields['default_osystem'].initial
138+ initial_series = self.fields['default_distro_series'].initial
139 self.initial['default_distro_series'] = '%s/%s' % (
140 initial_os,
141 initial_series
142
143=== modified file 'src/maasserver/forms_settings.py'
144--- src/maasserver/forms_settings.py 2014-06-10 14:45:14 +0000
145+++ src/maasserver/forms_settings.py 2014-06-11 09:53:56 +0000
146@@ -23,43 +23,58 @@
147 from socket import gethostname
148
149 from django import forms
150-from maasserver.models.config import DEFAULT_OS
151+from maasserver.models.config import (
152+ Config,
153+ DEFAULT_OS,
154+ )
155 from maasserver.utils.forms import compose_invalid_choice_text
156-from provisioningserver.drivers.osystem import OperatingSystemRegistry
157+from maasserver.utils.os import (
158+ list_all_usable_osystems,
159+ list_all_usable_releases,
160+ list_osystem_choices,
161+ list_release_choices,
162+ )
163
164
165 INVALID_URL_MESSAGE = "Enter a valid url (e.g. http://host.example.com)."
166
167
168-def list_osystem_choices():
169- osystems = [osystem for _, osystem in OperatingSystemRegistry]
170- osystems = sorted(osystems, key=lambda osystem: osystem.title)
171- return [
172- (osystem.name, osystem.title)
173- for osystem in osystems
174- ]
175-
176-
177-def list_release_choices():
178- osystems = [osystem for _, osystem in OperatingSystemRegistry]
179- choices = []
180- for osystem in osystems:
181- supported = sorted(osystem.get_supported_releases())
182- options = osystem.format_release_choices(supported)
183- options = [
184- ('%s/%s' % (osystem.name, name), title)
185- for name, title in options
186- ]
187- choices += options
188- return choices
189-
190-
191 def list_commisioning_choices():
192 releases = DEFAULT_OS.get_supported_commissioning_releases()
193 options = DEFAULT_OS.format_release_choices(releases)
194 return list(options)
195
196
197+def make_default_osystem_field(*args, **kwargs):
198+ """Build and return the default_osystem field."""
199+ usable_oses = list_all_usable_osystems()
200+ os_choices = list_osystem_choices(usable_oses)
201+ field = forms.ChoiceField(
202+ initial = Config.objects.get_config('default_osystem'),
203+ choices = os_choices,
204+ error_messages = {
205+ 'invalid_choice': compose_invalid_choice_text(
206+ 'osystem', os_choices)
207+ },
208+ **kwargs)
209+ return field
210+
211+
212+def make_default_distro_series_field(*args, **kwargs):
213+ usable_oses = list_all_usable_osystems()
214+ release_choices = list_release_choices(
215+ list_all_usable_releases(usable_oses))
216+ field = forms.ChoiceField(
217+ initial = Config.objects.get_config('default_distro_series'),
218+ choices = release_choices,
219+ error_messages = {
220+ 'invalid_choice': compose_invalid_choice_text(
221+ 'release', release_choices)
222+ },
223+ **kwargs)
224+ return field
225+
226+
227 CONFIG_ITEMS = {
228 'check_compatibility': {
229 'default': False,
230@@ -162,32 +177,21 @@
231 }
232 },
233 'default_osystem': {
234- 'default': DEFAULT_OS.name,
235- 'form': forms.ChoiceField,
236+ 'form': make_default_osystem_field,
237 'form_kwargs': {
238 'label': "Default operating system used for deployment",
239- 'choices': list_osystem_choices(),
240 'required': False,
241- 'error_messages': {
242- 'invalid_choice': compose_invalid_choice_text(
243- 'osystem',
244- list_osystem_choices())},
245+ # This field's `choices` and `error_messages` are populated
246+ # at run-time to avoid a race condition.
247 }
248 },
249 'default_distro_series': {
250- 'default': '%s/%s' % (
251- DEFAULT_OS.name,
252- DEFAULT_OS.get_default_release()
253- ),
254- 'form': forms.ChoiceField,
255+ 'form': make_default_distro_series_field,
256 'form_kwargs': {
257 'label': "Default OS release used for deployment",
258- 'choices': list_release_choices(),
259 'required': False,
260- 'error_messages': {
261- 'invalid_choice': compose_invalid_choice_text(
262- 'distro_series',
263- list_release_choices())},
264+ # This field's `choices` and `error_messages` are populated
265+ # at run-time to avoid a race condition.
266 }
267 },
268 'commissioning_distro_series': {
269
270=== modified file 'src/maasserver/models/bootsourceselection.py'
271--- src/maasserver/models/bootsourceselection.py 2014-06-10 14:45:14 +0000
272+++ src/maasserver/models/bootsourceselection.py 2014-06-11 09:53:56 +0000
273@@ -26,14 +26,6 @@
274 from maasserver import DefaultMeta
275 from maasserver.models.cleansave import CleanSave
276 from maasserver.models.timestampedmodel import TimestampedModel
277-from provisioningserver.drivers.osystem.ubuntu import UbuntuOS
278-
279-
280-def list_release_choices():
281- """Return Django "choices" list for Ubuntu releases."""
282- osystem = UbuntuOS()
283- releases = osystem.get_supported_releases()
284- return osystem.format_release_choices(releases)
285
286
287 class BootSourceSelectionManager(Manager):
288@@ -51,8 +43,7 @@
289 boot_source = ForeignKey('maasserver.BootSource', blank=False)
290
291 release = CharField(
292- max_length=20, choices=list_release_choices(), blank=True,
293- default='',
294+ max_length=20, blank=True, default='',
295 help_text="The Ubuntu release for which to import resources.")
296
297 arches = djorm_pgarray.fields.ArrayField(dbtype="text")
298
299=== modified file 'src/maasserver/testing/factory.py'
300--- src/maasserver/testing/factory.py 2014-06-11 04:38:41 +0000
301+++ src/maasserver/testing/factory.py 2014-06-11 09:53:56 +0000
302@@ -64,7 +64,10 @@
303 # XXX 2014-05-13 blake-rouse bug=1319143
304 # Need to not import directly, use RPC to info from cluster.
305 from provisioningserver.drivers.osystem import OperatingSystemRegistry
306-from provisioningserver.drivers.osystem.ubuntu import UbuntuOS
307+from provisioningserver.drivers.osystem.ubuntu import (
308+ UbuntuOS,
309+ )
310+from provisioningserver.testing.os import FakeOS
311
312 # We have a limited number of public keys:
313 # src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub
314@@ -170,6 +173,19 @@
315 finally:
316 NODE_TRANSITIONS[None] = valid_initial_states
317
318+ def make_operating_system(self, name=None, release=None):
319+ """Make an :class:`OperatingSystem`."""
320+ if name is None:
321+ name = self.make_name('os')
322+ if release is None:
323+ release = self.make_name('release')
324+ self.make_boot_image(release=release, osystem=name)
325+ new_operating_system = FakeOS(
326+ name=name, purpose="install", releases=[release])
327+ OperatingSystemRegistry.register_item(
328+ new_operating_system.name, new_operating_system)
329+ return new_operating_system
330+
331 def make_node(self, mac=False, hostname=None, status=None,
332 architecture="i386/generic", updated=None,
333 created=None, nodegroup=None, routers=None, zone=None,
334
335=== modified file 'src/maasserver/tests/test_forms.py'
336--- src/maasserver/tests/test_forms.py 2014-06-11 04:38:41 +0000
337+++ src/maasserver/tests/test_forms.py 2014-06-11 09:53:56 +0000
338@@ -44,6 +44,7 @@
339 CommissioningForm,
340 CommissioningScriptForm,
341 ConfigForm,
342+ DeployForm,
343 DownloadProgressForm,
344 EditUserForm,
345 get_action_form,
346@@ -78,6 +79,7 @@
347 ValidatorMultipleChoiceField,
348 ZoneForm,
349 )
350+from provisioningserver.drivers.osystem import ubuntu
351 from maasserver.models import (
352 Config,
353 MACAddress,
354@@ -240,6 +242,28 @@
355 self.assertEqual(
356 [osystem], list_all_usable_osystems())
357
358+ def test_list_all_usable_osystems_omits_oses_without_boot_images(self):
359+ usable_os_name = factory.make_name('os')
360+ unusable_os_name = factory.make_name('os')
361+ self.make_usable_boot_images(osystem=usable_os_name)
362+ usable_os = make_usable_osystem(self, usable_os_name)
363+ unusable_os = make_usable_osystem(self, unusable_os_name)
364+
365+ usable_os_list = list_all_usable_osystems()
366+ self.assertIn(usable_os, usable_os_list)
367+ self.assertNotIn(unusable_os, usable_os_list)
368+
369+ def test_list_all_usable_osystems_omits_oses_not_supported(self):
370+ usable_os_name = factory.make_name('os')
371+ unusable_os_name = factory.make_name('os')
372+ self.make_usable_boot_images(osystem=usable_os_name)
373+ self.make_usable_boot_images(osystem=unusable_os_name)
374+ usable_os = make_usable_osystem(self, usable_os_name)
375+
376+ usable_os_list = list_all_usable_osystems()
377+ self.assertIn(usable_os, usable_os_list)
378+ self.assertNotIn(unusable_os_name, [os.name for os in usable_os_list])
379+
380 def test_list_all_usable_releases_combines_nodegroups(self):
381 expected = {}
382 osystems = []
383@@ -2146,3 +2170,39 @@
384 self.assertTrue(form.is_valid(), form._errors)
385 boot_source_selection = form.save()
386 self.assertAttributes(boot_source_selection, params)
387+
388+
389+class TestDeployForm(MAASServerTestCase):
390+ """Tests for `DeployForm`."""
391+
392+ def test_uses_live_data(self):
393+ # The DeployForm uses the database rather than just relying on
394+ # hard-coded stuff.
395+ os_name = factory.make_name('os')
396+ release_name = factory.make_name('release')
397+ factory.make_operating_system(
398+ name=os_name, release=release_name)
399+ release_name = "%s/%s" % (os_name, release_name)
400+ deploy_form = DeployForm()
401+ os_names = [
402+ name for name, title in deploy_form.fields['default_osystem'].choices]
403+ release_names = [
404+ name for name, title in deploy_form.fields['default_distro_series'].choices]
405+ self.assertIn(os_name, os_names)
406+ self.assertIn(release_name, release_names)
407+
408+ def test_accepts_new_values(self):
409+ os_name = 'ubuntu'
410+ release_name = factory.make_name('release')
411+ # XXX 2014-06-10 gmb:
412+ # We have to poke the release into the DISTRO_SERIES_CHOICES
413+ # dict, because otherwise it won't show up in the availble
414+ # releases on the DeployForm.
415+ ubuntu.DISTRO_SERIES_CHOICES[release_name] = release_name.title()
416+ factory.make_operating_system(name=os_name, release=release_name)
417+ params = {
418+ 'default_osystem': os_name,
419+ 'default_distro_series': "%s/%s" % (os_name, release_name),
420+ }
421+ form = DeployForm(data=params)
422+ self.assertTrue(form.is_valid())
423
424=== added file 'src/maasserver/utils/os.py'
425--- src/maasserver/utils/os.py 1970-01-01 00:00:00 +0000
426+++ src/maasserver/utils/os.py 2014-06-11 09:53:56 +0000
427@@ -0,0 +1,126 @@
428+# Copyright 2014 Canonical Ltd. This software is licensed under the
429+# GNU Affero General Public License version 3 (see the file LICENSE).
430+"""Utilities for working with operating systems."""
431+
432+from __future__ import (
433+ absolute_import,
434+ print_function,
435+ unicode_literals,
436+ )
437+
438+str = None
439+
440+__metaclass__ = type
441+__all__ = [
442+ 'get_distro_series_inital',
443+ 'get_release_requires_key',
444+ 'list_all_usable_osystems',
445+ 'list_all_usable_releases',
446+ 'list_osystem_choices',
447+ 'list_release_choices',
448+ ]
449+
450+
451+from maasserver.models import (
452+ BootImage,
453+ NodeGroup,
454+ )
455+from provisioningserver.drivers.osystem import OperatingSystemRegistry
456+
457+
458+def list_all_usable_osystems():
459+ """Return all operating systems that can be used for nodes.
460+
461+ These are the operating systems for which any nodegroup has the boot images
462+ required to boot the node.
463+ """
464+ # The Node edit form offers all usable operating systems as options for the
465+ # osystem field. Not all of these may be available in the node's
466+ # nodegroup, but to represent that accurately in the UI would depend on
467+ # the currently selected nodegroup. Narrowing the options down further
468+ # would have to happen browser-side.
469+ osystems = set()
470+ for nodegroup in NodeGroup.objects.all():
471+ osystems = osystems.union(
472+ BootImage.objects.get_usable_osystems(nodegroup))
473+ osystems = [
474+ OperatingSystemRegistry[osystem] for osystem in osystems
475+ if osystem in OperatingSystemRegistry
476+ ]
477+ return sorted(osystems, key=lambda osystem: osystem.title)
478+
479+
480+def list_osystem_choices(osystems):
481+ """Return Django "choices" list for `osystem`."""
482+ choices = [('', 'Default OS')]
483+ choices += [
484+ (osystem.name, osystem.title)
485+ for osystem in osystems
486+ ]
487+ return choices
488+
489+
490+def list_all_usable_releases(osystems):
491+ """Return dictionary of usable `releases` for each opertaing system."""
492+ distro_series = {}
493+ nodegroups = list(NodeGroup.objects.all())
494+ for osystem in osystems:
495+ releases = set()
496+ for nodegroup in nodegroups:
497+ releases = releases.union(
498+ BootImage.objects.get_usable_releases(nodegroup, osystem.name))
499+ distro_series[osystem.name] = sorted(releases)
500+ return distro_series
501+
502+
503+def get_release_requires_key(osystem, release):
504+ """Return astrisk for any release that requires
505+ a license key.
506+
507+ This is used by the JS, to display the licese_key field.
508+ """
509+ if osystem.requires_license_key(release):
510+ return '*'
511+ return ''
512+
513+
514+def list_release_choices(releases):
515+ """Return Django "choices" list for `releases`."""
516+ choices = [('', 'Default OS Release')]
517+ for key, value in releases.items():
518+ osystem = OperatingSystemRegistry[key]
519+ options = osystem.format_release_choices(value)
520+ requires_key = get_release_requires_key(osystem, '')
521+ choices += [(
522+ '%s/%s' % (osystem.name, requires_key),
523+ 'Latest %s Release' % osystem.title
524+ )]
525+ choices += [(
526+ '%s/%s%s' % (
527+ osystem.name,
528+ name,
529+ get_release_requires_key(osystem, name)
530+ ),
531+ title
532+ )
533+ for name, title in options
534+ ]
535+ return choices
536+
537+
538+def get_distro_series_inital(instance):
539+ """Returns the distro_series initial value for the instance."""
540+ osystem = instance.osystem
541+ series = instance.distro_series
542+ os_obj = OperatingSystemRegistry.get_item(osystem)
543+ if os_obj is not None:
544+ key_required = get_release_requires_key(os_obj, series)
545+ else:
546+ key_required = ''
547+ if osystem is not None and osystem != '':
548+ if series is None:
549+ series = ''
550+ return '%s/%s%s' % (osystem, series, key_required)
551+ return None
552+
553+
554
555=== modified file 'src/maasserver/views/tests/test_settings.py'
556--- src/maasserver/views/tests/test_settings.py 2014-05-29 18:20:58 +0000
557+++ src/maasserver/views/tests/test_settings.py 2014-06-11 09:53:56 +0000
558@@ -154,9 +154,11 @@
559
560 def test_settings_deploy_POST(self):
561 self.client_log_in(as_admin=True)
562- new_osystem = factory.getRandomOS()
563+ osystem_name = factory.make_name('os')
564+ release_name = factory.make_name('release')
565+ new_osystem = factory.make_operating_system(
566+ name=osystem_name, release=release_name)
567 new_default_osystem = new_osystem.name
568- new_default_distro_series = factory.getRandomRelease(new_osystem)
569 response = self.client.post(
570 reverse('settings'),
571 get_prefixed_form_data(
572@@ -165,7 +167,7 @@
573 'default_osystem': new_default_osystem,
574 'default_distro_series': '%s/%s' % (
575 new_default_osystem,
576- new_default_distro_series
577+ release_name,
578 ),
579 }))
580
581@@ -173,7 +175,7 @@
582 self.assertEqual(
583 (
584 new_default_osystem,
585- new_default_distro_series,
586+ release_name,
587 ),
588 (
589 Config.objects.get_config('default_osystem'),
590
591=== modified file 'src/provisioningserver/boot/tests/test_tftppath.py'
592--- src/provisioningserver/boot/tests/test_tftppath.py 2014-06-10 14:45:14 +0000
593+++ src/provisioningserver/boot/tests/test_tftppath.py 2014-06-11 09:53:56 +0000
594@@ -33,7 +33,6 @@
595 locate_tftp_path,
596 )
597 from provisioningserver.drivers.osystem import (
598- OperatingSystem,
599 OperatingSystemRegistry,
600 )
601 from provisioningserver.import_images.boot_image_mapping import (
602@@ -48,6 +47,7 @@
603 make_boot_image_storage_params,
604 )
605 from provisioningserver.testing.config import set_tftp_root
606+from provisioningserver.testing.os import FakeOS
607 from testtools.matchers import (
608 Not,
609 StartsWith,
610@@ -55,40 +55,6 @@
611 from testtools.testcase import ExpectedException
612
613
614-class FakeOS(OperatingSystem):
615-
616- name = ""
617- title = ""
618-
619- def __init__(self, name, purpose, releases=None):
620- self.name = name
621- self.title = name
622- self.purpose = purpose
623- if releases is None:
624- self.fake_list = [
625- factory.getRandomString()
626- for _ in range(3)
627- ]
628- else:
629- self.fake_list = releases
630-
631- def get_boot_image_purposes(self, *args):
632- return self.purpose
633-
634- def get_supported_releases(self):
635- return self.fake_list
636-
637- def get_default_release(self):
638- return self.fake_list[0]
639-
640- def format_release_choices(self, releases):
641- return [
642- (release, release)
643- for release in releases
644- if release in self.fake_list
645- ]
646-
647-
648 def make_image(params, purpose, metadata=None):
649 """Describe an image as a dict similar to what `list_boot_images` returns.
650
651
652=== added file 'src/provisioningserver/testing/os.py'
653--- src/provisioningserver/testing/os.py 1970-01-01 00:00:00 +0000
654+++ src/provisioningserver/testing/os.py 2014-06-11 09:53:56 +0000
655@@ -0,0 +1,54 @@
656+# Copyright 2014 Canonical Ltd. This software is licensed under the
657+# GNU Affero General Public License version 3 (see the file LICENSE).
658+"""Utilities for testing operating systems-related code."""
659+
660+from __future__ import (
661+ absolute_import,
662+ print_function,
663+ unicode_literals,
664+ )
665+
666+str = None
667+
668+__metaclass__ = type
669+__all__ = [
670+ 'FakeOS',
671+ ]
672+
673+
674+from maastesting.factory import factory
675+from provisioningserver.drivers.osystem import OperatingSystem
676+
677+
678+class FakeOS(OperatingSystem):
679+
680+ name = ""
681+ title = ""
682+
683+ def __init__(self, name, purpose, releases=None):
684+ self.name = name
685+ self.title = name
686+ self.purpose = purpose
687+ if releases is None:
688+ self.fake_list = [
689+ factory.getRandomString()
690+ for _ in range(3)
691+ ]
692+ else:
693+ self.fake_list = releases
694+
695+ def get_boot_image_purposes(self, *args):
696+ return self.purpose
697+
698+ def get_supported_releases(self):
699+ return self.fake_list
700+
701+ def get_default_release(self):
702+ return self.fake_list[0]
703+
704+ def format_release_choices(self, releases):
705+ return [
706+ (release, release)
707+ for release in releases
708+ if release in self.fake_list
709+ ]