Merge lp:~justinkov/maas/ephemeral into lp:~maas-committers/maas/trunk

Proposed by Justin Kovaric
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~justinkov/maas/ephemeral
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 620 lines (+289/-17)
17 files modified
src/maasserver/api/tests/test_ipranges.py (+29/-3)
src/maasserver/bootresources.py (+7/-1)
src/maasserver/forms_iprange.py (+4/-0)
src/maasserver/models/bootresource.py (+22/-0)
src/maasserver/models/node.py (+13/-1)
src/maasserver/rpc/boot.py (+54/-0)
src/maasserver/rpc/tests/test_boot.py (+56/-0)
src/maasserver/testing/architecture.py (+6/-4)
src/maasserver/testing/factory.py (+5/-3)
src/maasserver/utils/osystems.py (+11/-0)
src/provisioningserver/boot/pxe.py (+5/-1)
src/provisioningserver/boot/tftppath.py (+2/-1)
src/provisioningserver/drivers/osystem/__init__.py (+4/-1)
src/provisioningserver/drivers/osystem/caringo.py (+45/-0)
src/provisioningserver/kernel_opts.py (+1/-1)
src/provisioningserver/rackdservices/tftp.py (+17/-1)
src/provisioningserver/templates/pxe/config.ephemeral.amd64.generic.template (+8/-0)
To merge this branch: bzr merge lp:~justinkov/maas/ephemeral
Reviewer Review Type Date Requested Status
Blake Rouse (community) Needs Fixing
Mike Pontillo Pending
Review via email: mp+316056@code.launchpad.net

This proposal supersedes a proposal from 2017-01-26.

Commit message

Add ability for an OS to be deployed ephemerally.

Description of the change

Ephemeral OS support.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote : Posted in a previous version of this proposal

Thanks for submitting this merge proposal.

I may not be the correct person to review this change, because I'm not sure I understand the goals for this code.

Overall, this branch looks okay, but there are some things that need fixing. I've made some comments inline below.

The MAAS team aims for 100% code coverage with unit tests, so you'll also need to add those before we can accept this branch.

Thanks for your time and effort on this. Let us know if you have questions.

review: Needs Fixing
Revision history for this message
Justin Kovaric (justinkov) wrote : Posted in a previous version of this proposal

Added some responses below.

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

Overall the implementation looks great and the code is clean. It still is missing the level of tests that we require before merging a branch.

review: Needs Fixing
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Justin, any updates?

Revision history for this message
MAAS Lander (maas-lander) wrote :

Transitioned to Git.

lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.

git clone https://git.launchpad.net/maas

Unmerged revisions

5656. By Justin Kovaric <email address hidden> <email address hidden>

added comment

5655. By Justin Kovaric <email address hidden> <email address hidden>

more unit tests.

5654. By Justin Kovaric <email address hidden> <email address hidden>

more unit tests

5653. By Justin Kovaric <email address hidden> <email address hidden>

Added unit test to test ip range addition through api with form encoded data since Django has different ways of handling input.

5652. By Justin Kovaric <email address hidden> <email address hidden>

Unit test for new kparams attribute of boot resource file.

5651. By Justin Kovaric <email address hidden> <email address hidden>

changes from make lint run

5650. By Justin Kovaric <email address hidden> <email address hidden>

results of make format and make lint.

5649. By Justin Kovaric <email address hidden> <email address hidden>

Added comment.

5648. By Justin Kovaric <email address hidden> <email address hidden>

Fix so that ephemeral image doesn't boot from local on restart.

5647. By Justin Kovaric <email address hidden> <email address hidden>

removed check for root-dd (not needed)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/tests/test_ipranges.py'
2--- src/maasserver/api/tests/test_ipranges.py 2016-05-24 22:05:45 +0000
3+++ src/maasserver/api/tests/test_ipranges.py 2017-01-31 22:50:18 +0000
4@@ -8,6 +8,7 @@
5 import http.client
6 import json
7 import random
8+from urllib import parse
9
10 from django.conf import settings
11 from django.core.urlresolvers import reverse
12@@ -76,6 +77,33 @@
13 self.assertThat(data['end_ip'], Equals('10.0.0.20'))
14 self.assertThat(data['subnet']['id'], Equals(subnet.id))
15
16+ def test_create_dynamic_encoded(self):
17+ """
18+ There is a quirk in Django where the resulting handler will get a
19+ mutable dictionary in some instances (with just a json body) and an
20+ immutable dictionary in others (In the case for form encoded body).
21+
22+ Specifying a content type of x-www-form-urlencoded should cause
23+ Django to produce an immutable dictionary.
24+
25+ :return:
26+ """
27+ self.become_admin()
28+ uri = get_ipranges_uri()
29+ subnet = factory.make_Subnet(cidr='10.0.0.0/24')
30+ response = self.client.post(uri, parse.urlencode(
31+ {"type": "dynamic",
32+ "start_ip": "10.0.0.10",
33+ "end_ip": "10.0.0.20",
34+ "subnet": "%d" % subnet.id}),
35+ 'application/x-www-form-urlencoded')
36+ self.assertEqual(
37+ http.client.OK, response.status_code, response.content)
38+ data = json.loads(response.content.decode(settings.DEFAULT_CHARSET))
39+ self.assertThat(data['start_ip'], Equals('10.0.0.10'))
40+ self.assertThat(data['end_ip'], Equals('10.0.0.20'))
41+ self.assertThat(data['subnet']['id'], Equals(subnet.id))
42+
43 def test_create_dynamic_requires_admin(self):
44 uri = get_ipranges_uri()
45 subnet = factory.make_Subnet(cidr='10.0.0.0/24')
46@@ -172,9 +200,7 @@
47 subnet, '10.0.0.2', '10.0.0.10', user=self.user)
48 uri = get_iprange_uri(iprange)
49 comment = factory.make_name("comment")
50- response = self.client.put(uri, {
51- "comment": comment,
52- })
53+ response = self.client.put(uri, {"comment": comment})
54 self.assertEqual(
55 http.client.OK, response.status_code, response.content)
56 self.assertEqual(
57
58=== modified file 'src/maasserver/bootresources.py'
59--- src/maasserver/bootresources.py 2016-12-07 12:46:14 +0000
60+++ src/maasserver/bootresources.py 2017-01-31 22:50:18 +0000
61@@ -592,8 +592,14 @@
62 # values to expose in the simplestreams endpoint on the region.
63 # src_{package,release,version} is useful in determining where the
64 # bootloader came from.
65+ #
66+ # Updated the list below to allow for a simplestream server to also
67+ # provide an extra field called 'kparams' which allows someone to also
68+ # specify kernel parameters that are unique to the release. This is
69+ # needed for certain ephemeral images.
70 for extra_key in [
71- 'kpackage', 'src_package', 'src_release', 'src_version']:
72+ 'kpackage', 'src_package', 'src_release', 'src_version',
73+ 'kparams']:
74 if extra_key in product:
75 rfile.extra[extra_key] = product[extra_key]
76
77
78=== modified file 'src/maasserver/forms_iprange.py'
79--- src/maasserver/forms_iprange.py 2016-10-17 06:42:10 +0000
80+++ src/maasserver/forms_iprange.py 2017-01-31 22:50:18 +0000
81@@ -7,6 +7,8 @@
82 "IPRangeForm",
83 ]
84
85+import copy
86+
87 from maasserver.forms import MAASModelForm
88 from maasserver.models import Subnet
89 from maasserver.models.iprange import IPRange
90@@ -30,6 +32,8 @@
91 self, data=None, instance=None, request=None, *args, **kwargs):
92 if data is None:
93 data = {}
94+ else:
95+ data = copy.deepcopy(data)
96 # If this is a new IPRange, fill in the 'user' and 'subnet' fields
97 # automatically, if necessary.
98 if instance is None:
99
100=== modified file 'src/maasserver/models/bootresource.py'
101--- src/maasserver/models/bootresource.py 2017-01-09 22:30:23 +0000
102+++ src/maasserver/models/bootresource.py 2017-01-31 22:50:18 +0000
103@@ -335,6 +335,28 @@
104 return kernel.extra['kpackage']
105 return None
106
107+ def get_kparams_for_node(self, node):
108+ """Return the kernel package name for the kernel specified."""
109+ arch = node.split_arch()[0]
110+ os_release = node.get_osystem() + '/' + node.get_distro_series()
111+
112+ # Before hwe_kernel was introduced the subarchitecture was the
113+ # hwe_kernel simple stream still uses this convention
114+ if node.hwe_kernel is None or node.hwe_kernel == '':
115+ hwe_arch = arch + '/generic'
116+ else:
117+ hwe_arch = arch + '/' + node.hwe_kernel
118+
119+ resource = self.filter(name=os_release, architecture=hwe_arch).first()
120+ if resource:
121+ latest_set = resource.get_latest_set()
122+ if latest_set:
123+ kernel = latest_set.files.filter(
124+ filetype=BOOT_RESOURCE_FILE_TYPE.BOOT_KERNEL).first()
125+ if kernel and 'kparams' in kernel.extra:
126+ return kernel.extra['kparams']
127+ return None
128+
129 def get_available_commissioning_resources(self):
130 """Return list of Ubuntu boot resources that can be used for
131 commissioning.
132
133=== modified file 'src/maasserver/models/node.py'
134--- src/maasserver/models/node.py 2017-01-27 09:42:18 +0000
135+++ src/maasserver/models/node.py 2017-01-31 22:50:18 +0000
136@@ -157,6 +157,7 @@
137 )
138 import petname
139 from piston3.models import Token
140+from provisioningserver.drivers.osystem import OperatingSystemRegistry
141 from provisioningserver.drivers.power.registry import PowerDriverRegistry
142 from provisioningserver.events import (
143 EVENT_DETAILS,
144@@ -2981,7 +2982,18 @@
145 # Install the node if netboot is enabled,
146 # otherwise boot locally.
147 if self.netboot:
148- return "xinstall"
149+ arch, subarch = self.split_arch()
150+ osystem_obj = OperatingSystemRegistry.get_item(self.osystem,
151+ default=None)
152+ if osystem_obj is None:
153+ return "xinstall"
154+
155+ purposes = osystem_obj.get_boot_image_purposes(arch, subarch,
156+ '', '*')
157+ if "ephemeral" in purposes:
158+ return "ephemeral"
159+ else:
160+ return "xinstall"
161 else:
162 return "local"
163 elif (self.status == NODE_STATUS.DEPLOYED or
164
165=== modified file 'src/maasserver/rpc/boot.py'
166--- src/maasserver/rpc/boot.py 2016-11-07 18:07:57 +0000
167+++ src/maasserver/rpc/boot.py 2017-01-31 22:50:18 +0000
168@@ -7,6 +7,9 @@
169 "get_config",
170 ]
171
172+import re
173+import shlex
174+
175 from django.core.exceptions import (
176 ObjectDoesNotExist,
177 ValidationError,
178@@ -64,6 +67,7 @@
179 'commissioning': "commissioning",
180 'rescue': "rescue mode",
181 'xinstall': "installation",
182+ 'ephemeral': "ephemeral",
183 'local': "local boot",
184 'poweroff': "power off",
185 }
186@@ -114,6 +118,52 @@
187 return kernel, initrd, boot_dtb
188
189
190+def merge_kparams_with_extra(kparams, extra_kernel_opts):
191+ if kparams is None or kparams == '':
192+ return extra_kernel_opts
193+
194+ """
195+ This section will merge the kparams with the extra opts. Our goal is to
196+ start with what is in kparams and then look to extra_opts for anything
197+ to add to or override settings in kparams. Anything in extra_opts, which
198+ can be set through tabs, takes precedence so we use that to start with.
199+ """
200+ final_params = ''
201+ if extra_kernel_opts is not None and extra_kernel_opts != '':
202+ # We need to remove spaces from the tempita subsitutions so the split
203+ # command works as desired.
204+ final_params = re.sub('{{\s*([\w\.]*)\s*}}', '{{\g<1>}}',
205+ extra_kernel_opts)
206+
207+ # Need to get a list of all kernel params in the extra opts.
208+ elist = []
209+ if len(final_params) > 0:
210+ tmp = shlex.split(final_params)
211+ for tparam in tmp:
212+ idx = tparam.find('=')
213+ key = tparam[0:idx]
214+ elist.append(key)
215+
216+ # Go through all the kernel params as normally set.
217+ new_kparams = re.sub('{{\s*([\w\.]*)\s*}}', '{{\g<1>}}', kparams)
218+ params = shlex.split(new_kparams)
219+ for param in params:
220+ idx = param.find('=')
221+ key = param[0:idx]
222+ value = param[idx + 1:len(param)]
223+
224+ # The call to split will remove quotes, so we add them back in if
225+ # needed.
226+ if value.find(" ") > 0:
227+ value = '"' + value + '"'
228+
229+ # if the param is not in extra_opts, use the one from here.
230+ if key not in elist:
231+ final_params = final_params + ' ' + key + '=' + value
232+
233+ return final_params
234+
235+
236 @synchronous
237 @transactional
238 def get_config(
239@@ -233,6 +283,10 @@
240 extra_kernel_opts = None
241 else:
242 extra_kernel_opts = effective_kernel_opts
243+
244+ kparams = BootResource.objects.get_kparams_for_node(machine)
245+ extra_kernel_opts = merge_kparams_with_extra(kparams,
246+ extra_kernel_opts)
247 else:
248 purpose = "commissioning" # enlistment
249 preseed_url = compose_enlistment_preseed_url(rack_controller)
250
251=== modified file 'src/maasserver/rpc/tests/test_boot.py'
252--- src/maasserver/rpc/tests/test_boot.py 2016-11-07 18:07:57 +0000
253+++ src/maasserver/rpc/tests/test_boot.py 2017-01-31 22:50:18 +0000
254@@ -35,6 +35,7 @@
255 from maasserver.testing.factory import factory
256 from maasserver.testing.testcase import MAASServerTestCase
257 from maasserver.utils.orm import reload_object
258+from maasserver.utils.osystems import get_release_from_distro_info
259 from maastesting.matchers import (
260 MockCalledOnceWith,
261 MockNotCalled,
262@@ -59,6 +60,23 @@
263 architecture="%s/generic" % architecture.split('/')[0],
264 **kwargs)
265
266+ def make_node_with_extra(self, arch_name=None, extra=None, **kwargs):
267+ """
268+ Need since if we pass "extra" as part of kwargs, the code that creates
269+ a node will fail since "extra" isn't a valid parameter for that code
270+ path.
271+
272+ :param arch_name:
273+ :param extra:
274+ :param kwargs:
275+ :return:
276+ """
277+ architecture = make_usable_architecture(self, arch_name=arch_name,
278+ extra=extra)
279+ return factory.make_Node_with_Interface_on_Subnet(
280+ architecture="%s/generic" % architecture.split('/')[0],
281+ **kwargs)
282+
283 def test__returns_all_kernel_parameters(self):
284 rack_controller = factory.make_RackController()
285 local_ip = factory.make_ip_address()
286@@ -92,6 +110,44 @@
287 # Should not raise BootConfigNoResponse.
288 get_config(rack_controller.system_id, local_ip, remote_ip, mac=mac)
289
290+ def test__returns_kparams_for_known_node(self):
291+ rack_controller = factory.make_RackController()
292+ local_ip = factory.make_ip_address()
293+ remote_ip = factory.make_ip_address()
294+
295+ """
296+ The make_node function will result in a boot resource being created
297+ with an architecture that looks like "arch-YYY/hwe-Z", with YYY being
298+ a random string and Z being the first letter of the default
299+ commissioning image. If we don't create a node with this same kernel
300+ name, the node will use an architecture name of arch-YYY/generic which
301+ means the get_config won't find the matching boot resource file with
302+ the kparams attribute.
303+ """
304+ default_series = Config.objects.get_config(
305+ name='commissioning_distro_series')
306+ release = get_release_from_distro_info(default_series)
307+ hwe_kernel = "hwe-%s" % (release['version'].split()[0])
308+
309+ node = self.make_node_with_extra(status=NODE_STATUS.DEPLOYING,
310+ extra={'kparams': 'a=b'},
311+ hwe_kernel=hwe_kernel)
312+
313+ """
314+ Create a tag so that we can make sure the kparams attribute got merged
315+ with the tag's kernel_opts attribute.
316+ """
317+ tag = factory.make_Tag(kernel_opts="b=c")
318+ node.tags.add(tag)
319+
320+ mac = node.get_boot_interface().mac_address
321+ config = get_config(rack_controller.system_id, local_ip, remote_ip,
322+ mac=mac)
323+ extra = config.get('extra_opts', None)
324+
325+ self.assertIn('b=c', extra)
326+ self.assertIn('a=b', extra)
327+
328 def test__raises_BootConfigNoResponse_for_unknown_node(self):
329 rack_controller = factory.make_RackController()
330 local_ip = factory.make_ip_address()
331
332=== modified file 'src/maasserver/testing/architecture.py'
333--- src/maasserver/testing/architecture.py 2016-08-30 18:45:05 +0000
334+++ src/maasserver/testing/architecture.py 2017-01-31 22:50:18 +0000
335@@ -14,7 +14,8 @@
336 from maasserver.testing.factory import factory
337
338
339-def make_arch(with_subarch=True, arch_name=None, subarch_name=None):
340+def make_arch(with_subarch=True, arch_name=None, subarch_name=None,
341+ extra=None):
342 """Generate an arbitrary architecture name.
343
344 :param with_subarch: Should the architecture include a slash and a
345@@ -22,7 +23,7 @@
346 """
347 if arch_name is None:
348 arch_name = factory.make_name('arch')
349- factory.make_default_ubuntu_release_bootable(arch_name)
350+ factory.make_default_ubuntu_release_bootable(arch_name, extra=extra)
351 if with_subarch:
352 if subarch_name is None:
353 subarch_name = factory.make_name('sub')
354@@ -50,7 +51,8 @@
355
356
357 def make_usable_architecture(
358- testcase, with_subarch=True, arch_name=None, subarch_name=None):
359+ testcase, with_subarch=True, arch_name=None, subarch_name=None,
360+ extra=None):
361 """Return arbitrary architecture name, and make it "usable."
362
363 A usable architecture is one for which boot images are available.
364@@ -66,6 +68,6 @@
365 """
366 arch = make_arch(
367 with_subarch=with_subarch, arch_name=arch_name,
368- subarch_name=subarch_name)
369+ subarch_name=subarch_name, extra=extra)
370 patch_usable_architectures(testcase, [arch])
371 return arch
372
373=== modified file 'src/maasserver/testing/factory.py'
374--- src/maasserver/testing/factory.py 2017-01-26 18:51:52 +0000
375+++ src/maasserver/testing/factory.py 2017-01-31 22:50:18 +0000
376@@ -1660,7 +1660,8 @@
377 # cases this will always be true. The simplestreams content from
378 # maas.io, is formatted this way.
379 self.make_boot_resource_file_with_content(
380- resource_set, filename=filename, filetype=filetype, size=None)
381+ resource_set, filename=filename, filetype=filetype, size=None,
382+ extra=extra)
383 return resource
384
385 def make_incomplete_boot_resource(
386@@ -1690,7 +1691,7 @@
387 size=size, content=content)
388 return resource
389
390- def make_default_ubuntu_release_bootable(self, arch=None):
391+ def make_default_ubuntu_release_bootable(self, arch=None, extra=None):
392 if arch is None:
393 arch = self.make_name('arch')
394 default_osystem = Config.objects.get_config(
395@@ -1708,7 +1709,8 @@
396 with transaction.atomic():
397 return self.make_usable_boot_resource(
398 name=default_name, architecture=architecture,
399- kflavor='generic', rtype=BOOT_RESOURCE_TYPE.SYNCED)
400+ kflavor='generic', rtype=BOOT_RESOURCE_TYPE.SYNCED,
401+ extra=extra)
402
403 def make_BlockDevice(
404 self, node=None, name=None, id_path=None, size=None,
405
406=== modified file 'src/maasserver/utils/osystems.py'
407--- src/maasserver/utils/osystems.py 2017-01-10 09:53:51 +0000
408+++ src/maasserver/utils/osystems.py 2017-01-31 22:50:18 +0000
409@@ -30,6 +30,7 @@
410 BootSourceCache,
411 Config,
412 )
413+from provisioningserver.drivers.osystem import OperatingSystemRegistry
414
415
416 def list_all_usable_osystems():
417@@ -409,6 +410,16 @@
418 (not distro_series or distro_series == '')):
419 return hwe_kernel
420
421+ # If we are dealing with an ephemeral image, just return the hwe_kernel
422+ # as-is, i.e. just stick with generic.
423+ osystem_obj = OperatingSystemRegistry.get_item(osystem, default=None)
424+ if osystem_obj is not None:
425+ arch, subarch = architecture.split('/')
426+ purposes = osystem_obj.get_boot_image_purposes(arch, subarch,
427+ distro_series, '*')
428+ if "ephemeral" in purposes:
429+ return hwe_kernel
430+
431 # If we're not deploying Ubuntu we are just setting the kernel to be used
432 # during deployment
433 if osystem != "ubuntu":
434
435=== modified file 'src/provisioningserver/boot/pxe.py'
436--- src/provisioningserver/boot/pxe.py 2016-12-07 12:46:14 +0000
437+++ src/provisioningserver/boot/pxe.py 2017-01-31 22:50:18 +0000
438@@ -26,6 +26,7 @@
439 atomic_copy,
440 atomic_symlink,
441 )
442+import tempita
443
444
445 maaslog = get_maas_logger('pxe')
446@@ -113,8 +114,11 @@
447 template = self.get_template(
448 kernel_params.purpose, kernel_params.arch,
449 kernel_params.subarch)
450+ kernel_params.mac = extra.get('mac', '')
451 namespace = self.compose_template_namespace(kernel_params)
452- return BytesReader(template.substitute(namespace).encode("utf-8"))
453+ step1 = template.substitute(namespace)
454+ return BytesReader(tempita.Template(step1).substitute(namespace)
455+ .encode("utf-8"))
456
457 def _link_simplestream_bootloaders(self, stream_path, destination):
458 super()._link_simplestream_bootloaders(stream_path, destination)
459
460=== modified file 'src/provisioningserver/boot/tftppath.py'
461--- src/provisioningserver/boot/tftppath.py 2016-09-30 19:08:51 +0000
462+++ src/provisioningserver/boot/tftppath.py 2017-01-31 22:50:18 +0000
463@@ -161,7 +161,8 @@
464 image = dict(
465 osystem=osystem, architecture=arch, subarchitecture=subarch,
466 release=release, label=label, purpose=purpose)
467- if purpose == BOOT_IMAGE_PURPOSE.XINSTALL:
468+ if purpose == BOOT_IMAGE_PURPOSE.XINSTALL \
469+ or purpose == BOOT_IMAGE_PURPOSE.EPHEMERAL:
470 xinstall_path, xinstall_type = osystem_obj.get_xinstall_parameters(
471 arch, subarch, release, label)
472 image['xinstall_path'] = xinstall_path
473
474=== modified file 'src/provisioningserver/drivers/osystem/__init__.py'
475--- src/provisioningserver/drivers/osystem/__init__.py 2016-09-12 21:33:05 +0000
476+++ src/provisioningserver/drivers/osystem/__init__.py 2017-01-31 22:50:18 +0000
477@@ -32,7 +32,8 @@
478 DISKLESS = 'diskless'
479 #: Bootloader for enlistment, commissioning, and deployment
480 BOOTLOADER = 'bootloader'
481-
482+ #: Usable for ephemeral boots
483+ EPHEMERAL = 'ephemeral'
484
485 # A cluster-side representation of a Node, relevant to the osystem code,
486 # with only minimal fields.
487@@ -207,6 +208,7 @@
488 from provisioningserver.drivers.osystem.custom import CustomOS
489 from provisioningserver.drivers.osystem.windows import WindowsOS
490 from provisioningserver.drivers.osystem.suse import SUSEOS
491+from provisioningserver.drivers.osystem.caringo import CaringoOS
492
493 builtin_osystems = [
494 UbuntuOS(),
495@@ -215,6 +217,7 @@
496 CustomOS(),
497 WindowsOS(),
498 SUSEOS(),
499+ CaringoOS(),
500 ]
501 for osystem in builtin_osystems:
502 OperatingSystemRegistry.register_item(osystem.name, osystem)
503
504=== added file 'src/provisioningserver/drivers/osystem/caringo.py'
505--- src/provisioningserver/drivers/osystem/caringo.py 1970-01-01 00:00:00 +0000
506+++ src/provisioningserver/drivers/osystem/caringo.py 2017-01-31 22:50:18 +0000
507@@ -0,0 +1,45 @@
508+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
509+# GNU Affero General Public License version 3 (see the file LICENSE).
510+
511+"""Operating System class used for custom images."""
512+
513+__all__ = [
514+ "CaringoOS",
515+ ]
516+
517+from provisioningserver.drivers.osystem import (
518+ BOOT_IMAGE_PURPOSE,
519+ OperatingSystem,
520+)
521+
522+
523+class CaringoOS(OperatingSystem):
524+ """Custom operating system."""
525+
526+ name = "caringo"
527+ title = "Caringo"
528+
529+ def get_boot_image_purposes(self, arch, subarch, release, label):
530+ """Gets the purpose of each boot image."""
531+ return [BOOT_IMAGE_PURPOSE.EPHEMERAL]
532+
533+ def is_release_supported(self, release):
534+ """Return True when the release is supported, False otherwise."""
535+ # All release are supported, since the user uploaded it.
536+ return True
537+
538+ def get_default_release(self):
539+ """Gets the default release to use when a release is not
540+ explicit."""
541+ # No default for this OS.
542+ return ""
543+
544+ def get_release_title(self, release):
545+ """Return the title for the given release."""
546+ # Return the same name, since the cluster does not know about the
547+ # title of the image. The region will fix the title for the UI.
548+ return release
549+
550+ def get_xinstall_parameters(self, arch, subarch, release, label):
551+ """Returns the xinstall image name and type for given image."""
552+ return "root-tgz", "tgz"
553
554=== modified file 'src/provisioningserver/kernel_opts.py'
555--- src/provisioningserver/kernel_opts.py 2016-12-20 18:03:49 +0000
556+++ src/provisioningserver/kernel_opts.py 2017-01-31 22:50:18 +0000
557@@ -121,7 +121,7 @@
558
559 def compose_purpose_opts(params):
560 """Return the list of the purpose-specific kernel options."""
561- if params.purpose in ["commissioning", "xinstall", "enlist"]:
562+ if params.purpose in ["commissioning", "xinstall", "enlist", "ephemeral"]:
563 # These are kernel parameters read by the ephemeral environment.
564 tname = prefix_target_name(
565 get_ephemeral_name(
566
567=== modified file 'src/provisioningserver/rackdservices/tftp.py'
568--- src/provisioningserver/rackdservices/tftp.py 2016-12-07 12:46:14 +0000
569+++ src/provisioningserver/rackdservices/tftp.py 2017-01-31 22:50:18 +0000
570@@ -20,6 +20,7 @@
571 get_remote_mac,
572 )
573 from provisioningserver.drivers import ArchitectureRegistry
574+from provisioningserver.drivers.osystem import OperatingSystemRegistry
575 from provisioningserver.events import (
576 EVENT_TYPES,
577 send_node_event_mac_address,
578@@ -182,13 +183,28 @@
579
580 Calls `MarkNodeFailed` for the machine if its a known machine.
581 """
582+ is_ephemeral = False
583+ try:
584+ osystem_obj = OperatingSystemRegistry.get_item(params['osystem'],
585+ default=None)
586+ purposes = osystem_obj \
587+ .get_boot_image_purposes(params["arch"], params["subarch"],
588+ params.get("release", ""),
589+ params.get("label", ""))
590+ if "ephemeral" in purposes:
591+ is_ephemeral = True
592+ except:
593+ pass
594+
595 system_id = params.pop("system_id", None)
596- if params["purpose"] == "local":
597+ if params["purpose"] == "local" and not is_ephemeral:
598 # Local purpose doesn't use a boot image so jsut set the label
599 # to "local", but this value will no be used.
600 params["label"] = "local"
601 return params
602 else:
603+ if params["purpose"] == "local" and is_ephemeral:
604+ params["purpose"] = "ephemeral"
605 boot_image = get_boot_image(params)
606 if boot_image is None:
607 # No matching boot image.
608
609=== added file 'src/provisioningserver/templates/pxe/config.ephemeral.amd64.generic.template'
610--- src/provisioningserver/templates/pxe/config.ephemeral.amd64.generic.template 1970-01-01 00:00:00 +0000
611+++ src/provisioningserver/templates/pxe/config.ephemeral.amd64.generic.template 2017-01-31 22:50:18 +0000
612@@ -0,0 +1,8 @@
613+DEFAULT execute
614+
615+LABEL execute
616+ SAY booting ephemeral image...
617+ SAY extra={{ kernel_params.extra_opts }}
618+ KERNEL {{kernel_params | kernel_path }}
619+ APPEND initrd={{kernel_params | initrd_path }} {{ kernel_params.extra_opts }} maas_url={{kernel_params.preseed_url }}
620+