Merge lp:~blake-rouse/maas/osystem-preseed-cleanup into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Superseded
Proposed branch: lp:~blake-rouse/maas/osystem-preseed-cleanup
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 1796 lines (+141/-1132)
22 files modified
src/maasserver/api.py (+0/-43)
src/maasserver/compose_preseed.py (+8/-0)
src/maasserver/enum.py (+0/-33)
src/maasserver/forms.py (+0/-122)
src/maasserver/forms_settings.py (+0/-28)
src/maasserver/migrations/0077_add_osystem_to_node.py (+0/-264)
src/maasserver/models/bootsource.py (+8/-0)
src/maasserver/models/bootsourceselection.py (+10/-6)
src/maasserver/models/node.py (+0/-56)
src/maasserver/models/tests/test_node.py (+0/-36)
src/maasserver/static/js/node_add.js (+0/-19)
src/maasserver/static/js/os_distro_select.js (+0/-131)
src/maasserver/static/js/tests/test_os_distro_select.html (+0/-38)
src/maasserver/static/js/tests/test_os_distro_select.js (+0/-106)
src/maasserver/testing/factory.py (+3/-2)
src/maasserver/tests/test_api_boot_source_selections.py (+7/-7)
src/maasserver/tests/test_api_node.py (+12/-87)
src/maasserver/tests/test_api_pxeconfig.py (+0/-10)
src/maasserver/tests/test_compose_preseed.py (+15/-0)
src/maasserver/tests/test_forms.py (+66/-140)
src/maasserver/views/tests/test_settings.py (+0/-4)
src/provisioningserver/driver/__init__.py (+12/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/osystem-preseed-cleanup
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+217099@code.launchpad.net

This proposal supersedes a proposal from 2014-04-24.

This proposal has been superseded by a proposal from 2014-05-29.

Description of the change

This is the final change in the series of changes to allow MAAS to deploy other operating systems. As we have Windows and CentOS support coming soon this is needed to easily add new operating systems.

Removed the DISTRO_SERIES enums as they are no longer needed, all information comes from the OperatingSystemRegistry.

Added the ability for an operating system to compose its own preseed. This will be used for Windows, CentOS and other operating systems.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks for doing this work. It's good to see those old hard-coded enums go at last. Unfortunately there's that pervasive problem of the registries living in a different process, possibly on a different machine. We should probably have been a lot clearer about that.

This branch answers a question I asked on one of your other merge proposals: does OperatingSystem really need to be a base class? If it's going to have a method for generating preseed data, and if it really can't be addressed with simple text templating, then it probably does.

An uncomfortable number of dimensional axes are coming together in compose_preseed: commissioning vs. deployment, operating systems with and without preseed code, conventional installer or fastpath (or whatever install options some other OS might have in their place). Gotta nip that sort of burgeoning complexity in the bud, or it will eventually stifle further development. Fight it with fire because by nature it will just keep growing.

So, assuming that we really do need OS-specific preseed-generating code, I would look for a way to move the Ubuntu-specific parts into the Ubuntu OperatingSystem. Or if most-but-not-all operating systems can generate their preseeds using templates, delegate most operating systems' implementations to a single template-based helper. (If templates can reasonably do the job for all operating systems we have in mind, of course, just use templating and stick with generic code.)

The hasattr check is best avoided, I think. If you're going to rely on dynamic dispatch for this, go all the way. You can accommodate some kind of sensible default in the dynamic method's definition, but an unconditional code path is going to be more manageable than special-casing in the code. Conditionals are our profession's equivalent of moving parts: they introduce complexity and risk.

There also seems to be a tacit rule in there that the commissioning OS must be, or work like, Ubuntu. That is worth being very explicit about. This isn't just a series of "if" statements to make things work, it's defining how a node can or cannot be installed. The details of of how these conditions interact are crucial, and yet I don't see any tests covering things like “we generate the customary Ubuntu preseed for a commissioning node, even if it's to be deployed with a different OS.”

By the way, I do appreciate how this branch is small and focused and not thrown in with a large bag of other changes!

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

Removed the hasattr check to use a NotImplemented exception. Can you give this another review?

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks, this branch looks a lot simpler than I remember it! Could you add a note in the docstring for compose_preseed about the NotImplementedError and what it means? Similar to “:param …:” and “:return:” the format also supports “:raise …:”.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2014-05-29 18:21:27 +0000
3+++ src/maasserver/api.py 2014-05-29 18:21:27 +0000
4@@ -258,10 +258,6 @@
5 OperatingSystemRegistry,
6 )
7 from provisioningserver.kernel_opts import KernelParameters
8-from provisioningserver.driver import (
9- BOOT_IMAGE_PURPOSE,
10- OperatingSystemRegistry,
11- )
12 from provisioningserver.power_schema import UNKNOWN_POWER_TYPE
13 import simplejson as json
14
15@@ -422,11 +418,7 @@
16 operating system the node will use.
17 :type osystem: unicode
18 :param distro_series: If present, this parameter specifies the
19-<<<<<<< TREE
20 OS release the node will use.
21-=======
22- os relase the node will use.
23->>>>>>> MERGE-SOURCE
24 :type distro_series: unicode
25
26 Ideally we'd have MIME multipart and content-transfer-encoding etc.
27@@ -453,7 +445,6 @@
28 node = Node.objects.get_node_or_404(
29 system_id=system_id, user=request.user,
30 perm=NODE_PERMISSION.EDIT)
31-<<<<<<< TREE
32 Form = get_node_edit_form(request.user)
33 form = Form(instance=node)
34 form.set_distro_series(series=series)
35@@ -461,9 +452,6 @@
36 form.save()
37 else:
38 raise ValidationError(form.errors)
39-=======
40- node.set_osystem_and_distro_series(osystem, series)
41->>>>>>> MERGE-SOURCE
42 nodes = Node.objects.start_nodes(
43 [system_id], request.user, user_data=user_data)
44 if len(nodes) == 0:
45@@ -476,10 +464,6 @@
46 """Release a node. Opposite of `NodesHandler.acquire`."""
47 node = Node.objects.get_node_or_404(
48 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
49-<<<<<<< TREE
50-=======
51- node.set_osystem_and_distro_series(osystem='', series='')
52->>>>>>> MERGE-SOURCE
53 if node.status == NODE_STATUS.READY:
54 # Nothing to do. This may be a redundant retry, and the
55 # postcondition is achieved, so call this success.
56@@ -2429,7 +2413,6 @@
57 if node.should_use_traditional_installer():
58 return "install"
59 else:
60-<<<<<<< TREE
61 # Return normal install if the booting operating system
62 # doesn't support xinstall.
63 osystem_obj = OperatingSystemRegistry[osystem]
64@@ -2438,17 +2421,6 @@
65 if BOOT_IMAGE_PURPOSE.XINSTALL in purposes:
66 return "xinstall"
67 return "install"
68-=======
69- # Check that the booting operating system, actually supports
70- # fast-path installation. If it does not then, we need to
71- # return normal install.
72- osystem_obj = OperatingSystemRegistry[osystem]
73- purposes = osystem_obj.get_boot_image_purposes(
74- arch, subarch, series, label)
75- if BOOT_IMAGE_PURPOSE.XINSTALL in purposes:
76- return "xinstall"
77- return "install"
78->>>>>>> MERGE-SOURCE
79 else:
80 return "local" # TODO: Investigate.
81 else:
82@@ -2555,18 +2527,10 @@
83
84 subarch = get_optional_param(request.GET, 'subarch', 'generic')
85
86-<<<<<<< TREE
87 # Get the purpose, without a selected label
88 purpose = get_boot_purpose(
89 node, osystem, arch, subarch, series, label=None)
90
91-=======
92- # Get the purpose, checking that if node is using xinstall, the operating
93- # system actaully supports that mode. We pass None for the label, here
94- # because it is unknown which label will be used yet.
95- purpose = get_boot_purpose(node, osystem, arch, subarch, series, None)
96-
97->>>>>>> MERGE-SOURCE
98 # We use as our default label the label of the most recent image for
99 # the criteria we've assembled above. If there is no latest image
100 # (which should never happen in reality but may happen in tests), we
101@@ -2590,16 +2554,9 @@
102 subarch = latest_image.subarchitecture
103 label = get_optional_param(request.GET, 'label', latest_label)
104
105-<<<<<<< TREE
106 # Get the supported purpose with the boot label
107 purpose = get_boot_purpose(node, osystem, arch, subarch, series, label)
108
109-=======
110- # Now that we have the correct label, lets check the boot purpose again
111- # to make sure that the boot image with that label, is the correct purpose.
112- purpose = get_boot_purpose(node, osystem, arch, subarch, series, label)
113-
114->>>>>>> MERGE-SOURCE
115 if node is not None:
116 # We don't care if the kernel opts is from the global setting or a tag,
117 # just get the options
118
119=== modified file 'src/maasserver/compose_preseed.py'
120--- src/maasserver/compose_preseed.py 2013-10-18 09:54:17 +0000
121+++ src/maasserver/compose_preseed.py 2014-05-29 18:21:27 +0000
122@@ -20,6 +20,7 @@
123
124 from maasserver.enum import NODE_STATUS
125 from maasserver.utils import absolute_reverse
126+from provisioningserver.driver import OperatingSystemRegistry
127 import yaml
128
129
130@@ -104,6 +105,13 @@
131 if node.status == NODE_STATUS.COMMISSIONING:
132 return compose_commissioning_preseed(token, base_url)
133 else:
134+ osystem = OperatingSystemRegistry[node.get_osystem()]
135+ metadata_url = absolute_reverse('metadata', base_url=base_url)
136+ try:
137+ return osystem.compose_preseed(node, token, metadata_url)
138+ except NotImplementedError:
139+ pass
140+
141 if node.should_use_traditional_installer():
142 return compose_cloud_init_preseed(token, base_url)
143 else:
144
145=== modified file 'src/maasserver/enum.py'
146--- src/maasserver/enum.py 2014-03-28 16:36:32 +0000
147+++ src/maasserver/enum.py 2014-05-29 18:21:27 +0000
148@@ -13,7 +13,6 @@
149
150 __metaclass__ = type
151 __all__ = [
152- 'COMMISSIONING_DISTRO_SERIES_CHOICES',
153 'COMPONENT',
154 'NODEGROUP_STATUS',
155 'NODEGROUP_STATUS_CHOICES',
156@@ -25,8 +24,6 @@
157 'NODE_STATUS_CHOICES',
158 'NODE_STATUS_CHOICES_DICT',
159 'PRESEED_TYPE',
160- 'DISTRO_SERIES',
161- 'DISTRO_SERIES_CHOICES',
162 'USERDATA_TYPE',
163 ]
164
165@@ -86,36 +83,6 @@
166 NODE_STATUS_CHOICES_DICT = OrderedDict(NODE_STATUS_CHOICES)
167
168
169-class DISTRO_SERIES:
170- """List of supported ubuntu releases."""
171- #:
172- default = ''
173- #:
174- precise = 'precise'
175- #:
176- quantal = 'quantal'
177- #:
178- raring = 'raring'
179- #:
180- saucy = 'saucy'
181- #:
182- trusty = 'trusty'
183-
184-DISTRO_SERIES_CHOICES = (
185- (DISTRO_SERIES.default, 'Default Ubuntu Release'),
186- (DISTRO_SERIES.precise, 'Ubuntu 12.04 LTS "Precise Pangolin"'),
187- (DISTRO_SERIES.quantal, 'Ubuntu 12.10 "Quantal Quetzal"'),
188- (DISTRO_SERIES.raring, 'Ubuntu 13.04 "Raring Ringtail"'),
189- (DISTRO_SERIES.saucy, 'Ubuntu 13.10 "Saucy Salamander"'),
190- (DISTRO_SERIES.trusty, 'Ubuntu 14.04 LTS "Trusty Tahr"'),
191-)
192-
193-
194-COMMISSIONING_DISTRO_SERIES_CHOICES = (
195- (DISTRO_SERIES.trusty, dict(DISTRO_SERIES_CHOICES)[DISTRO_SERIES.trusty]),
196-)
197-
198-
199 class NODE_PERMISSION:
200 """Permissions relating to nodes."""
201 VIEW = 'view_node'
202
203=== modified file 'src/maasserver/forms.py'
204--- src/maasserver/forms.py 2014-05-29 18:21:27 +0000
205+++ src/maasserver/forms.py 2014-05-29 17:03:20 +0000
206@@ -85,10 +85,6 @@
207 from maasserver.forms_settings import (
208 CONFIG_ITEMS_KEYS,
209 get_config_field,
210-<<<<<<< TREE
211-=======
212- list_commisioning_choices,
213->>>>>>> MERGE-SOURCE
214 INVALID_SETTING_MSG_TEMPLATE,
215 list_commisioning_choices,
216 )
217@@ -219,7 +215,6 @@
218 return all_architectures[0]
219
220
221-<<<<<<< TREE
222 def list_all_usable_osystems():
223 """Return all operating systems that can be used for nodes.
224
225@@ -320,82 +315,6 @@
226 return None
227
228
229-=======
230-def list_all_usable_osystems():
231- """Return all operating systems that can be used for nodes.
232-
233- These are the operating systems for which any nodegroup has the boot images
234- required to boot the node.
235- """
236- # The Node edit form offers all usable operating systems as options for the
237- # osystem field. Not all of these may be available in the node's
238- # nodegroup, but to represent that accurately in the UI would depend on
239- # the currently selected nodegroup. Narrowing the options down further
240- # would have to happen browser-side.
241- osystems = set()
242- for nodegroup in NodeGroup.objects.all():
243- osystems = osystems.union(
244- BootImage.objects.get_usable_osystems(nodegroup))
245- osystems = [OperatingSystemRegistry[osystem] for osystem in osystems]
246- return sorted(osystems, key=lambda osystem: osystem.title)
247-
248-
249-def list_osystem_choices(osystems):
250- """Return Django "choices" list for `osystem`."""
251- choices = [('', 'Default OS')]
252- choices += [
253- (osystem.name, osystem.title)
254- for osystem in osystems
255- ]
256- return choices
257-
258-
259-def list_all_usable_releases(osystems):
260- """Return dictionary of usable `releases` for each opertaing system."""
261- distro_series = {}
262- nodegroups = list(NodeGroup.objects.all())
263- for osystem in osystems:
264- releases = set()
265- for nodegroup in nodegroups:
266- releases = releases.union(
267- BootImage.objects.get_usable_releases(nodegroup, osystem.name))
268- distro_series[osystem.name] = sorted(releases)
269- return distro_series
270-
271-
272-def list_release_choices(releases):
273- """Return Django "choices" list for `releases`."""
274- choices = [('', 'Default OS Release')]
275- for key, value in releases.items():
276- osystem = OperatingSystemRegistry[key]
277- options = osystem.format_release_choices(value)
278- choices += [(
279- '%s/' % osystem.name,
280- 'Latest %s Release' % osystem.title
281- )]
282- choices += [
283- ('%s/%s' % (osystem.name, name), title)
284- for name, title in options
285- ]
286- return choices
287-
288-
289-def clean_distro_series_field(form, field, os_field):
290- # distro_series field can be supplied the value os/release, that is the
291- # way the web UI provides the value.
292- new_distro_series = form.cleaned_data.get(field)
293- if new_distro_series is not None and '/' in new_distro_series:
294- os, release = new_distro_series.split('/', 1)
295- if 'os' in form.cleaned_data:
296- new_os = form.cleaned_data[os_field]
297- if os != new_os:
298- raise ValidationError(
299- "%s option does not match osystem option." % field)
300- return release
301- return new_distro_series
302-
303-
304->>>>>>> MERGE-SOURCE
305 class NodeForm(ModelForm):
306
307 def __init__(self, request=None, *args, **kwargs):
308@@ -428,7 +347,6 @@
309 choices=choices, required=True, initial=default_arch,
310 error_messages={'invalid_choice': invalid_arch_message})
311
312-<<<<<<< TREE
313 def set_up_osystem_and_distro_series_fields(self, instance):
314 """Create the `osystem` and `distro_series` fields.
315
316@@ -455,43 +373,6 @@
317 if instance is not None:
318 self.initial['distro_series'] = initial_value
319
320-=======
321- def set_up_osystem_and_distro_series_fields(self, instance):
322- """Create the `osystem` and `distro_series` fields.
323-
324- This needs to be done on the fly so that we can pass a dynamic list of
325- usable operating systems and distro_series.
326- """
327- osystems = list_all_usable_osystems()
328- releases = list_all_usable_releases(osystems)
329- choices = list_osystem_choices(osystems)
330- distro_choices = list_release_choices(releases)
331- invalid_osystem_message = compose_invalid_choice_text(
332- 'osystem', choices)
333- invalid_distro_series_message = compose_invalid_choice_text(
334- 'distro_series', distro_choices)
335- self.fields['osystem'] = forms.ChoiceField(
336- label="OS", choices=choices, required=False, initial='',
337- error_messages={'invalid_choice': invalid_osystem_message})
338- self.fields['distro_series'] = forms.ChoiceField(
339- label="Release", choices=distro_choices,
340- required=False, initial='',
341- error_messages={'invalid_choice': invalid_distro_series_message})
342-
343- # Set the initial for the distro_series so it is selected
344- # correctly in the gui.
345- if instance is not None:
346- osystem = instance.osystem
347- series = instance.distro_series
348- if osystem is not None and osystem != '':
349- if series is None:
350- series = ''
351- self.initial['distro_series'] = '%s/%s' % (
352- osystem,
353- series,
354- )
355-
356->>>>>>> MERGE-SOURCE
357 def clean_hostname(self):
358 # Don't allow the hostname to be changed if the node is
359 # currently allocated. Juju knows the node by its old name, so
360@@ -516,7 +397,6 @@
361 is_valid = False
362 return is_valid
363
364-<<<<<<< TREE
365 def set_distro_series(self, series=''):
366 """Sets the osystem and distro_series, from the provided
367 distro_series.
368@@ -536,8 +416,6 @@
369 else:
370 self.data['distro_series'] = series
371
372-=======
373->>>>>>> MERGE-SOURCE
374 hostname = forms.CharField(
375 label="Host name", required=False, help_text=(
376 "The FQDN (Fully Qualified Domain Name) is derived from the "
377
378=== modified file 'src/maasserver/forms_settings.py'
379--- src/maasserver/forms_settings.py 2014-05-29 18:21:27 +0000
380+++ src/maasserver/forms_settings.py 2014-05-29 16:59:04 +0000
381@@ -31,7 +31,6 @@
382 INVALID_URL_MESSAGE = "Enter a valid url (e.g. http://host.example.com)."
383
384
385-<<<<<<< TREE
386 def list_osystem_choices():
387 osystems = [osystem for _, osystem in OperatingSystemRegistry]
388 osystems = sorted(osystems, key=lambda osystem: osystem.title)
389@@ -59,33 +58,6 @@
390 releases = DEFAULT_OS.get_supported_commissioning_releases()
391 options = DEFAULT_OS.format_release_choices(releases)
392 return list(options)
393-=======
394-def list_osystem_choices():
395- return [
396- (osystem.name, osystem.title)
397- for _, osystem in OperatingSystemRegistry
398- ]
399-
400-
401-def list_release_choices():
402- osystems = [osystem for _, osystem in OperatingSystemRegistry]
403- choices = []
404- for osystem in osystems:
405- supported = sorted(osystem.get_supported_releases())
406- options = osystem.format_release_choices(supported)
407- options = [
408- ('%s/%s' % (osystem.name, name), title)
409- for name, title in options
410- ]
411- choices += options
412- return choices
413-
414-
415-def list_commisioning_choices():
416- releases = DEFAULT_OS.get_supported_commissioning_releases()
417- options = DEFAULT_OS.format_release_choices(releases)
418- return [(name, title) for name, title in options]
419->>>>>>> MERGE-SOURCE
420
421
422 CONFIG_ITEMS = {
423
424=== removed file 'src/maasserver/migrations/0077_add_osystem_to_node.py'
425--- src/maasserver/migrations/0077_add_osystem_to_node.py 2014-05-29 18:21:27 +0000
426+++ src/maasserver/migrations/0077_add_osystem_to_node.py 1970-01-01 00:00:00 +0000
427@@ -1,264 +0,0 @@
428-# -*- coding: utf-8 -*-
429-from south.utils import datetime_utils as datetime
430-from south.db import db
431-from south.v2 import SchemaMigration
432-from django.db import models
433-
434-
435-class Migration(SchemaMigration):
436-
437- def forwards(self, orm):
438- # Adding field 'Node.osystem'
439- db.add_column(u'maasserver_node', 'osystem',
440- self.gf('django.db.models.fields.CharField')(default=u'', max_length=20, null=True, blank=True),
441- keep_default=False)
442-
443-
444- def backwards(self, orm):
445- # Deleting field 'Node.osystem'
446- db.delete_column(u'maasserver_node', 'osystem')
447-
448-
449- models = {
450- u'auth.group': {
451- 'Meta': {'object_name': 'Group'},
452- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
453- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
454- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
455- },
456- u'auth.permission': {
457- 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
458- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
459- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
460- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
461- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
462- },
463- u'auth.user': {
464- 'Meta': {'object_name': 'User'},
465- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
466- 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
467- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
468- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
469- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
470- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
471- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
472- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
473- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
474- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
475- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
476- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
477- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
478- },
479- u'contenttypes.contenttype': {
480- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
481- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
482- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
483- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
484- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
485- },
486- u'maasserver.bootimage': {
487- 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
488- 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
489- 'created': ('django.db.models.fields.DateTimeField', [], {}),
490- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
491- 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
492- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
493- 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
494- 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
495- 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
496- 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
497- 'updated': ('django.db.models.fields.DateTimeField', [], {})
498- },
499- u'maasserver.bootsource': {
500- 'Meta': {'object_name': 'BootSource'},
501- 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
502- 'created': ('django.db.models.fields.DateTimeField', [], {}),
503- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
504- 'keyring_data': ('django.db.models.fields.BinaryField', [], {'blank': 'True'}),
505- 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
506- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
507- 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
508- },
509- u'maasserver.bootsourceselection': {
510- 'Meta': {'object_name': 'BootSourceSelection'},
511- 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
512- 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
513- 'created': ('django.db.models.fields.DateTimeField', [], {}),
514- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
515- 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
516- 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
517- 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
518- 'updated': ('django.db.models.fields.DateTimeField', [], {})
519- },
520- u'maasserver.componenterror': {
521- 'Meta': {'object_name': 'ComponentError'},
522- 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
523- 'created': ('django.db.models.fields.DateTimeField', [], {}),
524- 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
525- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
526- 'updated': ('django.db.models.fields.DateTimeField', [], {})
527- },
528- u'maasserver.config': {
529- 'Meta': {'object_name': 'Config'},
530- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
531- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
532- 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
533- },
534- u'maasserver.dhcplease': {
535- 'Meta': {'object_name': 'DHCPLease'},
536- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
537- 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
538- 'mac': ('maasserver.fields.MACAddressField', [], {}),
539- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
540- },
541- u'maasserver.downloadprogress': {
542- 'Meta': {'object_name': 'DownloadProgress'},
543- 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
544- 'created': ('django.db.models.fields.DateTimeField', [], {}),
545- 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
546- 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
547- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
548- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
549- 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
550- 'updated': ('django.db.models.fields.DateTimeField', [], {})
551- },
552- u'maasserver.filestorage': {
553- 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
554- 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
555- 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
556- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
557- 'key': ('django.db.models.fields.CharField', [], {'default': "u'9bbf01e4-cbbd-11e3-afb3-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}),
558- 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
559- },
560- u'maasserver.macaddress': {
561- 'Meta': {'object_name': 'MACAddress'},
562- 'created': ('django.db.models.fields.DateTimeField', [], {}),
563- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
564- 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
565- 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
566- 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
567- 'updated': ('django.db.models.fields.DateTimeField', [], {})
568- },
569- u'maasserver.network': {
570- 'Meta': {'object_name': 'Network'},
571- 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
572- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
573- 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
574- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
575- 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
576- 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
577- },
578- u'maasserver.node': {
579- 'Meta': {'object_name': 'Node'},
580- 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
581- 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
582- 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
583- 'created': ('django.db.models.fields.DateTimeField', [], {}),
584- 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
585- 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
586- 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
587- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
588- 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
589- 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
590- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
591- 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
592- 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
593- 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
594- 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
595- 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
596- 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
597- 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
598- 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-9bbde11a-cbbd-11e3-afb3-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}),
599- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
600- 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
601- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
602- 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
603- },
604- u'maasserver.nodegroup': {
605- 'Meta': {'object_name': 'NodeGroup'},
606- 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
607- 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
608- 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
609- 'created': ('django.db.models.fields.DateTimeField', [], {}),
610- 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
611- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
612- 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
613- 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
614- 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
615- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
616- 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
617- },
618- u'maasserver.nodegroupinterface': {
619- 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
620- 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
621- 'created': ('django.db.models.fields.DateTimeField', [], {}),
622- 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
623- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
624- 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
625- 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
626- 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
627- 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
628- 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
629- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
630- 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
631- 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
632- 'updated': ('django.db.models.fields.DateTimeField', [], {})
633- },
634- u'maasserver.sshkey': {
635- 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
636- 'created': ('django.db.models.fields.DateTimeField', [], {}),
637- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
638- 'key': ('django.db.models.fields.TextField', [], {}),
639- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
640- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
641- },
642- u'maasserver.tag': {
643- 'Meta': {'object_name': 'Tag'},
644- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
645- 'created': ('django.db.models.fields.DateTimeField', [], {}),
646- 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
647- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
648- 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
649- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
650- 'updated': ('django.db.models.fields.DateTimeField', [], {})
651- },
652- u'maasserver.userprofile': {
653- 'Meta': {'object_name': 'UserProfile'},
654- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
655- 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
656- },
657- u'maasserver.zone': {
658- 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
659- 'created': ('django.db.models.fields.DateTimeField', [], {}),
660- 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
661- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
662- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
663- 'updated': ('django.db.models.fields.DateTimeField', [], {})
664- },
665- u'piston.consumer': {
666- 'Meta': {'object_name': 'Consumer'},
667- 'description': ('django.db.models.fields.TextField', [], {}),
668- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
669- 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
670- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
671- 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
672- 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
673- 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
674- },
675- u'piston.token': {
676- 'Meta': {'object_name': 'Token'},
677- 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
678- 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
679- 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
680- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
681- 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
682- 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
683- 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
684- 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1398350097L'}),
685- 'token_type': ('django.db.models.fields.IntegerField', [], {}),
686- 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
687- 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
688- }
689- }
690-
691- complete_apps = ['maasserver']
692\ No newline at end of file
693
694=== modified file 'src/maasserver/models/bootsource.py'
695--- src/maasserver/models/bootsource.py 2014-05-22 13:42:52 +0000
696+++ src/maasserver/models/bootsource.py 2014-05-29 18:21:27 +0000
697@@ -29,6 +29,14 @@
698 from maasserver.fields import EditableBinaryField
699 from maasserver.models.cleansave import CleanSave
700 from maasserver.models.timestampedmodel import TimestampedModel
701+from provisioningserver.driver.os_ubuntu import UbuntuOS
702+
703+
704+def list_release_choices():
705+ """Return Django "choices" list for Ubuntu releases."""
706+ osystem = UbuntuOS()
707+ releases = osystem.get_supported_releases()
708+ return osystem.format_release_choices(releases)
709
710
711 class BootSource(CleanSave, TimestampedModel):
712
713=== modified file 'src/maasserver/models/bootsourceselection.py'
714--- src/maasserver/models/bootsourceselection.py 2014-05-16 11:41:18 +0000
715+++ src/maasserver/models/bootsourceselection.py 2014-05-29 18:21:27 +0000
716@@ -24,12 +24,16 @@
717 )
718 import djorm_pgarray.fields
719 from maasserver import DefaultMeta
720-from maasserver.enum import (
721- DISTRO_SERIES,
722- DISTRO_SERIES_CHOICES,
723- )
724 from maasserver.models.cleansave import CleanSave
725 from maasserver.models.timestampedmodel import TimestampedModel
726+from provisioningserver.driver.os_ubuntu import UbuntuOS
727+
728+
729+def list_release_choices():
730+ """Return Django "choices" list for Ubuntu releases."""
731+ osystem = UbuntuOS()
732+ releases = osystem.get_supported_releases()
733+ return osystem.format_release_choices(releases)
734
735
736 class BootSourceSelectionManager(Manager):
737@@ -47,8 +51,8 @@
738 boot_source = ForeignKey('maasserver.BootSource', blank=False)
739
740 release = CharField(
741- max_length=20, choices=DISTRO_SERIES_CHOICES, blank=True,
742- default=DISTRO_SERIES.default,
743+ max_length=20, choices=list_release_choices(), blank=True,
744+ default='',
745 help_text="The Ubuntu release for which to import resources.")
746
747 arches = djorm_pgarray.fields.ArrayField(dbtype="text")
748
749=== modified file 'src/maasserver/models/node.py'
750--- src/maasserver/models/node.py 2014-05-29 18:21:27 +0000
751+++ src/maasserver/models/node.py 2014-05-29 18:21:27 +0000
752@@ -508,23 +508,11 @@
753 owner = ForeignKey(
754 User, default=None, blank=True, null=True, editable=False)
755
756-<<<<<<< TREE
757 osystem = CharField(
758 max_length=20, blank=True, default='')
759
760-=======
761- osystem = CharField(
762- max_length=20, null=True, blank=True, default='',
763- validators=[validate_osystem])
764-
765->>>>>>> MERGE-SOURCE
766 distro_series = CharField(
767-<<<<<<< TREE
768 max_length=20, blank=True, default='')
769-=======
770- max_length=20, null=True, blank=True, default='',
771- validators=[validate_distro_series])
772->>>>>>> MERGE-SOURCE
773
774 architecture = CharField(max_length=31, blank=False)
775
776@@ -831,7 +819,6 @@
777 """The name of the queue for tasks specific to this node."""
778 return self.nodegroup.work_queue
779
780-<<<<<<< TREE
781 def get_osystem(self):
782 """Return the operating system to install that node."""
783 use_default_osystem = (self.osystem is None or self.osystem == '')
784@@ -840,39 +827,15 @@
785 else:
786 return self.osystem
787
788-=======
789- def get_osystem(self):
790- """Return the operating system to install that node."""
791- use_default_osystem = (
792- not self.osystem or
793- self.osystem == '')
794- if use_default_osystem:
795- return Config.objects.get_config('default_osystem')
796- else:
797- return self.osystem
798-
799->>>>>>> MERGE-SOURCE
800 def get_distro_series(self):
801 """Return the distro series to install that node."""
802-<<<<<<< TREE
803 use_default_osystem = (
804 self.osystem is None or
805 self.osystem == '')
806-=======
807- use_default_osystem = (
808- not self.osystem or
809- self.osystem == '')
810->>>>>>> MERGE-SOURCE
811 use_default_distro_series = (
812-<<<<<<< TREE
813 self.distro_series is None or
814 self.distro_series == '')
815 if use_default_osystem and use_default_distro_series:
816-=======
817- not self.distro_series or
818- self.distro_series == '')
819- if use_default_osystem and use_default_distro_series:
820->>>>>>> MERGE-SOURCE
821 return Config.objects.get_config('default_distro_series')
822 elif use_default_distro_series:
823 osystem = OperatingSystemRegistry[self.osystem]
824@@ -880,25 +843,6 @@
825 else:
826 return self.distro_series
827
828-<<<<<<< TREE
829-=======
830- def set_osystem(self, osystem=''):
831- """Set the operating system to install that node."""
832- self.osystem = osystem
833- self.save()
834-
835- def set_distro_series(self, series=''):
836- """Set the distro series to install that node."""
837- self.distro_series = series
838- self.save()
839-
840- def set_osystem_and_distro_series(self, osystem='', series=''):
841- """Set the oeprating system to install that node."""
842- self.osystem = osystem
843- self.distro_series = series
844- self.save()
845-
846->>>>>>> MERGE-SOURCE
847 def get_effective_power_parameters(self):
848 """Return effective power parameters, including any defaults."""
849 if self.power_parameters:
850
851=== modified file 'src/maasserver/models/tests/test_node.py'
852--- src/maasserver/models/tests/test_node.py 2014-05-29 18:21:27 +0000
853+++ src/maasserver/models/tests/test_node.py 2014-05-29 17:03:20 +0000
854@@ -269,7 +269,6 @@
855 offset += timedelta(1)
856 self.assertEqual(macs[0], node.get_primary_mac().mac_address)
857
858-<<<<<<< TREE
859 def test_get_osystem_returns_default_osystem(self):
860 node = factory.make_node(osystem='')
861 osystem = Config.objects.get_config('default_osystem')
862@@ -278,41 +277,6 @@
863 def test_get_distro_series_returns_default_series(self):
864 node = factory.make_node(distro_series='')
865 series = Config.objects.get_config('default_distro_series')
866-=======
867- def test_get_osystem_returns_default_osystem_and_series(self):
868- node = factory.make_node()
869- osystem = Config.objects.get_config('default_osystem')
870- series = Config.objects.get_config('default_distro_series')
871- self.assertEqual(osystem, node.get_osystem())
872- self.assertEqual(series, node.get_distro_series())
873-
874- def test_get_series_returns_default_for_osystem(self):
875- node = factory.make_node()
876- osystem = factory.getRandomOS()
877- series = osystem.get_default_release()
878- node.set_osystem(osystem.name)
879- self.assertEqual(series, node.get_distro_series())
880-
881- def test_set_get_osystem_returns_osystem(self):
882- osystem = factory.getRandomOS()
883- node = factory.make_node()
884- node.set_osystem(osystem.name)
885- self.assertEqual(osystem.name, node.get_osystem())
886-
887- def test_set_get_distro_series_returns_series(self):
888- osystem = factory.getRandomOS()
889- series = factory.getRandomRelease(osystem)
890- node = factory.make_node()
891- node.set_distro_series(series)
892->>>>>>> MERGE-SOURCE
893- self.assertEqual(series, node.get_distro_series())
894-
895- def test_set_get_osystem_and_distro_series_returns_valid(self):
896- osystem = factory.getRandomOS()
897- series = factory.getRandomRelease(osystem)
898- node = factory.make_node()
899- node.set_osystem_and_distro_series(osystem.name, series)
900- self.assertEqual(osystem.name, node.get_osystem())
901 self.assertEqual(series, node.get_distro_series())
902
903 def test_delete_node_deletes_related_mac(self):
904
905=== modified file 'src/maasserver/static/js/node_add.js'
906--- src/maasserver/static/js/node_add.js 2014-05-29 18:21:27 +0000
907+++ src/maasserver/static/js/node_add.js 2014-05-16 15:22:54 +0000
908@@ -234,7 +234,6 @@
909 },
910
911 /**
912-<<<<<<< TREE
913 * If the 'distro_series' field is present, link it to operating
914 * system field.
915 *
916@@ -251,24 +250,6 @@
917 },
918
919 /**
920-=======
921- * If the 'distro_series' field is present, setup the linked
922- * 'distro_series' field.
923- *
924- * @method setUpDistroSeriesField
925- */
926- setUpDistroSeriesField: function() {
927- if (Y.Lang.isValue(Y.one('#id_distro_series'))) {
928- var widget = new Y.maas.os_distro_select.OSReleaseWidget({
929- srcNode: '#id_distro_series'
930- });
931- widget.bindTo(Y.one('#id_osystem'), 'change');
932- widget.render();
933- }
934- },
935-
936- /**
937->>>>>>> MERGE-SOURCE
938 * If the 'power_type' field is present, setup the linked
939 * 'power_parameter' field.
940 *
941
942=== renamed file 'src/maasserver/static/js/os_distro_select.js.moved' => 'src/maasserver/static/js/os_distro_select.js'
943=== removed file 'src/maasserver/static/js/os_distro_select.js'
944--- src/maasserver/static/js/os_distro_select.js 2014-05-29 18:21:27 +0000
945+++ src/maasserver/static/js/os_distro_select.js 1970-01-01 00:00:00 +0000
946@@ -1,131 +0,0 @@
947-/* Copyright 2012-2014 Canonical Ltd. This software is licensed under the
948- * GNU Affero General Public License version 3 (see the file LICENSE).
949- *
950- * OS/Release seletion utilities.
951- *
952- * @module Y.maas.power_parameter
953- */
954-
955-YUI.add('maas.os_distro_select', function(Y) {
956-
957-Y.log('loading maas.os_distro_select');
958-var module = Y.namespace('maas.os_distro_select');
959-
960-// Only used to mockup io in tests.
961-module._io = new Y.IO();
962-
963-var OSReleaseWidget;
964-
965-/**
966- * A widget class used to have the content of a node's release <select>
967- * modified based on the selected operating system.
968- *
969- */
970-OSReleaseWidget = function() {
971- OSReleaseWidget.superclass.constructor.apply(this, arguments);
972-};
973-
974-OSReleaseWidget.NAME = 'os-release-widget';
975-
976-Y.extend(OSReleaseWidget, Y.Widget, {
977-
978- /**
979- * Initialize the widget.
980- * - cfg.srcNode is the node which will be updated when the selected
981- * value of the 'os node' will change.
982- * - cfg.osNode is the node containing a 'select' element. When
983- * the selected element will change, the srcNode HTML will be
984- * updated.
985- *
986- * @method initializer
987- */
988- initializer: function(cfg) {
989- this.initialSkip = true;
990- },
991-
992- /**
993- * Bind the widget to events (to name 'event_name') generated by the given
994- * 'osNode'.
995- *
996- * @method bindTo
997- */
998- bindTo: function(osNode, event_name) {
999- var self = this;
1000- Y.one(osNode).on(event_name, function(e) {
1001- var osValue = e.currentTarget.get('value');
1002- self.switchTo(osValue);
1003- });
1004- var osValue = Y.one(osNode).get('value');
1005- self.switchTo(osValue);
1006- },
1007-
1008- /**
1009- * React to a new value of the os node: update the HTML of
1010- * 'srcNode'.
1011- *
1012- * @method switchTo
1013- */
1014- switchTo: function(newOSValue) {
1015- var srcNode = this.get('srcNode');
1016- var options = srcNode.all('option');
1017- var selected = false;
1018- options.each(function(option) {
1019- var value = option.get('value');
1020- var split_value = value.split("/");
1021-
1022- // Only show the default option, if that
1023- // is the selected os option as well.
1024- if(newOSValue == '') {
1025- if(value == '') {
1026- option.removeClass('hidden');
1027- option.set('selected', 'selected');
1028- }
1029- else {
1030- option.addClass('hidden');
1031- }
1032- }
1033- else {
1034- if(split_value[0] == newOSValue) {
1035- option.removeClass('hidden');
1036- if(split_value[1] == '') {
1037- selected = true;
1038- option.set('selected', 'selected');
1039- }
1040- }
1041- else {
1042- option.addClass('hidden');
1043- }
1044- }
1045- });
1046-
1047- // See if this was the inital skip. As the following
1048- // should only be done, after the first load, as the
1049- // initial will already be selected correctly.
1050- if(this.initialSkip == true) {
1051- this.initialSkip = false;
1052- return;
1053- }
1054-
1055- // See if a selection was made, if not then we need
1056- // to select the first visible as a default is not
1057- // present.
1058- if(!selected) {
1059- var first_option = null;
1060- options.each(function(option) {
1061- if(!option.hasClass('hidden')) {
1062- if(first_option == null) {
1063- first_option = option;
1064- }
1065- }
1066- });
1067- if(first_option != null) {
1068- first_option.set('selected', 'selected');
1069- }
1070- }
1071- }
1072-});
1073-
1074-module.OSReleaseWidget = OSReleaseWidget;
1075-
1076-}, '0.1', {'requires': ['widget', 'io']}
1077-);
1078
1079=== renamed file 'src/maasserver/static/js/tests/test_os_distro_select.html.moved' => 'src/maasserver/static/js/tests/test_os_distro_select.html'
1080=== removed file 'src/maasserver/static/js/tests/test_os_distro_select.html'
1081--- src/maasserver/static/js/tests/test_os_distro_select.html 2014-05-29 18:21:27 +0000
1082+++ src/maasserver/static/js/tests/test_os_distro_select.html 1970-01-01 00:00:00 +0000
1083@@ -1,38 +0,0 @@
1084-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1085-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
1086- <head>
1087- <title>Test maas.os_distro_select</title>
1088-
1089- <!-- YUI and test setup -->
1090- <script type="text/javascript" src="../testing/yui_test_conf.js"></script>
1091- <script type="text/javascript" src="/usr/share/javascript/yui3/yui/yui.js"></script>
1092- <script type="text/javascript" src="../testing/testrunner.js"></script>
1093- <script type="text/javascript" src="../testing/testing.js"></script>
1094- <!-- The module under test -->
1095- <script type="text/javascript" src="../os_distro_select.js"></script>
1096- <!-- The test suite -->
1097- <script type="text/javascript" src="test_os_distro_select.js"></script>
1098- </head>
1099- <body>
1100- <span id="suite">maas.os_distro_select.tests</span>
1101- <script type="text/x-template" id="select_node">
1102- <select id="id_osystem">
1103- <option value="" selected="selected">Default OS</option>
1104- <option value="value1">Value1</option>
1105- <option value="value2">Value2</option>
1106- </select>
1107- </script>
1108- <script type="text/x-template" id="target_node">
1109- <select id="id_distro_series">
1110- <option value="" selected="selected">Default Release</option>
1111- <option value="value1/series1">Value1Series1</option>
1112- <option value="value1/series2">Value1Series2</option>
1113- <option value="value1/series3">Value1Series3</option>
1114- <option value="value2/series1">Value2Series1</option>
1115- <option value="value2/series2">Value2Series2</option>
1116- <option value="value2/series3">Value2Series3</option>
1117- </select>
1118- </script>
1119- <div id="placeholder"></div>
1120- </body>
1121-</html>
1122
1123=== renamed file 'src/maasserver/static/js/tests/test_os_distro_select.js.moved' => 'src/maasserver/static/js/tests/test_os_distro_select.js'
1124=== removed file 'src/maasserver/static/js/tests/test_os_distro_select.js'
1125--- src/maasserver/static/js/tests/test_os_distro_select.js 2014-05-29 18:21:27 +0000
1126+++ src/maasserver/static/js/tests/test_os_distro_select.js 1970-01-01 00:00:00 +0000
1127@@ -1,106 +0,0 @@
1128-/* Copyright 2012 Canonical Ltd. This software is licensed under the
1129- * GNU Affero General Public License version 3 (see the file LICENSE).
1130- */
1131-
1132-YUI({ useBrowserConsole: true }).add(
1133- 'maas.os_distro_select.tests', function(Y) {
1134-
1135-Y.log('loading maas.os_distro_select.tests');
1136-var namespace = Y.namespace('maas.os_distro_select.tests');
1137-
1138-var module = Y.maas.os_distro_select;
1139-var suite = new Y.Test.Suite("maas.os_distro_select Tests");
1140-
1141-var select_node_template = Y.one('#select_node').getContent();
1142-var target_node_template = Y.one('#target_node').getContent();
1143-
1144-suite.add(new Y.maas.testing.TestCase({
1145- name: 'test-os_distro_select',
1146-
1147- setUp: function () {
1148- Y.one('#placeholder').empty().append(
1149- Y.Node.create(select_node_template).append(
1150- Y.Node.create(target_node_template)));
1151- },
1152-
1153- testBindToDefaultShowsDefaultRelease: function() {
1154- var widget = new Y.maas.os_distro_select.OSReleaseWidget({
1155- srcNode: '#id_distro_series',
1156- });
1157- widget.bindTo(Y.one('#id_osystem'), 'change');
1158-
1159- var options = Y.one('#id_distro_series').all('option');
1160- options.each(function(option) {
1161- var value = option.get('value');
1162- if(value == '') {
1163- Y.Assert.isFalse(option.hasClass('hidden'));
1164- }
1165- else {
1166- Y.Assert.isTrue(option.hasClass('hidden'));
1167- }
1168- });
1169- },
1170-
1171- testNonDefaultShowsRelatedReleases: function() {
1172- var node = Y.one('#id_distro_series');
1173- node.append('<option value="value1/">Value1Default</option>');
1174-
1175- var widget = new Y.maas.os_distro_select.OSReleaseWidget({
1176- srcNode: '#id_distro_series',
1177- });
1178- widget.bindTo(Y.one('#id_osystem'), 'change');
1179-
1180- var newValue = 'value1';
1181- var select = Y.one('#id_osystem');
1182- select.set('value', newValue);
1183- select.simulate('change');
1184-
1185- var options = Y.one('#id_distro_series').all('option');
1186- options.each(function(option) {
1187- var value = option.get('value');
1188- if(value == '') {
1189- Y.Assert.isTrue(option.hasClass('hidden'));
1190- }
1191- else {
1192- var split_value = value.split('/');
1193- if(split_value[0] == newValue) {
1194- Y.Assert.isFalse(option.hasClass('hidden'));
1195- }
1196- else {
1197- Y.Assert.isTrue(option.hasClass('hidden'));
1198- }
1199- }
1200- });
1201-
1202- Y.Assert.areEqual(
1203- newValue + '/', Y.one('#id_distro_series').get('value'))
1204- },
1205-
1206- testInitialSkipOnFirstChange: function() {
1207- var newValue = 'value1/series1';
1208- var select = Y.one('#id_osystem');
1209- select.set('value', 'value1');
1210- select.simulate('change');
1211- select = Y.one('#id_distro_series');
1212- select.set('value', newValue);
1213- select.simulate('change');
1214-
1215- var widget = new Y.maas.os_distro_select.OSReleaseWidget({
1216- srcNode: '#id_distro_series',
1217- });
1218-
1219- Y.Assert.isTrue(widget.initialSkip);
1220- widget.bindTo(Y.one('#id_osystem'), 'change');
1221- Y.Assert.isFalse(widget.initialSkip);
1222-
1223- Y.Assert.areEqual(
1224- newValue, Y.one('#id_distro_series').get('value'))
1225- }
1226-
1227-}));
1228-
1229-namespace.suite = suite;
1230-
1231-}, '0.1', {'requires': [
1232- 'node-event-simulate', 'test', 'maas.testing', 'maas.os_distro_select']}
1233-);
1234
1235=== modified file 'src/maasserver/testing/factory.py'
1236--- src/maasserver/testing/factory.py 2014-05-29 17:03:20 +0000
1237+++ src/maasserver/testing/factory.py 2014-05-29 18:21:27 +0000
1238@@ -23,7 +23,6 @@
1239 from django.contrib.auth.models import User
1240 from maasserver.clusterrpc.power_parameters import get_power_types
1241 from maasserver.enum import (
1242- DISTRO_SERIES_CHOICES,
1243 NODE_STATUS,
1244 NODEGROUP_STATUS,
1245 NODEGROUPINTERFACE_MANAGEMENT,
1246@@ -62,6 +61,7 @@
1247 # XXX 2014-05-13 blake-rouse bug=1319143
1248 # Need to not import directly, use RPC to info from cluster.
1249 from provisioningserver.driver import OperatingSystemRegistry
1250+from provisioningserver.driver.os_ubuntu import UbuntuOS
1251
1252 # We have a limited number of public keys:
1253 # src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub
1254@@ -717,7 +717,8 @@
1255 if boot_source is None:
1256 boot_source = self.make_boot_source()
1257 if release is None:
1258- release = self.getRandomChoice(DISTRO_SERIES_CHOICES)
1259+ ubuntu_os = UbuntuOS()
1260+ release = self.getRandomRelease(ubuntu_os)
1261 if arches is None:
1262 arch_count = random.randint(1, 10)
1263 arches = [self.make_name("arch") for i in range(arch_count)]
1264
1265=== modified file 'src/maasserver/tests/test_api_boot_source_selections.py'
1266--- src/maasserver/tests/test_api_boot_source_selections.py 2014-05-27 13:53:21 +0000
1267+++ src/maasserver/tests/test_api_boot_source_selections.py 2014-05-29 18:21:27 +0000
1268@@ -19,11 +19,11 @@
1269
1270 from django.core.urlresolvers import reverse
1271 from maasserver.api import DISPLAYED_BOOTSOURCESELECTION_FIELDS
1272-from maasserver.enum import DISTRO_SERIES_CHOICES
1273 from maasserver.models import BootSourceSelection
1274 from maasserver.testing import reload_object
1275 from maasserver.testing.api import APITestCase
1276 from maasserver.testing.factory import factory
1277+from provisioningserver.driver.os_ubuntu import UbuntuOS
1278 from testtools.matchers import MatchesStructure
1279
1280
1281@@ -98,8 +98,8 @@
1282 def test_PUT_updates_boot_source_selection(self):
1283 self.become_admin()
1284 boot_source_selection = factory.make_boot_source_selection()
1285- new_release = factory.getRandomChoice(
1286- DISTRO_SERIES_CHOICES)
1287+ ubuntu_os = UbuntuOS()
1288+ new_release = factory.getRandomRelease(ubuntu_os)
1289 new_values = {
1290 'release': new_release,
1291 'arches': [factory.make_name('arch'), factory.make_name('arch')],
1292@@ -160,8 +160,8 @@
1293 def test_POST_creates_boot_source_selection(self):
1294 self.become_admin()
1295 boot_source = factory.make_boot_source()
1296- new_release = factory.getRandomChoice(
1297- DISTRO_SERIES_CHOICES)
1298+ ubuntu_os = UbuntuOS()
1299+ new_release = factory.getRandomRelease(ubuntu_os)
1300 params = {
1301 'release': new_release,
1302 'arches': [factory.make_name('arch'), factory.make_name('arch')],
1303@@ -182,8 +182,8 @@
1304
1305 def test_POST_requires_admin(self):
1306 boot_source = factory.make_boot_source()
1307- new_release = factory.getRandomChoice(
1308- DISTRO_SERIES_CHOICES)
1309+ ubuntu_os = UbuntuOS()
1310+ new_release = factory.getRandomRelease(ubuntu_os)
1311 params = {
1312 'release': new_release,
1313 'arches': [factory.make_name('arch'), factory.make_name('arch')],
1314
1315=== modified file 'src/maasserver/tests/test_api_node.py'
1316--- src/maasserver/tests/test_api_node.py 2014-05-29 18:21:27 +0000
1317+++ src/maasserver/tests/test_api_node.py 2014-05-28 19:06:28 +0000
1318@@ -48,7 +48,6 @@
1319 NodeUserData,
1320 )
1321 from metadataserver.nodeinituser import get_node_init_user
1322-from provisioningserver.driver.os_ubuntu import UbuntuOS
1323
1324
1325 class NodeAnonAPITest(MAASServerTestCase):
1326@@ -222,7 +221,6 @@
1327 self.assertEqual(
1328 node.system_id, json.loads(response.content)['system_id'])
1329
1330-<<<<<<< TREE
1331 def test_POST_start_sets_osystem_and_distro_series(self):
1332 node = factory.make_node(
1333 owner=self.logged_in_user, mac=True,
1334@@ -230,91 +228,18 @@
1335 architecture=make_usable_architecture(self))
1336 osystem = make_usable_osystem(self)
1337 distro_series = factory.getRandomRelease(osystem)
1338-=======
1339- def test_POST_start_sets_osystem(self):
1340- node = factory.make_node(
1341- owner=self.logged_in_user, mac=True,
1342- power_type='ether_wake')
1343- osystem = factory.getRandomOS()
1344- response = self.client.post(
1345- self.get_node_uri(node),
1346- {'op': 'start', 'osystem': osystem.name})
1347- self.assertEqual(
1348- (httplib.OK, node.system_id),
1349- (response.status_code, json.loads(response.content)['system_id']))
1350- self.assertEqual(
1351- osystem.name, reload_object(node).osystem)
1352-
1353- def test_POST_start_accepts_os(self):
1354- node = factory.make_node(
1355- owner=self.logged_in_user, mac=True,
1356- power_type='ether_wake')
1357- osystem = factory.getRandomOS()
1358- response = self.client.post(
1359- self.get_node_uri(node),
1360- {'op': 'start', 'os': osystem.name})
1361- self.assertEqual(
1362- (httplib.OK, node.system_id),
1363- (response.status_code, json.loads(response.content)['system_id']))
1364- self.assertEqual(
1365- osystem.name, reload_object(node).osystem)
1366-
1367- def test_POST_start_sets_osystem_and_distro_series(self):
1368- node = factory.make_node(
1369- owner=self.logged_in_user, mac=True,
1370- power_type='ether_wake')
1371- osystem = factory.getRandomOS()
1372- distro_series = factory.getRandomRelease(osystem)
1373- response = self.client.post(
1374- self.get_node_uri(node), {
1375- 'op': 'start',
1376- 'osystem': osystem.name,
1377- 'distro_series': distro_series
1378- })
1379- self.assertEqual(
1380- (httplib.OK, node.system_id),
1381- (response.status_code, json.loads(response.content)['system_id']))
1382- self.assertEqual(
1383- osystem.name, reload_object(node).osystem)
1384- self.assertEqual(
1385- distro_series, reload_object(node).distro_series)
1386-
1387- def test_POST_start_sets_distro_series_defaults_ubuntu(self):
1388- node = factory.make_node(
1389- owner=self.logged_in_user, mac=True,
1390- power_type='ether_wake')
1391- osystem = UbuntuOS()
1392- distro_series = factory.getRandomRelease(osystem)
1393->>>>>>> MERGE-SOURCE
1394- response = self.client.post(
1395- self.get_node_uri(node), {
1396- 'op': 'start',
1397- 'distro_series': distro_series
1398- })
1399- self.assertEqual(
1400- (httplib.OK, node.system_id),
1401- (response.status_code, json.loads(response.content)['system_id']))
1402- self.assertEqual(
1403- osystem.name, reload_object(node).osystem)
1404- self.assertEqual(
1405- distro_series, reload_object(node).distro_series)
1406-
1407- def test_POST_start_validates_osystem(self):
1408- node = factory.make_node(
1409- owner=self.logged_in_user, mac=True,
1410- power_type='ether_wake')
1411- invalid_osystem = factory.getRandomString()
1412- response = self.client.post(
1413- self.get_node_uri(node),
1414- {'op': 'start', 'osystem': invalid_osystem})
1415- self.assertEqual(
1416- (
1417- httplib.BAD_REQUEST,
1418- {'osystem': [
1419- "Value u'%s' is not a valid choice." %
1420- invalid_osystem]}
1421- ),
1422- (response.status_code, json.loads(response.content)))
1423+ response = self.client.post(
1424+ self.get_node_uri(node), {
1425+ 'op': 'start',
1426+ 'distro_series': distro_series
1427+ })
1428+ self.assertEqual(
1429+ (httplib.OK, node.system_id),
1430+ (response.status_code, json.loads(response.content)['system_id']))
1431+ self.assertEqual(
1432+ osystem.name, reload_object(node).osystem)
1433+ self.assertEqual(
1434+ distro_series, reload_object(node).distro_series)
1435
1436 def test_POST_start_validates_distro_series(self):
1437 node = factory.make_node(
1438
1439=== modified file 'src/maasserver/tests/test_api_pxeconfig.py'
1440--- src/maasserver/tests/test_api_pxeconfig.py 2014-05-29 18:21:27 +0000
1441+++ src/maasserver/tests/test_api_pxeconfig.py 2014-05-29 17:03:20 +0000
1442@@ -310,7 +310,6 @@
1443 node.use_fastpath_installer()
1444 for name, value in parameters.items():
1445 setattr(node, name, value)
1446-<<<<<<< TREE
1447 osystem = node.get_osystem()
1448 series = node.get_distro_series()
1449 arch, subarch = node.architecture.split('/')
1450@@ -334,15 +333,6 @@
1451 'install',
1452 api.get_boot_purpose(
1453 node, node_os, arch, subarch, node_series, None))
1454-=======
1455- osystem = node.get_osystem()
1456- series = node.get_distro_series()
1457- arch, subarch = node.architecture.split('/')
1458- self.assertEqual(
1459- purpose,
1460- api.get_boot_purpose(
1461- node, osystem, arch, subarch, series, None))
1462->>>>>>> MERGE-SOURCE
1463
1464 def test_pxeconfig_uses_boot_purpose(self):
1465 fake_boot_purpose = factory.make_name("purpose")
1466
1467=== modified file 'src/maasserver/tests/test_compose_preseed.py'
1468--- src/maasserver/tests/test_compose_preseed.py 2013-10-07 09:12:40 +0000
1469+++ src/maasserver/tests/test_compose_preseed.py 2014-05-29 18:21:27 +0000
1470@@ -17,8 +17,10 @@
1471 from maasserver.compose_preseed import compose_preseed
1472 from maasserver.enum import NODE_STATUS
1473 from maasserver.testing.factory import factory
1474+from maasserver.testing.osystems import make_usable_osystem
1475 from maasserver.testing.testcase import MAASServerTestCase
1476 from maasserver.utils import absolute_reverse
1477+from maastesting.matchers import MockCalledOnceWith
1478 from metadataserver.models import NodeKey
1479 from testtools.matchers import (
1480 KeysEqual,
1481@@ -110,3 +112,16 @@
1482 self.assertEqual(
1483 absolute_reverse('curtin-metadata'),
1484 preseed['datasource']['MAAS']['metadata_url'])
1485+
1486+ def test_compose_preseed_with_osystem_compose_preseed(self):
1487+ osystem = make_usable_osystem(self)
1488+ mock_compose = self.patch(osystem, 'compose_preseed')
1489+ node = factory.make_node(
1490+ osystem=osystem.name, status=NODE_STATUS.READY)
1491+
1492+ token = NodeKey.objects.get_token_for_node(node)
1493+ url = absolute_reverse('metadata')
1494+ compose_preseed(node)
1495+ self.assertThat(
1496+ mock_compose,
1497+ MockCalledOnceWith(node, token, url))
1498
1499=== modified file 'src/maasserver/tests/test_forms.py'
1500--- src/maasserver/tests/test_forms.py 2014-05-29 18:21:27 +0000
1501+++ src/maasserver/tests/test_forms.py 2014-05-29 18:21:27 +0000
1502@@ -29,7 +29,6 @@
1503 from django.http import QueryDict
1504 from maasserver.clusterrpc.power_parameters import get_power_type_choices
1505 from maasserver.enum import (
1506- DISTRO_SERIES_CHOICES,
1507 NODE_STATUS,
1508 NODEGROUP_STATUS,
1509 NODEGROUPINTERFACE_MANAGEMENT,
1510@@ -99,11 +98,6 @@
1511 make_usable_architecture,
1512 patch_usable_architectures,
1513 )
1514-from maasserver.testing.osystems import (
1515- make_osystem_with_releases,
1516- make_usable_osystem,
1517- patch_usable_osystems,
1518- )
1519 from maasserver.testing.factory import factory
1520 from maasserver.testing.osystems import (
1521 make_osystem_with_releases,
1522@@ -117,6 +111,7 @@
1523 from metadataserver.models import CommissioningScript
1524 from netaddr import IPNetwork
1525 from provisioningserver import tasks
1526+from provisioningserver.driver.os_ubuntu import UbuntuOS
1527 from testtools import TestCase
1528 from testtools.matchers import (
1529 AllMatch,
1530@@ -153,14 +148,9 @@
1531 release = factory.make_name('release')
1532 for purpose in ['install', 'commissioning']:
1533 factory.make_boot_image(
1534-<<<<<<< TREE
1535 nodegroup=nodegroup, osystem=osystem, architecture=arch,
1536 subarchitecture=subarchitecture, release=release,
1537 purpose=purpose)
1538-=======
1539- nodegroup=nodegroup, osystem=osystem, architecture=arch,
1540- subarchitecture=subarchitecture, release=release, purpose=purpose)
1541->>>>>>> MERGE-SOURCE
1542
1543 def test_initialize_node_group_leaves_nodegroup_reference_intact(self):
1544 preselected_nodegroup = factory.make_node_group()
1545@@ -223,131 +213,67 @@
1546 arches = [factory.make_name('arch') for _ in range(5)]
1547 self.assertEqual(arches[0], pick_default_architecture(arches))
1548
1549-<<<<<<< TREE
1550- def test_list_all_usable_osystems_combines_nodegroups(self):
1551- osystem_names = [factory.make_name('os') for _ in range(3)]
1552- expected = []
1553- for name in osystem_names:
1554- self.make_usable_boot_images(osystem=name)
1555- expected.append(make_usable_osystem(self, name))
1556- self.assertItemsEqual(expected, list_all_usable_osystems())
1557-
1558- def test_list_all_usable_osystems_sorts_output(self):
1559- osystem_names = [factory.make_name('os') for _ in range(3)]
1560- expected = []
1561- for name in osystem_names:
1562- self.make_usable_boot_images(osystem=name)
1563- expected.append(make_usable_osystem(self, name))
1564- expected = sorted(expected, key=lambda osystem: osystem.title)
1565- self.assertEqual(expected, list_all_usable_osystems())
1566-
1567- def test_list_all_usable_osystems_returns_no_duplicates(self):
1568- os_name = factory.make_name('os')
1569- self.make_usable_boot_images(osystem=os_name)
1570- self.make_usable_boot_images(osystem=os_name)
1571- osystem = make_usable_osystem(self, os_name)
1572- self.assertEqual(
1573- [osystem], list_all_usable_osystems())
1574-
1575- def test_list_all_usable_releases_combines_nodegroups(self):
1576- expected = {}
1577- osystems = []
1578- os_names = [factory.make_name('os') for _ in range(3)]
1579- for name in os_names:
1580- releases = [factory.make_name('release') for _ in range(3)]
1581- for release in releases:
1582- self.make_usable_boot_images(osystem=name, release=release)
1583- osystems.append(
1584- make_usable_osystem(self, name, releases=releases))
1585- expected[name] = releases
1586- self.assertItemsEqual(expected, list_all_usable_releases(osystems))
1587-
1588- def test_list_all_usable_releases_sorts_output(self):
1589- expected = {}
1590- osystems = []
1591- os_names = [factory.make_name('os') for _ in range(3)]
1592- for name in os_names:
1593- releases = [factory.make_name('release') for _ in range(3)]
1594- for release in releases:
1595- self.make_usable_boot_images(osystem=name, release=release)
1596- osystems.append(
1597- make_usable_osystem(self, name, releases=releases))
1598- expected[name] = sorted(releases)
1599- self.assertEqual(expected, list_all_usable_releases(osystems))
1600-
1601- def test_list_all_usable_releases_returns_no_duplicates(self):
1602- os_name = factory.make_name('os')
1603- release = factory.make_name('release')
1604- self.make_usable_boot_images(osystem=os_name, release=release)
1605- self.make_usable_boot_images(osystem=os_name, release=release)
1606- osystem = make_usable_osystem(self, os_name, releases=[release])
1607- expected = {}
1608- expected[os_name] = [release]
1609- self.assertEqual(expected, list_all_usable_releases([osystem]))
1610-
1611-=======
1612- def test_list_all_usable_osystems_combines_nodegroups(self):
1613- osystem_names = [factory.make_name('os') for _ in range(3)]
1614- expected = []
1615- for name in osystem_names:
1616- self.make_usable_boot_images(osystem=name)
1617- expected.append(make_usable_osystem(self, name))
1618- self.assertItemsEqual(expected, list_all_usable_osystems())
1619-
1620- def test_list_all_usable_osystems_sorts_output(self):
1621- osystem_names = [factory.make_name('os') for _ in range(3)]
1622- expected = []
1623- for name in osystem_names:
1624- self.make_usable_boot_images(osystem=name)
1625- expected.append(make_usable_osystem(self, name))
1626- expected = sorted(expected, key=lambda osystem: osystem.title)
1627- self.assertEqual(expected, list_all_usable_osystems())
1628-
1629- def test_list_all_usable_osystems_returns_no_duplicates(self):
1630- os_name = factory.make_name('os')
1631- self.make_usable_boot_images(osystem=os_name)
1632- self.make_usable_boot_images(osystem=os_name)
1633- osystem = make_usable_osystem(self, os_name)
1634- self.assertEqual(
1635- [osystem], list_all_usable_osystems())
1636-
1637- def test_list_all_usable_releases_combines_nodegroups(self):
1638- expected = {}
1639- osystems = []
1640- os_names = [factory.make_name('os') for _ in range(3)]
1641- for name in os_names:
1642- releases = [factory.make_name('release') for _ in range(3)]
1643- for release in releases:
1644- self.make_usable_boot_images(osystem=name, release=release)
1645- osystems.append(
1646- make_usable_osystem(self, name, releases=releases))
1647- expected[name] = releases
1648- self.assertItemsEqual(expected, list_all_usable_releases(osystems))
1649-
1650- def test_list_all_usable_releases_sorts_output(self):
1651- expected = {}
1652- osystems = []
1653- os_names = [factory.make_name('os') for _ in range(3)]
1654- for name in os_names:
1655- releases = [factory.make_name('release') for _ in range(3)]
1656- for release in releases:
1657- self.make_usable_boot_images(osystem=name, release=release)
1658- osystems.append(
1659- make_usable_osystem(self, name, releases=releases))
1660- expected[name] = sorted(releases)
1661- self.assertEqual(expected, list_all_usable_releases(osystems))
1662-
1663- def test_list_all_usable_releases_returns_no_duplicates(self):
1664- os_name = factory.make_name('os')
1665- release = factory.make_name('release')
1666- self.make_usable_boot_images(osystem=os_name, release=release)
1667- self.make_usable_boot_images(osystem=os_name, release=release)
1668- osystem = make_usable_osystem(self, os_name, releases=[release])
1669- expected = {}
1670- expected[os_name] = [release]
1671- self.assertEqual(expected, list_all_usable_releases([osystem]))
1672-
1673->>>>>>> MERGE-SOURCE
1674+ def test_list_all_usable_osystems_combines_nodegroups(self):
1675+ osystem_names = [factory.make_name('os') for _ in range(3)]
1676+ expected = []
1677+ for name in osystem_names:
1678+ self.make_usable_boot_images(osystem=name)
1679+ expected.append(make_usable_osystem(self, name))
1680+ self.assertItemsEqual(expected, list_all_usable_osystems())
1681+
1682+ def test_list_all_usable_osystems_sorts_output(self):
1683+ osystem_names = [factory.make_name('os') for _ in range(3)]
1684+ expected = []
1685+ for name in osystem_names:
1686+ self.make_usable_boot_images(osystem=name)
1687+ expected.append(make_usable_osystem(self, name))
1688+ expected = sorted(expected, key=lambda osystem: osystem.title)
1689+ self.assertEqual(expected, list_all_usable_osystems())
1690+
1691+ def test_list_all_usable_osystems_returns_no_duplicates(self):
1692+ os_name = factory.make_name('os')
1693+ self.make_usable_boot_images(osystem=os_name)
1694+ self.make_usable_boot_images(osystem=os_name)
1695+ osystem = make_usable_osystem(self, os_name)
1696+ self.assertEqual(
1697+ [osystem], list_all_usable_osystems())
1698+
1699+ def test_list_all_usable_releases_combines_nodegroups(self):
1700+ expected = {}
1701+ osystems = []
1702+ os_names = [factory.make_name('os') for _ in range(3)]
1703+ for name in os_names:
1704+ releases = [factory.make_name('release') for _ in range(3)]
1705+ for release in releases:
1706+ self.make_usable_boot_images(osystem=name, release=release)
1707+ osystems.append(
1708+ make_usable_osystem(self, name, releases=releases))
1709+ expected[name] = releases
1710+ self.assertItemsEqual(expected, list_all_usable_releases(osystems))
1711+
1712+ def test_list_all_usable_releases_sorts_output(self):
1713+ expected = {}
1714+ osystems = []
1715+ os_names = [factory.make_name('os') for _ in range(3)]
1716+ for name in os_names:
1717+ releases = [factory.make_name('release') for _ in range(3)]
1718+ for release in releases:
1719+ self.make_usable_boot_images(osystem=name, release=release)
1720+ osystems.append(
1721+ make_usable_osystem(self, name, releases=releases))
1722+ expected[name] = sorted(releases)
1723+ self.assertEqual(expected, list_all_usable_releases(osystems))
1724+
1725+ def test_list_all_usable_releases_returns_no_duplicates(self):
1726+ os_name = factory.make_name('os')
1727+ release = factory.make_name('release')
1728+ self.make_usable_boot_images(osystem=os_name, release=release)
1729+ self.make_usable_boot_images(osystem=os_name, release=release)
1730+ osystem = make_usable_osystem(self, os_name, releases=[release])
1731+ expected = {}
1732+ expected[os_name] = [release]
1733+ self.assertEqual(expected, list_all_usable_releases([osystem]))
1734+
1735 def test_remove_None_values_removes_None_values_in_dict(self):
1736 random_input = factory.getRandomString()
1737 self.assertEqual(
1738@@ -2136,8 +2062,8 @@
1739
1740 def test_edits_boot_source_selection_object(self):
1741 boot_source_selection = factory.make_boot_source_selection()
1742- new_release = factory.getRandomChoice(
1743- DISTRO_SERIES_CHOICES)
1744+ ubuntu_os = UbuntuOS()
1745+ new_release = factory.getRandomRelease(ubuntu_os)
1746 params = {
1747 'release': new_release,
1748 'arches': [factory.make_name('arch'), factory.make_name('arch')],
1749@@ -2154,8 +2080,8 @@
1750
1751 def test_creates_boot_source_selection_object(self):
1752 boot_source = factory.make_boot_source()
1753- new_release = factory.getRandomChoice(
1754- DISTRO_SERIES_CHOICES)
1755+ ubuntu_os = UbuntuOS()
1756+ new_release = factory.getRandomRelease(ubuntu_os)
1757 params = {
1758 'release': new_release,
1759 'arches': [factory.make_name('arch'), factory.make_name('arch')],
1760
1761=== modified file 'src/maasserver/views/tests/test_settings.py'
1762--- src/maasserver/views/tests/test_settings.py 2014-05-29 18:21:27 +0000
1763+++ src/maasserver/views/tests/test_settings.py 2014-05-15 19:48:27 +0000
1764@@ -20,10 +20,6 @@
1765 from django.contrib.auth.models import User
1766 from django.core.urlresolvers import reverse
1767 from lxml.html import fromstring
1768-<<<<<<< TREE
1769-=======
1770-from maasserver.models.config import DEFAULT_OS
1771->>>>>>> MERGE-SOURCE
1772 from maasserver.models import (
1773 Config,
1774 UserProfile,
1775
1776=== modified file 'src/provisioningserver/driver/__init__.py'
1777--- src/provisioningserver/driver/__init__.py 2014-05-15 17:27:53 +0000
1778+++ src/provisioningserver/driver/__init__.py 2014-05-29 18:21:27 +0000
1779@@ -158,6 +158,18 @@
1780 :returns: list of supported purposes
1781 """
1782
1783+ def compose_preseed(self, node, token, metadata_url):
1784+ """Composes the preseed for the given node.
1785+
1786+ :param node: Node preseed needs generating.
1787+ :param token: OAuth token for url.
1788+ :param metadata_url: Metdata url for node.
1789+ :returns: Preseed for node.
1790+ :raise:
1791+ NotImplementedError: doesn't implement a custom preseed
1792+ """
1793+ raise NotImplementedError()
1794+
1795
1796 class HardwareDiscoverContext:
1797