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
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/api.py 2014-05-29 18:21:27 +0000
@@ -258,10 +258,6 @@
258 OperatingSystemRegistry,258 OperatingSystemRegistry,
259 )259 )
260from provisioningserver.kernel_opts import KernelParameters260from provisioningserver.kernel_opts import KernelParameters
261from provisioningserver.driver import (
262 BOOT_IMAGE_PURPOSE,
263 OperatingSystemRegistry,
264 )
265from provisioningserver.power_schema import UNKNOWN_POWER_TYPE261from provisioningserver.power_schema import UNKNOWN_POWER_TYPE
266import simplejson as json262import simplejson as json
267263
@@ -422,11 +418,7 @@
422 operating system the node will use.418 operating system the node will use.
423 :type osystem: unicode419 :type osystem: unicode
424 :param distro_series: If present, this parameter specifies the420 :param distro_series: If present, this parameter specifies the
425<<<<<<< TREE
426 OS release the node will use.421 OS release the node will use.
427=======
428 os relase the node will use.
429>>>>>>> MERGE-SOURCE
430 :type distro_series: unicode422 :type distro_series: unicode
431423
432 Ideally we'd have MIME multipart and content-transfer-encoding etc.424 Ideally we'd have MIME multipart and content-transfer-encoding etc.
@@ -453,7 +445,6 @@
453 node = Node.objects.get_node_or_404(445 node = Node.objects.get_node_or_404(
454 system_id=system_id, user=request.user,446 system_id=system_id, user=request.user,
455 perm=NODE_PERMISSION.EDIT)447 perm=NODE_PERMISSION.EDIT)
456<<<<<<< TREE
457 Form = get_node_edit_form(request.user)448 Form = get_node_edit_form(request.user)
458 form = Form(instance=node)449 form = Form(instance=node)
459 form.set_distro_series(series=series)450 form.set_distro_series(series=series)
@@ -461,9 +452,6 @@
461 form.save()452 form.save()
462 else:453 else:
463 raise ValidationError(form.errors)454 raise ValidationError(form.errors)
464=======
465 node.set_osystem_and_distro_series(osystem, series)
466>>>>>>> MERGE-SOURCE
467 nodes = Node.objects.start_nodes(455 nodes = Node.objects.start_nodes(
468 [system_id], request.user, user_data=user_data)456 [system_id], request.user, user_data=user_data)
469 if len(nodes) == 0:457 if len(nodes) == 0:
@@ -476,10 +464,6 @@
476 """Release a node. Opposite of `NodesHandler.acquire`."""464 """Release a node. Opposite of `NodesHandler.acquire`."""
477 node = Node.objects.get_node_or_404(465 node = Node.objects.get_node_or_404(
478 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)466 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
479<<<<<<< TREE
480=======
481 node.set_osystem_and_distro_series(osystem='', series='')
482>>>>>>> MERGE-SOURCE
483 if node.status == NODE_STATUS.READY:467 if node.status == NODE_STATUS.READY:
484 # Nothing to do. This may be a redundant retry, and the468 # Nothing to do. This may be a redundant retry, and the
485 # postcondition is achieved, so call this success.469 # postcondition is achieved, so call this success.
@@ -2429,7 +2413,6 @@
2429 if node.should_use_traditional_installer():2413 if node.should_use_traditional_installer():
2430 return "install"2414 return "install"
2431 else:2415 else:
2432<<<<<<< TREE
2433 # Return normal install if the booting operating system2416 # Return normal install if the booting operating system
2434 # doesn't support xinstall.2417 # doesn't support xinstall.
2435 osystem_obj = OperatingSystemRegistry[osystem]2418 osystem_obj = OperatingSystemRegistry[osystem]
@@ -2438,17 +2421,6 @@
2438 if BOOT_IMAGE_PURPOSE.XINSTALL in purposes:2421 if BOOT_IMAGE_PURPOSE.XINSTALL in purposes:
2439 return "xinstall"2422 return "xinstall"
2440 return "install"2423 return "install"
2441=======
2442 # Check that the booting operating system, actually supports
2443 # fast-path installation. If it does not then, we need to
2444 # return normal install.
2445 osystem_obj = OperatingSystemRegistry[osystem]
2446 purposes = osystem_obj.get_boot_image_purposes(
2447 arch, subarch, series, label)
2448 if BOOT_IMAGE_PURPOSE.XINSTALL in purposes:
2449 return "xinstall"
2450 return "install"
2451>>>>>>> MERGE-SOURCE
2452 else:2424 else:
2453 return "local" # TODO: Investigate.2425 return "local" # TODO: Investigate.
2454 else:2426 else:
@@ -2555,18 +2527,10 @@
25552527
2556 subarch = get_optional_param(request.GET, 'subarch', 'generic')2528 subarch = get_optional_param(request.GET, 'subarch', 'generic')
25572529
2558<<<<<<< TREE
2559 # Get the purpose, without a selected label2530 # Get the purpose, without a selected label
2560 purpose = get_boot_purpose(2531 purpose = get_boot_purpose(
2561 node, osystem, arch, subarch, series, label=None)2532 node, osystem, arch, subarch, series, label=None)
25622533
2563=======
2564 # Get the purpose, checking that if node is using xinstall, the operating
2565 # system actaully supports that mode. We pass None for the label, here
2566 # because it is unknown which label will be used yet.
2567 purpose = get_boot_purpose(node, osystem, arch, subarch, series, None)
2568
2569>>>>>>> MERGE-SOURCE
2570 # We use as our default label the label of the most recent image for2534 # We use as our default label the label of the most recent image for
2571 # the criteria we've assembled above. If there is no latest image2535 # the criteria we've assembled above. If there is no latest image
2572 # (which should never happen in reality but may happen in tests), we2536 # (which should never happen in reality but may happen in tests), we
@@ -2590,16 +2554,9 @@
2590 subarch = latest_image.subarchitecture2554 subarch = latest_image.subarchitecture
2591 label = get_optional_param(request.GET, 'label', latest_label)2555 label = get_optional_param(request.GET, 'label', latest_label)
25922556
2593<<<<<<< TREE
2594 # Get the supported purpose with the boot label2557 # Get the supported purpose with the boot label
2595 purpose = get_boot_purpose(node, osystem, arch, subarch, series, label)2558 purpose = get_boot_purpose(node, osystem, arch, subarch, series, label)
25962559
2597=======
2598 # Now that we have the correct label, lets check the boot purpose again
2599 # to make sure that the boot image with that label, is the correct purpose.
2600 purpose = get_boot_purpose(node, osystem, arch, subarch, series, label)
2601
2602>>>>>>> MERGE-SOURCE
2603 if node is not None:2560 if node is not None:
2604 # We don't care if the kernel opts is from the global setting or a tag,2561 # We don't care if the kernel opts is from the global setting or a tag,
2605 # just get the options2562 # just get the options
26062563
=== modified file 'src/maasserver/compose_preseed.py'
--- src/maasserver/compose_preseed.py 2013-10-18 09:54:17 +0000
+++ src/maasserver/compose_preseed.py 2014-05-29 18:21:27 +0000
@@ -20,6 +20,7 @@
2020
21from maasserver.enum import NODE_STATUS21from maasserver.enum import NODE_STATUS
22from maasserver.utils import absolute_reverse22from maasserver.utils import absolute_reverse
23from provisioningserver.driver import OperatingSystemRegistry
23import yaml24import yaml
2425
2526
@@ -104,6 +105,13 @@
104 if node.status == NODE_STATUS.COMMISSIONING:105 if node.status == NODE_STATUS.COMMISSIONING:
105 return compose_commissioning_preseed(token, base_url)106 return compose_commissioning_preseed(token, base_url)
106 else:107 else:
108 osystem = OperatingSystemRegistry[node.get_osystem()]
109 metadata_url = absolute_reverse('metadata', base_url=base_url)
110 try:
111 return osystem.compose_preseed(node, token, metadata_url)
112 except NotImplementedError:
113 pass
114
107 if node.should_use_traditional_installer():115 if node.should_use_traditional_installer():
108 return compose_cloud_init_preseed(token, base_url)116 return compose_cloud_init_preseed(token, base_url)
109 else:117 else:
110118
=== modified file 'src/maasserver/enum.py'
--- src/maasserver/enum.py 2014-03-28 16:36:32 +0000
+++ src/maasserver/enum.py 2014-05-29 18:21:27 +0000
@@ -13,7 +13,6 @@
1313
14__metaclass__ = type14__metaclass__ = type
15__all__ = [15__all__ = [
16 'COMMISSIONING_DISTRO_SERIES_CHOICES',
17 'COMPONENT',16 'COMPONENT',
18 'NODEGROUP_STATUS',17 'NODEGROUP_STATUS',
19 'NODEGROUP_STATUS_CHOICES',18 'NODEGROUP_STATUS_CHOICES',
@@ -25,8 +24,6 @@
25 'NODE_STATUS_CHOICES',24 'NODE_STATUS_CHOICES',
26 'NODE_STATUS_CHOICES_DICT',25 'NODE_STATUS_CHOICES_DICT',
27 'PRESEED_TYPE',26 'PRESEED_TYPE',
28 'DISTRO_SERIES',
29 'DISTRO_SERIES_CHOICES',
30 'USERDATA_TYPE',27 'USERDATA_TYPE',
31 ]28 ]
3229
@@ -86,36 +83,6 @@
86NODE_STATUS_CHOICES_DICT = OrderedDict(NODE_STATUS_CHOICES)83NODE_STATUS_CHOICES_DICT = OrderedDict(NODE_STATUS_CHOICES)
8784
8885
89class DISTRO_SERIES:
90 """List of supported ubuntu releases."""
91 #:
92 default = ''
93 #:
94 precise = 'precise'
95 #:
96 quantal = 'quantal'
97 #:
98 raring = 'raring'
99 #:
100 saucy = 'saucy'
101 #:
102 trusty = 'trusty'
103
104DISTRO_SERIES_CHOICES = (
105 (DISTRO_SERIES.default, 'Default Ubuntu Release'),
106 (DISTRO_SERIES.precise, 'Ubuntu 12.04 LTS "Precise Pangolin"'),
107 (DISTRO_SERIES.quantal, 'Ubuntu 12.10 "Quantal Quetzal"'),
108 (DISTRO_SERIES.raring, 'Ubuntu 13.04 "Raring Ringtail"'),
109 (DISTRO_SERIES.saucy, 'Ubuntu 13.10 "Saucy Salamander"'),
110 (DISTRO_SERIES.trusty, 'Ubuntu 14.04 LTS "Trusty Tahr"'),
111)
112
113
114COMMISSIONING_DISTRO_SERIES_CHOICES = (
115 (DISTRO_SERIES.trusty, dict(DISTRO_SERIES_CHOICES)[DISTRO_SERIES.trusty]),
116)
117
118
119class NODE_PERMISSION:86class NODE_PERMISSION:
120 """Permissions relating to nodes."""87 """Permissions relating to nodes."""
121 VIEW = 'view_node'88 VIEW = 'view_node'
12289
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/forms.py 2014-05-29 17:03:20 +0000
@@ -85,10 +85,6 @@
85from maasserver.forms_settings import (85from maasserver.forms_settings import (
86 CONFIG_ITEMS_KEYS,86 CONFIG_ITEMS_KEYS,
87 get_config_field,87 get_config_field,
88<<<<<<< TREE
89=======
90 list_commisioning_choices,
91>>>>>>> MERGE-SOURCE
92 INVALID_SETTING_MSG_TEMPLATE,88 INVALID_SETTING_MSG_TEMPLATE,
93 list_commisioning_choices,89 list_commisioning_choices,
94 )90 )
@@ -219,7 +215,6 @@
219 return all_architectures[0]215 return all_architectures[0]
220216
221217
222<<<<<<< TREE
223def list_all_usable_osystems():218def list_all_usable_osystems():
224 """Return all operating systems that can be used for nodes.219 """Return all operating systems that can be used for nodes.
225220
@@ -320,82 +315,6 @@
320 return None315 return None
321316
322317
323=======
324def list_all_usable_osystems():
325 """Return all operating systems that can be used for nodes.
326
327 These are the operating systems for which any nodegroup has the boot images
328 required to boot the node.
329 """
330 # The Node edit form offers all usable operating systems as options for the
331 # osystem field. Not all of these may be available in the node's
332 # nodegroup, but to represent that accurately in the UI would depend on
333 # the currently selected nodegroup. Narrowing the options down further
334 # would have to happen browser-side.
335 osystems = set()
336 for nodegroup in NodeGroup.objects.all():
337 osystems = osystems.union(
338 BootImage.objects.get_usable_osystems(nodegroup))
339 osystems = [OperatingSystemRegistry[osystem] for osystem in osystems]
340 return sorted(osystems, key=lambda osystem: osystem.title)
341
342
343def list_osystem_choices(osystems):
344 """Return Django "choices" list for `osystem`."""
345 choices = [('', 'Default OS')]
346 choices += [
347 (osystem.name, osystem.title)
348 for osystem in osystems
349 ]
350 return choices
351
352
353def list_all_usable_releases(osystems):
354 """Return dictionary of usable `releases` for each opertaing system."""
355 distro_series = {}
356 nodegroups = list(NodeGroup.objects.all())
357 for osystem in osystems:
358 releases = set()
359 for nodegroup in nodegroups:
360 releases = releases.union(
361 BootImage.objects.get_usable_releases(nodegroup, osystem.name))
362 distro_series[osystem.name] = sorted(releases)
363 return distro_series
364
365
366def list_release_choices(releases):
367 """Return Django "choices" list for `releases`."""
368 choices = [('', 'Default OS Release')]
369 for key, value in releases.items():
370 osystem = OperatingSystemRegistry[key]
371 options = osystem.format_release_choices(value)
372 choices += [(
373 '%s/' % osystem.name,
374 'Latest %s Release' % osystem.title
375 )]
376 choices += [
377 ('%s/%s' % (osystem.name, name), title)
378 for name, title in options
379 ]
380 return choices
381
382
383def clean_distro_series_field(form, field, os_field):
384 # distro_series field can be supplied the value os/release, that is the
385 # way the web UI provides the value.
386 new_distro_series = form.cleaned_data.get(field)
387 if new_distro_series is not None and '/' in new_distro_series:
388 os, release = new_distro_series.split('/', 1)
389 if 'os' in form.cleaned_data:
390 new_os = form.cleaned_data[os_field]
391 if os != new_os:
392 raise ValidationError(
393 "%s option does not match osystem option." % field)
394 return release
395 return new_distro_series
396
397
398>>>>>>> MERGE-SOURCE
399class NodeForm(ModelForm):318class NodeForm(ModelForm):
400319
401 def __init__(self, request=None, *args, **kwargs):320 def __init__(self, request=None, *args, **kwargs):
@@ -428,7 +347,6 @@
428 choices=choices, required=True, initial=default_arch,347 choices=choices, required=True, initial=default_arch,
429 error_messages={'invalid_choice': invalid_arch_message})348 error_messages={'invalid_choice': invalid_arch_message})
430349
431<<<<<<< TREE
432 def set_up_osystem_and_distro_series_fields(self, instance):350 def set_up_osystem_and_distro_series_fields(self, instance):
433 """Create the `osystem` and `distro_series` fields.351 """Create the `osystem` and `distro_series` fields.
434352
@@ -455,43 +373,6 @@
455 if instance is not None:373 if instance is not None:
456 self.initial['distro_series'] = initial_value374 self.initial['distro_series'] = initial_value
457375
458=======
459 def set_up_osystem_and_distro_series_fields(self, instance):
460 """Create the `osystem` and `distro_series` fields.
461
462 This needs to be done on the fly so that we can pass a dynamic list of
463 usable operating systems and distro_series.
464 """
465 osystems = list_all_usable_osystems()
466 releases = list_all_usable_releases(osystems)
467 choices = list_osystem_choices(osystems)
468 distro_choices = list_release_choices(releases)
469 invalid_osystem_message = compose_invalid_choice_text(
470 'osystem', choices)
471 invalid_distro_series_message = compose_invalid_choice_text(
472 'distro_series', distro_choices)
473 self.fields['osystem'] = forms.ChoiceField(
474 label="OS", choices=choices, required=False, initial='',
475 error_messages={'invalid_choice': invalid_osystem_message})
476 self.fields['distro_series'] = forms.ChoiceField(
477 label="Release", choices=distro_choices,
478 required=False, initial='',
479 error_messages={'invalid_choice': invalid_distro_series_message})
480
481 # Set the initial for the distro_series so it is selected
482 # correctly in the gui.
483 if instance is not None:
484 osystem = instance.osystem
485 series = instance.distro_series
486 if osystem is not None and osystem != '':
487 if series is None:
488 series = ''
489 self.initial['distro_series'] = '%s/%s' % (
490 osystem,
491 series,
492 )
493
494>>>>>>> MERGE-SOURCE
495 def clean_hostname(self):376 def clean_hostname(self):
496 # Don't allow the hostname to be changed if the node is377 # Don't allow the hostname to be changed if the node is
497 # currently allocated. Juju knows the node by its old name, so378 # currently allocated. Juju knows the node by its old name, so
@@ -516,7 +397,6 @@
516 is_valid = False397 is_valid = False
517 return is_valid398 return is_valid
518399
519<<<<<<< TREE
520 def set_distro_series(self, series=''):400 def set_distro_series(self, series=''):
521 """Sets the osystem and distro_series, from the provided401 """Sets the osystem and distro_series, from the provided
522 distro_series.402 distro_series.
@@ -536,8 +416,6 @@
536 else:416 else:
537 self.data['distro_series'] = series417 self.data['distro_series'] = series
538418
539=======
540>>>>>>> MERGE-SOURCE
541 hostname = forms.CharField(419 hostname = forms.CharField(
542 label="Host name", required=False, help_text=(420 label="Host name", required=False, help_text=(
543 "The FQDN (Fully Qualified Domain Name) is derived from the "421 "The FQDN (Fully Qualified Domain Name) is derived from the "
544422
=== modified file 'src/maasserver/forms_settings.py'
--- src/maasserver/forms_settings.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/forms_settings.py 2014-05-29 16:59:04 +0000
@@ -31,7 +31,6 @@
31INVALID_URL_MESSAGE = "Enter a valid url (e.g. http://host.example.com)."31INVALID_URL_MESSAGE = "Enter a valid url (e.g. http://host.example.com)."
3232
3333
34<<<<<<< TREE
35def list_osystem_choices():34def list_osystem_choices():
36 osystems = [osystem for _, osystem in OperatingSystemRegistry]35 osystems = [osystem for _, osystem in OperatingSystemRegistry]
37 osystems = sorted(osystems, key=lambda osystem: osystem.title)36 osystems = sorted(osystems, key=lambda osystem: osystem.title)
@@ -59,33 +58,6 @@
59 releases = DEFAULT_OS.get_supported_commissioning_releases()58 releases = DEFAULT_OS.get_supported_commissioning_releases()
60 options = DEFAULT_OS.format_release_choices(releases)59 options = DEFAULT_OS.format_release_choices(releases)
61 return list(options)60 return list(options)
62=======
63def list_osystem_choices():
64 return [
65 (osystem.name, osystem.title)
66 for _, osystem in OperatingSystemRegistry
67 ]
68
69
70def list_release_choices():
71 osystems = [osystem for _, osystem in OperatingSystemRegistry]
72 choices = []
73 for osystem in osystems:
74 supported = sorted(osystem.get_supported_releases())
75 options = osystem.format_release_choices(supported)
76 options = [
77 ('%s/%s' % (osystem.name, name), title)
78 for name, title in options
79 ]
80 choices += options
81 return choices
82
83
84def list_commisioning_choices():
85 releases = DEFAULT_OS.get_supported_commissioning_releases()
86 options = DEFAULT_OS.format_release_choices(releases)
87 return [(name, title) for name, title in options]
88>>>>>>> MERGE-SOURCE
8961
9062
91CONFIG_ITEMS = {63CONFIG_ITEMS = {
9264
=== removed file 'src/maasserver/migrations/0077_add_osystem_to_node.py'
--- src/maasserver/migrations/0077_add_osystem_to_node.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/migrations/0077_add_osystem_to_node.py 1970-01-01 00:00:00 +0000
@@ -1,264 +0,0 @@
1# -*- coding: utf-8 -*-
2from south.utils import datetime_utils as datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'Node.osystem'
12 db.add_column(u'maasserver_node', 'osystem',
13 self.gf('django.db.models.fields.CharField')(default=u'', max_length=20, null=True, blank=True),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Node.osystem'
19 db.delete_column(u'maasserver_node', 'osystem')
20
21
22 models = {
23 u'auth.group': {
24 'Meta': {'object_name': 'Group'},
25 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
27 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
28 },
29 u'auth.permission': {
30 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
31 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
32 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
35 },
36 u'auth.user': {
37 'Meta': {'object_name': 'User'},
38 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
39 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
40 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
41 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
42 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
43 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
44 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
45 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
46 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
47 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
48 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
49 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
50 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
51 },
52 u'contenttypes.contenttype': {
53 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
54 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
55 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
56 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
57 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
58 },
59 u'maasserver.bootimage': {
60 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
61 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
62 'created': ('django.db.models.fields.DateTimeField', [], {}),
63 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
64 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
65 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
66 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
67 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
68 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
69 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
70 'updated': ('django.db.models.fields.DateTimeField', [], {})
71 },
72 u'maasserver.bootsource': {
73 'Meta': {'object_name': 'BootSource'},
74 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
75 'created': ('django.db.models.fields.DateTimeField', [], {}),
76 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 'keyring_data': ('django.db.models.fields.BinaryField', [], {'blank': 'True'}),
78 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
79 'updated': ('django.db.models.fields.DateTimeField', [], {}),
80 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
81 },
82 u'maasserver.bootsourceselection': {
83 'Meta': {'object_name': 'BootSourceSelection'},
84 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
85 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
86 'created': ('django.db.models.fields.DateTimeField', [], {}),
87 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
88 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
89 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
90 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
91 'updated': ('django.db.models.fields.DateTimeField', [], {})
92 },
93 u'maasserver.componenterror': {
94 'Meta': {'object_name': 'ComponentError'},
95 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
96 'created': ('django.db.models.fields.DateTimeField', [], {}),
97 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
98 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99 'updated': ('django.db.models.fields.DateTimeField', [], {})
100 },
101 u'maasserver.config': {
102 'Meta': {'object_name': 'Config'},
103 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
105 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
106 },
107 u'maasserver.dhcplease': {
108 'Meta': {'object_name': 'DHCPLease'},
109 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
110 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
111 'mac': ('maasserver.fields.MACAddressField', [], {}),
112 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
113 },
114 u'maasserver.downloadprogress': {
115 'Meta': {'object_name': 'DownloadProgress'},
116 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
117 'created': ('django.db.models.fields.DateTimeField', [], {}),
118 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
119 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
120 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
122 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
123 'updated': ('django.db.models.fields.DateTimeField', [], {})
124 },
125 u'maasserver.filestorage': {
126 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
127 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
128 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
129 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130 'key': ('django.db.models.fields.CharField', [], {'default': "u'9bbf01e4-cbbd-11e3-afb3-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}),
131 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
132 },
133 u'maasserver.macaddress': {
134 'Meta': {'object_name': 'MACAddress'},
135 'created': ('django.db.models.fields.DateTimeField', [], {}),
136 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
138 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
139 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
140 'updated': ('django.db.models.fields.DateTimeField', [], {})
141 },
142 u'maasserver.network': {
143 'Meta': {'object_name': 'Network'},
144 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
145 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
147 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
148 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
149 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
150 },
151 u'maasserver.node': {
152 'Meta': {'object_name': 'Node'},
153 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
154 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
155 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
156 'created': ('django.db.models.fields.DateTimeField', [], {}),
157 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
158 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
159 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
160 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
161 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
162 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
163 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
164 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
165 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
166 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
167 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
168 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
169 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
170 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
171 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-9bbde11a-cbbd-11e3-afb3-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}),
172 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
173 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
174 'updated': ('django.db.models.fields.DateTimeField', [], {}),
175 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
176 },
177 u'maasserver.nodegroup': {
178 'Meta': {'object_name': 'NodeGroup'},
179 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
180 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
181 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
182 'created': ('django.db.models.fields.DateTimeField', [], {}),
183 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
184 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
185 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
186 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
187 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
188 'updated': ('django.db.models.fields.DateTimeField', [], {}),
189 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
190 },
191 u'maasserver.nodegroupinterface': {
192 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
193 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
194 'created': ('django.db.models.fields.DateTimeField', [], {}),
195 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
196 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
198 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
199 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
200 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
201 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
202 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
203 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
204 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
205 'updated': ('django.db.models.fields.DateTimeField', [], {})
206 },
207 u'maasserver.sshkey': {
208 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
209 'created': ('django.db.models.fields.DateTimeField', [], {}),
210 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
211 'key': ('django.db.models.fields.TextField', [], {}),
212 'updated': ('django.db.models.fields.DateTimeField', [], {}),
213 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
214 },
215 u'maasserver.tag': {
216 'Meta': {'object_name': 'Tag'},
217 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
218 'created': ('django.db.models.fields.DateTimeField', [], {}),
219 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
220 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
221 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
222 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
223 'updated': ('django.db.models.fields.DateTimeField', [], {})
224 },
225 u'maasserver.userprofile': {
226 'Meta': {'object_name': 'UserProfile'},
227 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
229 },
230 u'maasserver.zone': {
231 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
232 'created': ('django.db.models.fields.DateTimeField', [], {}),
233 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
234 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
235 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
236 'updated': ('django.db.models.fields.DateTimeField', [], {})
237 },
238 u'piston.consumer': {
239 'Meta': {'object_name': 'Consumer'},
240 'description': ('django.db.models.fields.TextField', [], {}),
241 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
242 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
243 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
244 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
245 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
246 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
247 },
248 u'piston.token': {
249 'Meta': {'object_name': 'Token'},
250 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
251 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
252 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
253 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
254 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
255 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
256 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
257 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1398350097L'}),
258 'token_type': ('django.db.models.fields.IntegerField', [], {}),
259 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
260 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
261 }
262 }
263
264 complete_apps = ['maasserver']
265\ No newline at end of file0\ No newline at end of file
2661
=== modified file 'src/maasserver/models/bootsource.py'
--- src/maasserver/models/bootsource.py 2014-05-22 13:42:52 +0000
+++ src/maasserver/models/bootsource.py 2014-05-29 18:21:27 +0000
@@ -29,6 +29,14 @@
29from maasserver.fields import EditableBinaryField29from maasserver.fields import EditableBinaryField
30from maasserver.models.cleansave import CleanSave30from maasserver.models.cleansave import CleanSave
31from maasserver.models.timestampedmodel import TimestampedModel31from maasserver.models.timestampedmodel import TimestampedModel
32from provisioningserver.driver.os_ubuntu import UbuntuOS
33
34
35def list_release_choices():
36 """Return Django "choices" list for Ubuntu releases."""
37 osystem = UbuntuOS()
38 releases = osystem.get_supported_releases()
39 return osystem.format_release_choices(releases)
3240
3341
34class BootSource(CleanSave, TimestampedModel):42class BootSource(CleanSave, TimestampedModel):
3543
=== modified file 'src/maasserver/models/bootsourceselection.py'
--- src/maasserver/models/bootsourceselection.py 2014-05-16 11:41:18 +0000
+++ src/maasserver/models/bootsourceselection.py 2014-05-29 18:21:27 +0000
@@ -24,12 +24,16 @@
24 )24 )
25import djorm_pgarray.fields25import djorm_pgarray.fields
26from maasserver import DefaultMeta26from maasserver import DefaultMeta
27from maasserver.enum import (
28 DISTRO_SERIES,
29 DISTRO_SERIES_CHOICES,
30 )
31from maasserver.models.cleansave import CleanSave27from maasserver.models.cleansave import CleanSave
32from maasserver.models.timestampedmodel import TimestampedModel28from maasserver.models.timestampedmodel import TimestampedModel
29from provisioningserver.driver.os_ubuntu import UbuntuOS
30
31
32def list_release_choices():
33 """Return Django "choices" list for Ubuntu releases."""
34 osystem = UbuntuOS()
35 releases = osystem.get_supported_releases()
36 return osystem.format_release_choices(releases)
3337
3438
35class BootSourceSelectionManager(Manager):39class BootSourceSelectionManager(Manager):
@@ -47,8 +51,8 @@
47 boot_source = ForeignKey('maasserver.BootSource', blank=False)51 boot_source = ForeignKey('maasserver.BootSource', blank=False)
4852
49 release = CharField(53 release = CharField(
50 max_length=20, choices=DISTRO_SERIES_CHOICES, blank=True,54 max_length=20, choices=list_release_choices(), blank=True,
51 default=DISTRO_SERIES.default,55 default='',
52 help_text="The Ubuntu release for which to import resources.")56 help_text="The Ubuntu release for which to import resources.")
5357
54 arches = djorm_pgarray.fields.ArrayField(dbtype="text")58 arches = djorm_pgarray.fields.ArrayField(dbtype="text")
5559
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/models/node.py 2014-05-29 18:21:27 +0000
@@ -508,23 +508,11 @@
508 owner = ForeignKey(508 owner = ForeignKey(
509 User, default=None, blank=True, null=True, editable=False)509 User, default=None, blank=True, null=True, editable=False)
510510
511<<<<<<< TREE
512 osystem = CharField(511 osystem = CharField(
513 max_length=20, blank=True, default='')512 max_length=20, blank=True, default='')
514513
515=======
516 osystem = CharField(
517 max_length=20, null=True, blank=True, default='',
518 validators=[validate_osystem])
519
520>>>>>>> MERGE-SOURCE
521 distro_series = CharField(514 distro_series = CharField(
522<<<<<<< TREE
523 max_length=20, blank=True, default='')515 max_length=20, blank=True, default='')
524=======
525 max_length=20, null=True, blank=True, default='',
526 validators=[validate_distro_series])
527>>>>>>> MERGE-SOURCE
528516
529 architecture = CharField(max_length=31, blank=False)517 architecture = CharField(max_length=31, blank=False)
530518
@@ -831,7 +819,6 @@
831 """The name of the queue for tasks specific to this node."""819 """The name of the queue for tasks specific to this node."""
832 return self.nodegroup.work_queue820 return self.nodegroup.work_queue
833821
834<<<<<<< TREE
835 def get_osystem(self):822 def get_osystem(self):
836 """Return the operating system to install that node."""823 """Return the operating system to install that node."""
837 use_default_osystem = (self.osystem is None or self.osystem == '')824 use_default_osystem = (self.osystem is None or self.osystem == '')
@@ -840,39 +827,15 @@
840 else:827 else:
841 return self.osystem828 return self.osystem
842829
843=======
844 def get_osystem(self):
845 """Return the operating system to install that node."""
846 use_default_osystem = (
847 not self.osystem or
848 self.osystem == '')
849 if use_default_osystem:
850 return Config.objects.get_config('default_osystem')
851 else:
852 return self.osystem
853
854>>>>>>> MERGE-SOURCE
855 def get_distro_series(self):830 def get_distro_series(self):
856 """Return the distro series to install that node."""831 """Return the distro series to install that node."""
857<<<<<<< TREE
858 use_default_osystem = (832 use_default_osystem = (
859 self.osystem is None or833 self.osystem is None or
860 self.osystem == '')834 self.osystem == '')
861=======
862 use_default_osystem = (
863 not self.osystem or
864 self.osystem == '')
865>>>>>>> MERGE-SOURCE
866 use_default_distro_series = (835 use_default_distro_series = (
867<<<<<<< TREE
868 self.distro_series is None or836 self.distro_series is None or
869 self.distro_series == '')837 self.distro_series == '')
870 if use_default_osystem and use_default_distro_series:838 if use_default_osystem and use_default_distro_series:
871=======
872 not self.distro_series or
873 self.distro_series == '')
874 if use_default_osystem and use_default_distro_series:
875>>>>>>> MERGE-SOURCE
876 return Config.objects.get_config('default_distro_series')839 return Config.objects.get_config('default_distro_series')
877 elif use_default_distro_series:840 elif use_default_distro_series:
878 osystem = OperatingSystemRegistry[self.osystem]841 osystem = OperatingSystemRegistry[self.osystem]
@@ -880,25 +843,6 @@
880 else:843 else:
881 return self.distro_series844 return self.distro_series
882845
883<<<<<<< TREE
884=======
885 def set_osystem(self, osystem=''):
886 """Set the operating system to install that node."""
887 self.osystem = osystem
888 self.save()
889
890 def set_distro_series(self, series=''):
891 """Set the distro series to install that node."""
892 self.distro_series = series
893 self.save()
894
895 def set_osystem_and_distro_series(self, osystem='', series=''):
896 """Set the oeprating system to install that node."""
897 self.osystem = osystem
898 self.distro_series = series
899 self.save()
900
901>>>>>>> MERGE-SOURCE
902 def get_effective_power_parameters(self):846 def get_effective_power_parameters(self):
903 """Return effective power parameters, including any defaults."""847 """Return effective power parameters, including any defaults."""
904 if self.power_parameters:848 if self.power_parameters:
905849
=== modified file 'src/maasserver/models/tests/test_node.py'
--- src/maasserver/models/tests/test_node.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/models/tests/test_node.py 2014-05-29 17:03:20 +0000
@@ -269,7 +269,6 @@
269 offset += timedelta(1)269 offset += timedelta(1)
270 self.assertEqual(macs[0], node.get_primary_mac().mac_address)270 self.assertEqual(macs[0], node.get_primary_mac().mac_address)
271271
272<<<<<<< TREE
273 def test_get_osystem_returns_default_osystem(self):272 def test_get_osystem_returns_default_osystem(self):
274 node = factory.make_node(osystem='')273 node = factory.make_node(osystem='')
275 osystem = Config.objects.get_config('default_osystem')274 osystem = Config.objects.get_config('default_osystem')
@@ -278,41 +277,6 @@
278 def test_get_distro_series_returns_default_series(self):277 def test_get_distro_series_returns_default_series(self):
279 node = factory.make_node(distro_series='')278 node = factory.make_node(distro_series='')
280 series = Config.objects.get_config('default_distro_series')279 series = Config.objects.get_config('default_distro_series')
281=======
282 def test_get_osystem_returns_default_osystem_and_series(self):
283 node = factory.make_node()
284 osystem = Config.objects.get_config('default_osystem')
285 series = Config.objects.get_config('default_distro_series')
286 self.assertEqual(osystem, node.get_osystem())
287 self.assertEqual(series, node.get_distro_series())
288
289 def test_get_series_returns_default_for_osystem(self):
290 node = factory.make_node()
291 osystem = factory.getRandomOS()
292 series = osystem.get_default_release()
293 node.set_osystem(osystem.name)
294 self.assertEqual(series, node.get_distro_series())
295
296 def test_set_get_osystem_returns_osystem(self):
297 osystem = factory.getRandomOS()
298 node = factory.make_node()
299 node.set_osystem(osystem.name)
300 self.assertEqual(osystem.name, node.get_osystem())
301
302 def test_set_get_distro_series_returns_series(self):
303 osystem = factory.getRandomOS()
304 series = factory.getRandomRelease(osystem)
305 node = factory.make_node()
306 node.set_distro_series(series)
307>>>>>>> MERGE-SOURCE
308 self.assertEqual(series, node.get_distro_series())
309
310 def test_set_get_osystem_and_distro_series_returns_valid(self):
311 osystem = factory.getRandomOS()
312 series = factory.getRandomRelease(osystem)
313 node = factory.make_node()
314 node.set_osystem_and_distro_series(osystem.name, series)
315 self.assertEqual(osystem.name, node.get_osystem())
316 self.assertEqual(series, node.get_distro_series())280 self.assertEqual(series, node.get_distro_series())
317281
318 def test_delete_node_deletes_related_mac(self):282 def test_delete_node_deletes_related_mac(self):
319283
=== modified file 'src/maasserver/static/js/node_add.js'
--- src/maasserver/static/js/node_add.js 2014-05-29 18:21:27 +0000
+++ src/maasserver/static/js/node_add.js 2014-05-16 15:22:54 +0000
@@ -234,7 +234,6 @@
234 },234 },
235235
236 /**236 /**
237<<<<<<< TREE
238 * If the 'distro_series' field is present, link it to operating237 * If the 'distro_series' field is present, link it to operating
239 * system field.238 * system field.
240 *239 *
@@ -251,24 +250,6 @@
251 },250 },
252251
253 /**252 /**
254=======
255 * If the 'distro_series' field is present, setup the linked
256 * 'distro_series' field.
257 *
258 * @method setUpDistroSeriesField
259 */
260 setUpDistroSeriesField: function() {
261 if (Y.Lang.isValue(Y.one('#id_distro_series'))) {
262 var widget = new Y.maas.os_distro_select.OSReleaseWidget({
263 srcNode: '#id_distro_series'
264 });
265 widget.bindTo(Y.one('#id_osystem'), 'change');
266 widget.render();
267 }
268 },
269
270 /**
271>>>>>>> MERGE-SOURCE
272 * If the 'power_type' field is present, setup the linked253 * If the 'power_type' field is present, setup the linked
273 * 'power_parameter' field.254 * 'power_parameter' field.
274 *255 *
275256
=== renamed file 'src/maasserver/static/js/os_distro_select.js.moved' => 'src/maasserver/static/js/os_distro_select.js'
=== removed file 'src/maasserver/static/js/os_distro_select.js'
--- src/maasserver/static/js/os_distro_select.js 2014-05-29 18:21:27 +0000
+++ src/maasserver/static/js/os_distro_select.js 1970-01-01 00:00:00 +0000
@@ -1,131 +0,0 @@
1/* Copyright 2012-2014 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * OS/Release seletion utilities.
5 *
6 * @module Y.maas.power_parameter
7 */
8
9YUI.add('maas.os_distro_select', function(Y) {
10
11Y.log('loading maas.os_distro_select');
12var module = Y.namespace('maas.os_distro_select');
13
14// Only used to mockup io in tests.
15module._io = new Y.IO();
16
17var OSReleaseWidget;
18
19/**
20 * A widget class used to have the content of a node's release <select>
21 * modified based on the selected operating system.
22 *
23 */
24OSReleaseWidget = function() {
25 OSReleaseWidget.superclass.constructor.apply(this, arguments);
26};
27
28OSReleaseWidget.NAME = 'os-release-widget';
29
30Y.extend(OSReleaseWidget, Y.Widget, {
31
32 /**
33 * Initialize the widget.
34 * - cfg.srcNode is the node which will be updated when the selected
35 * value of the 'os node' will change.
36 * - cfg.osNode is the node containing a 'select' element. When
37 * the selected element will change, the srcNode HTML will be
38 * updated.
39 *
40 * @method initializer
41 */
42 initializer: function(cfg) {
43 this.initialSkip = true;
44 },
45
46 /**
47 * Bind the widget to events (to name 'event_name') generated by the given
48 * 'osNode'.
49 *
50 * @method bindTo
51 */
52 bindTo: function(osNode, event_name) {
53 var self = this;
54 Y.one(osNode).on(event_name, function(e) {
55 var osValue = e.currentTarget.get('value');
56 self.switchTo(osValue);
57 });
58 var osValue = Y.one(osNode).get('value');
59 self.switchTo(osValue);
60 },
61
62 /**
63 * React to a new value of the os node: update the HTML of
64 * 'srcNode'.
65 *
66 * @method switchTo
67 */
68 switchTo: function(newOSValue) {
69 var srcNode = this.get('srcNode');
70 var options = srcNode.all('option');
71 var selected = false;
72 options.each(function(option) {
73 var value = option.get('value');
74 var split_value = value.split("/");
75
76 // Only show the default option, if that
77 // is the selected os option as well.
78 if(newOSValue == '') {
79 if(value == '') {
80 option.removeClass('hidden');
81 option.set('selected', 'selected');
82 }
83 else {
84 option.addClass('hidden');
85 }
86 }
87 else {
88 if(split_value[0] == newOSValue) {
89 option.removeClass('hidden');
90 if(split_value[1] == '') {
91 selected = true;
92 option.set('selected', 'selected');
93 }
94 }
95 else {
96 option.addClass('hidden');
97 }
98 }
99 });
100
101 // See if this was the inital skip. As the following
102 // should only be done, after the first load, as the
103 // initial will already be selected correctly.
104 if(this.initialSkip == true) {
105 this.initialSkip = false;
106 return;
107 }
108
109 // See if a selection was made, if not then we need
110 // to select the first visible as a default is not
111 // present.
112 if(!selected) {
113 var first_option = null;
114 options.each(function(option) {
115 if(!option.hasClass('hidden')) {
116 if(first_option == null) {
117 first_option = option;
118 }
119 }
120 });
121 if(first_option != null) {
122 first_option.set('selected', 'selected');
123 }
124 }
125 }
126});
127
128module.OSReleaseWidget = OSReleaseWidget;
129
130}, '0.1', {'requires': ['widget', 'io']}
131);
1320
=== renamed file 'src/maasserver/static/js/tests/test_os_distro_select.html.moved' => 'src/maasserver/static/js/tests/test_os_distro_select.html'
=== removed file 'src/maasserver/static/js/tests/test_os_distro_select.html'
--- src/maasserver/static/js/tests/test_os_distro_select.html 2014-05-29 18:21:27 +0000
+++ src/maasserver/static/js/tests/test_os_distro_select.html 1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3 <head>
4 <title>Test maas.os_distro_select</title>
5
6 <!-- YUI and test setup -->
7 <script type="text/javascript" src="../testing/yui_test_conf.js"></script>
8 <script type="text/javascript" src="/usr/share/javascript/yui3/yui/yui.js"></script>
9 <script type="text/javascript" src="../testing/testrunner.js"></script>
10 <script type="text/javascript" src="../testing/testing.js"></script>
11 <!-- The module under test -->
12 <script type="text/javascript" src="../os_distro_select.js"></script>
13 <!-- The test suite -->
14 <script type="text/javascript" src="test_os_distro_select.js"></script>
15 </head>
16 <body>
17 <span id="suite">maas.os_distro_select.tests</span>
18 <script type="text/x-template" id="select_node">
19 <select id="id_osystem">
20 <option value="" selected="selected">Default OS</option>
21 <option value="value1">Value1</option>
22 <option value="value2">Value2</option>
23 </select>
24 </script>
25 <script type="text/x-template" id="target_node">
26 <select id="id_distro_series">
27 <option value="" selected="selected">Default Release</option>
28 <option value="value1/series1">Value1Series1</option>
29 <option value="value1/series2">Value1Series2</option>
30 <option value="value1/series3">Value1Series3</option>
31 <option value="value2/series1">Value2Series1</option>
32 <option value="value2/series2">Value2Series2</option>
33 <option value="value2/series3">Value2Series3</option>
34 </select>
35 </script>
36 <div id="placeholder"></div>
37 </body>
38</html>
390
=== renamed file 'src/maasserver/static/js/tests/test_os_distro_select.js.moved' => 'src/maasserver/static/js/tests/test_os_distro_select.js'
=== removed file 'src/maasserver/static/js/tests/test_os_distro_select.js'
--- src/maasserver/static/js/tests/test_os_distro_select.js 2014-05-29 18:21:27 +0000
+++ src/maasserver/static/js/tests/test_os_distro_select.js 1970-01-01 00:00:00 +0000
@@ -1,106 +0,0 @@
1/* Copyright 2012 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 */
4
5YUI({ useBrowserConsole: true }).add(
6 'maas.os_distro_select.tests', function(Y) {
7
8Y.log('loading maas.os_distro_select.tests');
9var namespace = Y.namespace('maas.os_distro_select.tests');
10
11var module = Y.maas.os_distro_select;
12var suite = new Y.Test.Suite("maas.os_distro_select Tests");
13
14var select_node_template = Y.one('#select_node').getContent();
15var target_node_template = Y.one('#target_node').getContent();
16
17suite.add(new Y.maas.testing.TestCase({
18 name: 'test-os_distro_select',
19
20 setUp: function () {
21 Y.one('#placeholder').empty().append(
22 Y.Node.create(select_node_template).append(
23 Y.Node.create(target_node_template)));
24 },
25
26 testBindToDefaultShowsDefaultRelease: function() {
27 var widget = new Y.maas.os_distro_select.OSReleaseWidget({
28 srcNode: '#id_distro_series',
29 });
30 widget.bindTo(Y.one('#id_osystem'), 'change');
31
32 var options = Y.one('#id_distro_series').all('option');
33 options.each(function(option) {
34 var value = option.get('value');
35 if(value == '') {
36 Y.Assert.isFalse(option.hasClass('hidden'));
37 }
38 else {
39 Y.Assert.isTrue(option.hasClass('hidden'));
40 }
41 });
42 },
43
44 testNonDefaultShowsRelatedReleases: function() {
45 var node = Y.one('#id_distro_series');
46 node.append('<option value="value1/">Value1Default</option>');
47
48 var widget = new Y.maas.os_distro_select.OSReleaseWidget({
49 srcNode: '#id_distro_series',
50 });
51 widget.bindTo(Y.one('#id_osystem'), 'change');
52
53 var newValue = 'value1';
54 var select = Y.one('#id_osystem');
55 select.set('value', newValue);
56 select.simulate('change');
57
58 var options = Y.one('#id_distro_series').all('option');
59 options.each(function(option) {
60 var value = option.get('value');
61 if(value == '') {
62 Y.Assert.isTrue(option.hasClass('hidden'));
63 }
64 else {
65 var split_value = value.split('/');
66 if(split_value[0] == newValue) {
67 Y.Assert.isFalse(option.hasClass('hidden'));
68 }
69 else {
70 Y.Assert.isTrue(option.hasClass('hidden'));
71 }
72 }
73 });
74
75 Y.Assert.areEqual(
76 newValue + '/', Y.one('#id_distro_series').get('value'))
77 },
78
79 testInitialSkipOnFirstChange: function() {
80 var newValue = 'value1/series1';
81 var select = Y.one('#id_osystem');
82 select.set('value', 'value1');
83 select.simulate('change');
84 select = Y.one('#id_distro_series');
85 select.set('value', newValue);
86 select.simulate('change');
87
88 var widget = new Y.maas.os_distro_select.OSReleaseWidget({
89 srcNode: '#id_distro_series',
90 });
91
92 Y.Assert.isTrue(widget.initialSkip);
93 widget.bindTo(Y.one('#id_osystem'), 'change');
94 Y.Assert.isFalse(widget.initialSkip);
95
96 Y.Assert.areEqual(
97 newValue, Y.one('#id_distro_series').get('value'))
98 }
99
100}));
101
102namespace.suite = suite;
103
104}, '0.1', {'requires': [
105 'node-event-simulate', 'test', 'maas.testing', 'maas.os_distro_select']}
106);
1070
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2014-05-29 17:03:20 +0000
+++ src/maasserver/testing/factory.py 2014-05-29 18:21:27 +0000
@@ -23,7 +23,6 @@
23from django.contrib.auth.models import User23from django.contrib.auth.models import User
24from maasserver.clusterrpc.power_parameters import get_power_types24from maasserver.clusterrpc.power_parameters import get_power_types
25from maasserver.enum import (25from maasserver.enum import (
26 DISTRO_SERIES_CHOICES,
27 NODE_STATUS,26 NODE_STATUS,
28 NODEGROUP_STATUS,27 NODEGROUP_STATUS,
29 NODEGROUPINTERFACE_MANAGEMENT,28 NODEGROUPINTERFACE_MANAGEMENT,
@@ -62,6 +61,7 @@
62# XXX 2014-05-13 blake-rouse bug=131914361# XXX 2014-05-13 blake-rouse bug=1319143
63# Need to not import directly, use RPC to info from cluster.62# Need to not import directly, use RPC to info from cluster.
64from provisioningserver.driver import OperatingSystemRegistry63from provisioningserver.driver import OperatingSystemRegistry
64from provisioningserver.driver.os_ubuntu import UbuntuOS
6565
66# We have a limited number of public keys:66# We have a limited number of public keys:
67# src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub67# src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub
@@ -717,7 +717,8 @@
717 if boot_source is None:717 if boot_source is None:
718 boot_source = self.make_boot_source()718 boot_source = self.make_boot_source()
719 if release is None:719 if release is None:
720 release = self.getRandomChoice(DISTRO_SERIES_CHOICES)720 ubuntu_os = UbuntuOS()
721 release = self.getRandomRelease(ubuntu_os)
721 if arches is None:722 if arches is None:
722 arch_count = random.randint(1, 10)723 arch_count = random.randint(1, 10)
723 arches = [self.make_name("arch") for i in range(arch_count)]724 arches = [self.make_name("arch") for i in range(arch_count)]
724725
=== modified file 'src/maasserver/tests/test_api_boot_source_selections.py'
--- src/maasserver/tests/test_api_boot_source_selections.py 2014-05-27 13:53:21 +0000
+++ src/maasserver/tests/test_api_boot_source_selections.py 2014-05-29 18:21:27 +0000
@@ -19,11 +19,11 @@
1919
20from django.core.urlresolvers import reverse20from django.core.urlresolvers import reverse
21from maasserver.api import DISPLAYED_BOOTSOURCESELECTION_FIELDS21from maasserver.api import DISPLAYED_BOOTSOURCESELECTION_FIELDS
22from maasserver.enum import DISTRO_SERIES_CHOICES
23from maasserver.models import BootSourceSelection22from maasserver.models import BootSourceSelection
24from maasserver.testing import reload_object23from maasserver.testing import reload_object
25from maasserver.testing.api import APITestCase24from maasserver.testing.api import APITestCase
26from maasserver.testing.factory import factory25from maasserver.testing.factory import factory
26from provisioningserver.driver.os_ubuntu import UbuntuOS
27from testtools.matchers import MatchesStructure27from testtools.matchers import MatchesStructure
2828
2929
@@ -98,8 +98,8 @@
98 def test_PUT_updates_boot_source_selection(self):98 def test_PUT_updates_boot_source_selection(self):
99 self.become_admin()99 self.become_admin()
100 boot_source_selection = factory.make_boot_source_selection()100 boot_source_selection = factory.make_boot_source_selection()
101 new_release = factory.getRandomChoice(101 ubuntu_os = UbuntuOS()
102 DISTRO_SERIES_CHOICES)102 new_release = factory.getRandomRelease(ubuntu_os)
103 new_values = {103 new_values = {
104 'release': new_release,104 'release': new_release,
105 'arches': [factory.make_name('arch'), factory.make_name('arch')],105 'arches': [factory.make_name('arch'), factory.make_name('arch')],
@@ -160,8 +160,8 @@
160 def test_POST_creates_boot_source_selection(self):160 def test_POST_creates_boot_source_selection(self):
161 self.become_admin()161 self.become_admin()
162 boot_source = factory.make_boot_source()162 boot_source = factory.make_boot_source()
163 new_release = factory.getRandomChoice(163 ubuntu_os = UbuntuOS()
164 DISTRO_SERIES_CHOICES)164 new_release = factory.getRandomRelease(ubuntu_os)
165 params = {165 params = {
166 'release': new_release,166 'release': new_release,
167 'arches': [factory.make_name('arch'), factory.make_name('arch')],167 'arches': [factory.make_name('arch'), factory.make_name('arch')],
@@ -182,8 +182,8 @@
182182
183 def test_POST_requires_admin(self):183 def test_POST_requires_admin(self):
184 boot_source = factory.make_boot_source()184 boot_source = factory.make_boot_source()
185 new_release = factory.getRandomChoice(185 ubuntu_os = UbuntuOS()
186 DISTRO_SERIES_CHOICES)186 new_release = factory.getRandomRelease(ubuntu_os)
187 params = {187 params = {
188 'release': new_release,188 'release': new_release,
189 'arches': [factory.make_name('arch'), factory.make_name('arch')],189 'arches': [factory.make_name('arch'), factory.make_name('arch')],
190190
=== modified file 'src/maasserver/tests/test_api_node.py'
--- src/maasserver/tests/test_api_node.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/tests/test_api_node.py 2014-05-28 19:06:28 +0000
@@ -48,7 +48,6 @@
48 NodeUserData,48 NodeUserData,
49 )49 )
50from metadataserver.nodeinituser import get_node_init_user50from metadataserver.nodeinituser import get_node_init_user
51from provisioningserver.driver.os_ubuntu import UbuntuOS
5251
5352
54class NodeAnonAPITest(MAASServerTestCase):53class NodeAnonAPITest(MAASServerTestCase):
@@ -222,7 +221,6 @@
222 self.assertEqual(221 self.assertEqual(
223 node.system_id, json.loads(response.content)['system_id'])222 node.system_id, json.loads(response.content)['system_id'])
224223
225<<<<<<< TREE
226 def test_POST_start_sets_osystem_and_distro_series(self):224 def test_POST_start_sets_osystem_and_distro_series(self):
227 node = factory.make_node(225 node = factory.make_node(
228 owner=self.logged_in_user, mac=True,226 owner=self.logged_in_user, mac=True,
@@ -230,91 +228,18 @@
230 architecture=make_usable_architecture(self))228 architecture=make_usable_architecture(self))
231 osystem = make_usable_osystem(self)229 osystem = make_usable_osystem(self)
232 distro_series = factory.getRandomRelease(osystem)230 distro_series = factory.getRandomRelease(osystem)
233=======231 response = self.client.post(
234 def test_POST_start_sets_osystem(self):232 self.get_node_uri(node), {
235 node = factory.make_node(233 'op': 'start',
236 owner=self.logged_in_user, mac=True,234 'distro_series': distro_series
237 power_type='ether_wake')235 })
238 osystem = factory.getRandomOS()236 self.assertEqual(
239 response = self.client.post(237 (httplib.OK, node.system_id),
240 self.get_node_uri(node),238 (response.status_code, json.loads(response.content)['system_id']))
241 {'op': 'start', 'osystem': osystem.name})239 self.assertEqual(
242 self.assertEqual(240 osystem.name, reload_object(node).osystem)
243 (httplib.OK, node.system_id),241 self.assertEqual(
244 (response.status_code, json.loads(response.content)['system_id']))242 distro_series, reload_object(node).distro_series)
245 self.assertEqual(
246 osystem.name, reload_object(node).osystem)
247
248 def test_POST_start_accepts_os(self):
249 node = factory.make_node(
250 owner=self.logged_in_user, mac=True,
251 power_type='ether_wake')
252 osystem = factory.getRandomOS()
253 response = self.client.post(
254 self.get_node_uri(node),
255 {'op': 'start', 'os': osystem.name})
256 self.assertEqual(
257 (httplib.OK, node.system_id),
258 (response.status_code, json.loads(response.content)['system_id']))
259 self.assertEqual(
260 osystem.name, reload_object(node).osystem)
261
262 def test_POST_start_sets_osystem_and_distro_series(self):
263 node = factory.make_node(
264 owner=self.logged_in_user, mac=True,
265 power_type='ether_wake')
266 osystem = factory.getRandomOS()
267 distro_series = factory.getRandomRelease(osystem)
268 response = self.client.post(
269 self.get_node_uri(node), {
270 'op': 'start',
271 'osystem': osystem.name,
272 'distro_series': distro_series
273 })
274 self.assertEqual(
275 (httplib.OK, node.system_id),
276 (response.status_code, json.loads(response.content)['system_id']))
277 self.assertEqual(
278 osystem.name, reload_object(node).osystem)
279 self.assertEqual(
280 distro_series, reload_object(node).distro_series)
281
282 def test_POST_start_sets_distro_series_defaults_ubuntu(self):
283 node = factory.make_node(
284 owner=self.logged_in_user, mac=True,
285 power_type='ether_wake')
286 osystem = UbuntuOS()
287 distro_series = factory.getRandomRelease(osystem)
288>>>>>>> MERGE-SOURCE
289 response = self.client.post(
290 self.get_node_uri(node), {
291 'op': 'start',
292 'distro_series': distro_series
293 })
294 self.assertEqual(
295 (httplib.OK, node.system_id),
296 (response.status_code, json.loads(response.content)['system_id']))
297 self.assertEqual(
298 osystem.name, reload_object(node).osystem)
299 self.assertEqual(
300 distro_series, reload_object(node).distro_series)
301
302 def test_POST_start_validates_osystem(self):
303 node = factory.make_node(
304 owner=self.logged_in_user, mac=True,
305 power_type='ether_wake')
306 invalid_osystem = factory.getRandomString()
307 response = self.client.post(
308 self.get_node_uri(node),
309 {'op': 'start', 'osystem': invalid_osystem})
310 self.assertEqual(
311 (
312 httplib.BAD_REQUEST,
313 {'osystem': [
314 "Value u'%s' is not a valid choice." %
315 invalid_osystem]}
316 ),
317 (response.status_code, json.loads(response.content)))
318243
319 def test_POST_start_validates_distro_series(self):244 def test_POST_start_validates_distro_series(self):
320 node = factory.make_node(245 node = factory.make_node(
321246
=== modified file 'src/maasserver/tests/test_api_pxeconfig.py'
--- src/maasserver/tests/test_api_pxeconfig.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/tests/test_api_pxeconfig.py 2014-05-29 17:03:20 +0000
@@ -310,7 +310,6 @@
310 node.use_fastpath_installer()310 node.use_fastpath_installer()
311 for name, value in parameters.items():311 for name, value in parameters.items():
312 setattr(node, name, value)312 setattr(node, name, value)
313<<<<<<< TREE
314 osystem = node.get_osystem()313 osystem = node.get_osystem()
315 series = node.get_distro_series()314 series = node.get_distro_series()
316 arch, subarch = node.architecture.split('/')315 arch, subarch = node.architecture.split('/')
@@ -334,15 +333,6 @@
334 'install',333 'install',
335 api.get_boot_purpose(334 api.get_boot_purpose(
336 node, node_os, arch, subarch, node_series, None))335 node, node_os, arch, subarch, node_series, None))
337=======
338 osystem = node.get_osystem()
339 series = node.get_distro_series()
340 arch, subarch = node.architecture.split('/')
341 self.assertEqual(
342 purpose,
343 api.get_boot_purpose(
344 node, osystem, arch, subarch, series, None))
345>>>>>>> MERGE-SOURCE
346336
347 def test_pxeconfig_uses_boot_purpose(self):337 def test_pxeconfig_uses_boot_purpose(self):
348 fake_boot_purpose = factory.make_name("purpose")338 fake_boot_purpose = factory.make_name("purpose")
349339
=== modified file 'src/maasserver/tests/test_compose_preseed.py'
--- src/maasserver/tests/test_compose_preseed.py 2013-10-07 09:12:40 +0000
+++ src/maasserver/tests/test_compose_preseed.py 2014-05-29 18:21:27 +0000
@@ -17,8 +17,10 @@
17from maasserver.compose_preseed import compose_preseed17from maasserver.compose_preseed import compose_preseed
18from maasserver.enum import NODE_STATUS18from maasserver.enum import NODE_STATUS
19from maasserver.testing.factory import factory19from maasserver.testing.factory import factory
20from maasserver.testing.osystems import make_usable_osystem
20from maasserver.testing.testcase import MAASServerTestCase21from maasserver.testing.testcase import MAASServerTestCase
21from maasserver.utils import absolute_reverse22from maasserver.utils import absolute_reverse
23from maastesting.matchers import MockCalledOnceWith
22from metadataserver.models import NodeKey24from metadataserver.models import NodeKey
23from testtools.matchers import (25from testtools.matchers import (
24 KeysEqual,26 KeysEqual,
@@ -110,3 +112,16 @@
110 self.assertEqual(112 self.assertEqual(
111 absolute_reverse('curtin-metadata'),113 absolute_reverse('curtin-metadata'),
112 preseed['datasource']['MAAS']['metadata_url'])114 preseed['datasource']['MAAS']['metadata_url'])
115
116 def test_compose_preseed_with_osystem_compose_preseed(self):
117 osystem = make_usable_osystem(self)
118 mock_compose = self.patch(osystem, 'compose_preseed')
119 node = factory.make_node(
120 osystem=osystem.name, status=NODE_STATUS.READY)
121
122 token = NodeKey.objects.get_token_for_node(node)
123 url = absolute_reverse('metadata')
124 compose_preseed(node)
125 self.assertThat(
126 mock_compose,
127 MockCalledOnceWith(node, token, url))
113128
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/tests/test_forms.py 2014-05-29 18:21:27 +0000
@@ -29,7 +29,6 @@
29from django.http import QueryDict29from django.http import QueryDict
30from maasserver.clusterrpc.power_parameters import get_power_type_choices30from maasserver.clusterrpc.power_parameters import get_power_type_choices
31from maasserver.enum import (31from maasserver.enum import (
32 DISTRO_SERIES_CHOICES,
33 NODE_STATUS,32 NODE_STATUS,
34 NODEGROUP_STATUS,33 NODEGROUP_STATUS,
35 NODEGROUPINTERFACE_MANAGEMENT,34 NODEGROUPINTERFACE_MANAGEMENT,
@@ -99,11 +98,6 @@
99 make_usable_architecture,98 make_usable_architecture,
100 patch_usable_architectures,99 patch_usable_architectures,
101 )100 )
102from maasserver.testing.osystems import (
103 make_osystem_with_releases,
104 make_usable_osystem,
105 patch_usable_osystems,
106 )
107from maasserver.testing.factory import factory101from maasserver.testing.factory import factory
108from maasserver.testing.osystems import (102from maasserver.testing.osystems import (
109 make_osystem_with_releases,103 make_osystem_with_releases,
@@ -117,6 +111,7 @@
117from metadataserver.models import CommissioningScript111from metadataserver.models import CommissioningScript
118from netaddr import IPNetwork112from netaddr import IPNetwork
119from provisioningserver import tasks113from provisioningserver import tasks
114from provisioningserver.driver.os_ubuntu import UbuntuOS
120from testtools import TestCase115from testtools import TestCase
121from testtools.matchers import (116from testtools.matchers import (
122 AllMatch,117 AllMatch,
@@ -153,14 +148,9 @@
153 release = factory.make_name('release')148 release = factory.make_name('release')
154 for purpose in ['install', 'commissioning']:149 for purpose in ['install', 'commissioning']:
155 factory.make_boot_image(150 factory.make_boot_image(
156<<<<<<< TREE
157 nodegroup=nodegroup, osystem=osystem, architecture=arch,151 nodegroup=nodegroup, osystem=osystem, architecture=arch,
158 subarchitecture=subarchitecture, release=release,152 subarchitecture=subarchitecture, release=release,
159 purpose=purpose)153 purpose=purpose)
160=======
161 nodegroup=nodegroup, osystem=osystem, architecture=arch,
162 subarchitecture=subarchitecture, release=release, purpose=purpose)
163>>>>>>> MERGE-SOURCE
164154
165 def test_initialize_node_group_leaves_nodegroup_reference_intact(self):155 def test_initialize_node_group_leaves_nodegroup_reference_intact(self):
166 preselected_nodegroup = factory.make_node_group()156 preselected_nodegroup = factory.make_node_group()
@@ -223,131 +213,67 @@
223 arches = [factory.make_name('arch') for _ in range(5)]213 arches = [factory.make_name('arch') for _ in range(5)]
224 self.assertEqual(arches[0], pick_default_architecture(arches))214 self.assertEqual(arches[0], pick_default_architecture(arches))
225215
226<<<<<<< TREE216 def test_list_all_usable_osystems_combines_nodegroups(self):
227 def test_list_all_usable_osystems_combines_nodegroups(self):217 osystem_names = [factory.make_name('os') for _ in range(3)]
228 osystem_names = [factory.make_name('os') for _ in range(3)]218 expected = []
229 expected = []219 for name in osystem_names:
230 for name in osystem_names:220 self.make_usable_boot_images(osystem=name)
231 self.make_usable_boot_images(osystem=name)221 expected.append(make_usable_osystem(self, name))
232 expected.append(make_usable_osystem(self, name))222 self.assertItemsEqual(expected, list_all_usable_osystems())
233 self.assertItemsEqual(expected, list_all_usable_osystems())223
234224 def test_list_all_usable_osystems_sorts_output(self):
235 def test_list_all_usable_osystems_sorts_output(self):225 osystem_names = [factory.make_name('os') for _ in range(3)]
236 osystem_names = [factory.make_name('os') for _ in range(3)]226 expected = []
237 expected = []227 for name in osystem_names:
238 for name in osystem_names:228 self.make_usable_boot_images(osystem=name)
239 self.make_usable_boot_images(osystem=name)229 expected.append(make_usable_osystem(self, name))
240 expected.append(make_usable_osystem(self, name))230 expected = sorted(expected, key=lambda osystem: osystem.title)
241 expected = sorted(expected, key=lambda osystem: osystem.title)231 self.assertEqual(expected, list_all_usable_osystems())
242 self.assertEqual(expected, list_all_usable_osystems())232
243233 def test_list_all_usable_osystems_returns_no_duplicates(self):
244 def test_list_all_usable_osystems_returns_no_duplicates(self):234 os_name = factory.make_name('os')
245 os_name = factory.make_name('os')235 self.make_usable_boot_images(osystem=os_name)
246 self.make_usable_boot_images(osystem=os_name)236 self.make_usable_boot_images(osystem=os_name)
247 self.make_usable_boot_images(osystem=os_name)237 osystem = make_usable_osystem(self, os_name)
248 osystem = make_usable_osystem(self, os_name)238 self.assertEqual(
249 self.assertEqual(239 [osystem], list_all_usable_osystems())
250 [osystem], list_all_usable_osystems())240
251241 def test_list_all_usable_releases_combines_nodegroups(self):
252 def test_list_all_usable_releases_combines_nodegroups(self):242 expected = {}
253 expected = {}243 osystems = []
254 osystems = []244 os_names = [factory.make_name('os') for _ in range(3)]
255 os_names = [factory.make_name('os') for _ in range(3)]245 for name in os_names:
256 for name in os_names:246 releases = [factory.make_name('release') for _ in range(3)]
257 releases = [factory.make_name('release') for _ in range(3)]247 for release in releases:
258 for release in releases:248 self.make_usable_boot_images(osystem=name, release=release)
259 self.make_usable_boot_images(osystem=name, release=release)249 osystems.append(
260 osystems.append(250 make_usable_osystem(self, name, releases=releases))
261 make_usable_osystem(self, name, releases=releases))251 expected[name] = releases
262 expected[name] = releases252 self.assertItemsEqual(expected, list_all_usable_releases(osystems))
263 self.assertItemsEqual(expected, list_all_usable_releases(osystems))253
264254 def test_list_all_usable_releases_sorts_output(self):
265 def test_list_all_usable_releases_sorts_output(self):255 expected = {}
266 expected = {}256 osystems = []
267 osystems = []257 os_names = [factory.make_name('os') for _ in range(3)]
268 os_names = [factory.make_name('os') for _ in range(3)]258 for name in os_names:
269 for name in os_names:259 releases = [factory.make_name('release') for _ in range(3)]
270 releases = [factory.make_name('release') for _ in range(3)]260 for release in releases:
271 for release in releases:261 self.make_usable_boot_images(osystem=name, release=release)
272 self.make_usable_boot_images(osystem=name, release=release)262 osystems.append(
273 osystems.append(263 make_usable_osystem(self, name, releases=releases))
274 make_usable_osystem(self, name, releases=releases))264 expected[name] = sorted(releases)
275 expected[name] = sorted(releases)265 self.assertEqual(expected, list_all_usable_releases(osystems))
276 self.assertEqual(expected, list_all_usable_releases(osystems))266
277267 def test_list_all_usable_releases_returns_no_duplicates(self):
278 def test_list_all_usable_releases_returns_no_duplicates(self):268 os_name = factory.make_name('os')
279 os_name = factory.make_name('os')269 release = factory.make_name('release')
280 release = factory.make_name('release')270 self.make_usable_boot_images(osystem=os_name, release=release)
281 self.make_usable_boot_images(osystem=os_name, release=release)271 self.make_usable_boot_images(osystem=os_name, release=release)
282 self.make_usable_boot_images(osystem=os_name, release=release)272 osystem = make_usable_osystem(self, os_name, releases=[release])
283 osystem = make_usable_osystem(self, os_name, releases=[release])273 expected = {}
284 expected = {}274 expected[os_name] = [release]
285 expected[os_name] = [release]275 self.assertEqual(expected, list_all_usable_releases([osystem]))
286 self.assertEqual(expected, list_all_usable_releases([osystem]))276
287
288=======
289 def test_list_all_usable_osystems_combines_nodegroups(self):
290 osystem_names = [factory.make_name('os') for _ in range(3)]
291 expected = []
292 for name in osystem_names:
293 self.make_usable_boot_images(osystem=name)
294 expected.append(make_usable_osystem(self, name))
295 self.assertItemsEqual(expected, list_all_usable_osystems())
296
297 def test_list_all_usable_osystems_sorts_output(self):
298 osystem_names = [factory.make_name('os') for _ in range(3)]
299 expected = []
300 for name in osystem_names:
301 self.make_usable_boot_images(osystem=name)
302 expected.append(make_usable_osystem(self, name))
303 expected = sorted(expected, key=lambda osystem: osystem.title)
304 self.assertEqual(expected, list_all_usable_osystems())
305
306 def test_list_all_usable_osystems_returns_no_duplicates(self):
307 os_name = factory.make_name('os')
308 self.make_usable_boot_images(osystem=os_name)
309 self.make_usable_boot_images(osystem=os_name)
310 osystem = make_usable_osystem(self, os_name)
311 self.assertEqual(
312 [osystem], list_all_usable_osystems())
313
314 def test_list_all_usable_releases_combines_nodegroups(self):
315 expected = {}
316 osystems = []
317 os_names = [factory.make_name('os') for _ in range(3)]
318 for name in os_names:
319 releases = [factory.make_name('release') for _ in range(3)]
320 for release in releases:
321 self.make_usable_boot_images(osystem=name, release=release)
322 osystems.append(
323 make_usable_osystem(self, name, releases=releases))
324 expected[name] = releases
325 self.assertItemsEqual(expected, list_all_usable_releases(osystems))
326
327 def test_list_all_usable_releases_sorts_output(self):
328 expected = {}
329 osystems = []
330 os_names = [factory.make_name('os') for _ in range(3)]
331 for name in os_names:
332 releases = [factory.make_name('release') for _ in range(3)]
333 for release in releases:
334 self.make_usable_boot_images(osystem=name, release=release)
335 osystems.append(
336 make_usable_osystem(self, name, releases=releases))
337 expected[name] = sorted(releases)
338 self.assertEqual(expected, list_all_usable_releases(osystems))
339
340 def test_list_all_usable_releases_returns_no_duplicates(self):
341 os_name = factory.make_name('os')
342 release = factory.make_name('release')
343 self.make_usable_boot_images(osystem=os_name, release=release)
344 self.make_usable_boot_images(osystem=os_name, release=release)
345 osystem = make_usable_osystem(self, os_name, releases=[release])
346 expected = {}
347 expected[os_name] = [release]
348 self.assertEqual(expected, list_all_usable_releases([osystem]))
349
350>>>>>>> MERGE-SOURCE
351 def test_remove_None_values_removes_None_values_in_dict(self):277 def test_remove_None_values_removes_None_values_in_dict(self):
352 random_input = factory.getRandomString()278 random_input = factory.getRandomString()
353 self.assertEqual(279 self.assertEqual(
@@ -2136,8 +2062,8 @@
21362062
2137 def test_edits_boot_source_selection_object(self):2063 def test_edits_boot_source_selection_object(self):
2138 boot_source_selection = factory.make_boot_source_selection()2064 boot_source_selection = factory.make_boot_source_selection()
2139 new_release = factory.getRandomChoice(2065 ubuntu_os = UbuntuOS()
2140 DISTRO_SERIES_CHOICES)2066 new_release = factory.getRandomRelease(ubuntu_os)
2141 params = {2067 params = {
2142 'release': new_release,2068 'release': new_release,
2143 'arches': [factory.make_name('arch'), factory.make_name('arch')],2069 'arches': [factory.make_name('arch'), factory.make_name('arch')],
@@ -2154,8 +2080,8 @@
21542080
2155 def test_creates_boot_source_selection_object(self):2081 def test_creates_boot_source_selection_object(self):
2156 boot_source = factory.make_boot_source()2082 boot_source = factory.make_boot_source()
2157 new_release = factory.getRandomChoice(2083 ubuntu_os = UbuntuOS()
2158 DISTRO_SERIES_CHOICES)2084 new_release = factory.getRandomRelease(ubuntu_os)
2159 params = {2085 params = {
2160 'release': new_release,2086 'release': new_release,
2161 'arches': [factory.make_name('arch'), factory.make_name('arch')],2087 'arches': [factory.make_name('arch'), factory.make_name('arch')],
21622088
=== modified file 'src/maasserver/views/tests/test_settings.py'
--- src/maasserver/views/tests/test_settings.py 2014-05-29 18:21:27 +0000
+++ src/maasserver/views/tests/test_settings.py 2014-05-15 19:48:27 +0000
@@ -20,10 +20,6 @@
20from django.contrib.auth.models import User20from django.contrib.auth.models import User
21from django.core.urlresolvers import reverse21from django.core.urlresolvers import reverse
22from lxml.html import fromstring22from lxml.html import fromstring
23<<<<<<< TREE
24=======
25from maasserver.models.config import DEFAULT_OS
26>>>>>>> MERGE-SOURCE
27from maasserver.models import (23from maasserver.models import (
28 Config,24 Config,
29 UserProfile,25 UserProfile,
3026
=== modified file 'src/provisioningserver/driver/__init__.py'
--- src/provisioningserver/driver/__init__.py 2014-05-15 17:27:53 +0000
+++ src/provisioningserver/driver/__init__.py 2014-05-29 18:21:27 +0000
@@ -158,6 +158,18 @@
158 :returns: list of supported purposes158 :returns: list of supported purposes
159 """159 """
160160
161 def compose_preseed(self, node, token, metadata_url):
162 """Composes the preseed for the given node.
163
164 :param node: Node preseed needs generating.
165 :param token: OAuth token for url.
166 :param metadata_url: Metdata url for node.
167 :returns: Preseed for node.
168 :raise:
169 NotImplementedError: doesn't implement a custom preseed
170 """
171 raise NotImplementedError()
172
161173
162class HardwareDiscoverContext:174class HardwareDiscoverContext:
163175