Merge lp:~raharper/curtin/trunk.snappy-dd into lp:~curtin-dev/curtin/trunk

Proposed by Ryan Harper on 2017-02-17
Status: Merged
Approved by: Ryan Harper on 2017-03-23
Approved revision: 422
Merged at revision: 479
Proposed branch: lp:~raharper/curtin/trunk.snappy-dd
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 738 lines (+485/-38)
11 files modified
curtin/block/__init__.py (+19/-10)
curtin/commands/block_meta.py (+16/-4)
curtin/commands/curthooks.py (+85/-0)
curtin/util.py (+3/-2)
examples/tests/ubuntu_core.yaml (+13/-0)
tests/unittests/test_commands_block_meta.py (+104/-1)
tests/unittests/test_curthooks.py (+144/-0)
tests/vmtests/__init__.py (+26/-12)
tests/vmtests/releases.py (+17/-0)
tests/vmtests/test_ubuntu_core.py (+46/-0)
tools/launch (+12/-9)
To merge this branch: bzr merge lp:~raharper/curtin/trunk.snappy-dd
Reviewer Review Type Date Requested Status
Scott Moser 2017-02-17 Approve on 2017-03-22
Review via email: mp+317672@code.launchpad.net

Commit Message

Add support for installing Ubuntu-Core 16 images

UC16 images are compressed-xz raw images which need to be written
directly to disk. Introduce new source types dd-{xz,gz,bz2,raw}
and pass them to block_meta write_image_to_disk. Update block_meta's
find_root_dev to look for UC16 specific paths to identify the root
partition.

UC16 images are generally not modifiable and do not contain dpkg/apt,
therefore curtin handles curthooks on UC16 differently. Add a new
top-level 'cloudconfig' key which is modeled after write_files to be used
by maas to inject cloud-init into a UC16 image; curtin appends these
files out in the "writable" partition of UC16 which will be mounted up on
top of the root partition. Add an initial vmtest (disabled by default) as
UC16 images are not yet available via maas streams data.

Description of the Change

Add support for installing Ubuntu-Core 16 images

UC16 images are compressed-xz raw images which need to be written
directly to disk. Introduce new source types dd-{xz,gz,bz2,raw}
and pass them to block_meta write_image_to_disk. Update block_meta's
find_root_dev to look for UC16 specific paths to identify the root
partition.

UC16 images are generally not modifiable and do not contain dpkg/apt,
therefore curtin handles curthooks on UC16 differently. Add a new
top-level 'cloudconfig' key which is modeled after write_files to be used
by maas to inject cloud-init into a UC16 image; curtin appends these
files out in the "writable" partition of UC16 which will be mounted up on
top of the root partition. Add an initial vmtest; currently a hack since
UC16 images are not yet available via maas streams data.

To post a comment you must log in.
lp:~raharper/curtin/trunk.snappy-dd updated on 2017-02-17
405. By Ryan Harper on 2017-02-17

Fix flake8 issues

Scott Moser (smoser) :
lp:~raharper/curtin/trunk.snappy-dd updated on 2017-03-22
406. By Ryan Harper on 2017-03-21

refactor snappy_curthooks; add handle_cloudconfig hook

407. By Ryan Harper on 2017-03-21

merge from smoser snappy branch

408. By Ryan Harper on 2017-03-21

remove testing tools

409. By Ryan Harper on 2017-03-22

unittest: block_meta.meta_simple and write_image_to_disk path

410. By Ryan Harper on 2017-03-22

revert smtar changes

411. By Ryan Harper on 2017-03-22

Update supported source types and extractor mappings

412. By Ryan Harper on 2017-03-22

Remove debugging from block_meta

413. By Ryan Harper on 2017-03-22

Rename Snappy->UbuntuCore; Add UbuntuCore curthooks unittets

414. By Ryan Harper on 2017-03-22

unittest: add ubuntu-core-network-config

415. By Ryan Harper on 2017-03-22

unittest: ubuntu-core check remove cloud-init.disabled if present

416. By Ryan Harper on 2017-03-22

unittest: handle_cloudconfig invalid config

417. By Ryan Harper on 2017-03-22

Drop useless S (--sparse) flag on extraction; add unittest for dd-tgz cmdline

418. By Ryan Harper on 2017-03-22

Use 'systemd-data/var/lib/snapd' to confirm target is Ubuntu-Core

419. By Ryan Harper on 2017-03-22

remove added whitespace

420. By Ryan Harper on 2017-03-22

merge from trunk

421. By Ryan Harper on 2017-03-22

vmtest: disable ubuntu-core until images are published in simplestreams

Scott Moser (smoser) :
Scott Moser (smoser) wrote :

just make those changes and i think its good.

review: Approve
lp:~raharper/curtin/trunk.snappy-dd updated on 2017-03-22
422. By Ryan Harper on 2017-03-22

Address remaining review comments

Ryan Harper (raharper) wrote :

Applied changes, running vmtest for regression checks here:

https://jenkins.ubuntu.com/server/job/curtin-vmtest-devel-debug/2/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'curtin/block/__init__.py'
2--- curtin/block/__init__.py 2017-02-08 06:02:02 +0000
3+++ curtin/block/__init__.py 2017-03-22 21:04:27 +0000
4@@ -540,10 +540,15 @@
5 return multipath_wwids
6
7
8-def get_root_device(dev, fpath="curtin"):
9- """
10- Get root partition for specified device, based on presence of /curtin.
11- """
12+def get_root_device(dev, paths=None):
13+ """
14+ Get root partition for specified device, based on presence of any
15+ paths in the provided paths list:
16+ """
17+ if paths is None:
18+ paths = ["curtin"]
19+ LOG.debug('Searching for filesystem on %s containing one of: %s',
20+ dev, paths)
21 partitions = get_pardevs_on_blockdevs(dev)
22 target = None
23 tmp_mount = tempfile.mkdtemp()
24@@ -553,10 +558,13 @@
25 try:
26 util.do_mount(dev_path, tmp_mount)
27 mp = tmp_mount
28- curtin_dir = os.path.join(tmp_mount, fpath)
29- if os.path.isdir(curtin_dir):
30- target = dev_path
31- break
32+ for path in paths:
33+ fullpath = os.path.join(tmp_mount, path)
34+ if os.path.isdir(fullpath):
35+ target = dev_path
36+ LOG.debug("Found path '%s' on device '%s'",
37+ path, dev_path)
38+ break
39 except Exception:
40 pass
41 finally:
42@@ -564,9 +572,10 @@
43 util.do_umount(mp)
44
45 os.rmdir(tmp_mount)
46-
47 if target is None:
48- raise ValueError("Could not find root device")
49+ raise ValueError(
50+ "Did not find any filesystem on %s that contained one of %s" %
51+ (dev, paths))
52 return target
53
54
55
56=== modified file 'curtin/commands/block_meta.py'
57--- curtin/commands/block_meta.py 2017-03-20 00:10:58 +0000
58+++ curtin/commands/block_meta.py 2017-03-22 21:04:27 +0000
59@@ -77,14 +77,26 @@
60 """
61 Write disk image to block device
62 """
63+ LOG.info('writing image to disk %s, %s', source, dev)
64+ extractor = {
65+ 'dd-tgz': '|tar -xOzf -',
66+ 'dd-txz': '|tar -xOJf -',
67+ 'dd-tbz': '|tar -xOjf -',
68+ 'dd-tar': '|smtar -xOf -',
69+ 'dd-bz2': '|bzcat',
70+ 'dd-gz': '|zcat',
71+ 'dd-xz': '|xzcat',
72+ 'dd-raw': ''
73+ }
74 (devname, devnode) = block.get_dev_name_entry(dev)
75 util.subp(args=['sh', '-c',
76- ('wget "$1" --progress=dot:mega -O - |'
77- 'tar -SxOzf - | dd of="$2"'),
78- '--', source, devnode])
79+ ('wget "$1" --progress=dot:mega -O - ' +
80+ extractor[source['type']] + '| dd bs=4M of="$2"'),
81+ '--', source['uri'], devnode])
82 util.subp(['partprobe', devnode])
83 udevadm_settle()
84- return block.get_root_device([devname, ])
85+ paths = ["curtin", "system-data/var/lib/snapd"]
86+ return block.get_root_device([devname], paths=paths)
87
88
89 def get_bootpt_cfg(cfg, enabled=False, fstype=None, root_fstype=None):
90
91=== modified file 'curtin/commands/curthooks.py'
92--- curtin/commands/curthooks.py 2017-02-11 18:15:51 +0000
93+++ curtin/commands/curthooks.py 2017-03-22 21:04:27 +0000
94@@ -672,6 +672,83 @@
95 util.system_upgrade(target=target)
96
97
98+def handle_cloudconfig(cfg, target=None):
99+ """write cloud-init configuration files into target
100+
101+ cloudconfig format is a dictionary of keys and values of content
102+
103+ cloudconfig:
104+ cfg-datasource:
105+ content:
106+ |
107+ #cloud-cfg
108+ datasource_list: [ MAAS ]
109+ cfg-maas:
110+ content:
111+ |
112+ #cloud-cfg
113+ reporting:
114+ maas: { consumer_key: 8cW9kadrWZcZvx8uWP,
115+ endpoint: 'http://XXX',
116+ token_key: jD57DB9VJYmDePCRkq,
117+ token_secret: mGFFMk6YFLA3h34QHCv22FjENV8hJkRX,
118+ type: webhook}
119+ """
120+ # check that cfg is dict
121+ if not isinstance(cfg, dict):
122+ raise ValueError("cloudconfig configuration is not in dict format")
123+
124+ # for each item in the dict
125+ # generate a path based on item key
126+ # if path is already in the item, LOG warning, and use generated path
127+ for cfgname, cfgvalue in cfg.items():
128+ cfgpath = "50-cloudconfig-%s.cfg" % cfgname
129+ if 'path' in cfgvalue:
130+ LOG.warning("cloudconfig ignoring 'path' key in config")
131+ cfgvalue['path'] = cfgpath
132+
133+ # re-use write_files format and adjust target to prepend
134+ LOG.debug('Calling write_files with cloudconfig @ %s', target)
135+ LOG.debug('Injecting cloud-config:\n%s', cfg)
136+ write_files({'write_files': cfg}, target)
137+
138+
139+def ubuntu_core_curthooks(cfg, target=None):
140+ """ Ubuntu-Core 16 images cannot execute standard curthooks
141+ Instead we copy in any cloud-init configuration to
142+ the 'LABEL=writable' partition mounted at target.
143+ """
144+
145+ ubuntu_core_target = os.path.join(target, "system-data")
146+ cc_target = os.path.join(ubuntu_core_target, 'etc/cloud/cloud.cfg.d')
147+
148+ cloudconfig = cfg.get('cloudconfig', None)
149+ if cloudconfig:
150+ # remove cloud-init.disabled, if found
151+ cloudinit_disable = os.path.join(ubuntu_core_target,
152+ 'etc/cloud/cloud-init.disabled')
153+ if os.path.exists(cloudinit_disable):
154+ util.del_file(cloudinit_disable)
155+
156+ handle_cloudconfig(cloudconfig, target=cc_target)
157+
158+ netconfig = cfg.get('network', None)
159+ if netconfig:
160+ LOG.info('Writing network configuration')
161+ ubuntu_core_netconfig = os.path.join(cc_target,
162+ "50-network-config.cfg")
163+ util.write_file(ubuntu_core_netconfig,
164+ content=config.dump_config(netconfig))
165+
166+
167+def target_is_ubuntu_core(target):
168+ """Check if Ubuntu-Core specific directory is present at target"""
169+ if target:
170+ return os.path.exists(util.target_path(target,
171+ 'system-data/var/lib/snapd'))
172+ return False
173+
174+
175 def curthooks(args):
176 state = util.load_command_environment()
177
178@@ -693,6 +770,14 @@
179 cfg = config.load_command_config(args, state)
180 stack_prefix = state.get('report_stack_prefix', '')
181
182+ if target_is_ubuntu_core(target):
183+ LOG.info('Detected Ubuntu-Core image, running hooks')
184+ with events.ReportEventStack(
185+ name=stack_prefix, reporting_enabled=True, level="INFO",
186+ description="Configuring Ubuntu-Core for first boot"):
187+ rv = ubuntu_core_curthooks(cfg, target)
188+ sys.exit(rv)
189+
190 with events.ReportEventStack(
191 name=stack_prefix + '/writing-config',
192 reporting_enabled=True, level="INFO",
193
194=== modified file 'curtin/util.py'
195--- curtin/util.py 2017-02-17 21:02:20 +0000
196+++ curtin/util.py 2017-03-22 21:04:27 +0000
197@@ -829,7 +829,8 @@
198 if type(source) is dict:
199 # already sanitized?
200 return source
201- supported = ['tgz', 'dd-tgz']
202+ supported = ['tgz', 'dd-tgz', 'dd-tbz', 'dd-txz', 'dd-tar', 'dd-bz2',
203+ 'dd-gz', 'dd-xz', 'dd-raw']
204 deftype = 'tgz'
205 for i in supported:
206 prefix = i + ":"
207@@ -852,7 +853,7 @@
208 if type(sources[i]) is not dict:
209 continue
210 if sources[i]['type'].startswith('dd-'):
211- src.append(sources[i]['uri'])
212+ src.append(sources[i])
213 return src
214
215
216
217=== added file 'examples/tests/ubuntu_core.yaml'
218--- examples/tests/ubuntu_core.yaml 1970-01-01 00:00:00 +0000
219+++ examples/tests/ubuntu_core.yaml 2017-03-22 21:04:27 +0000
220@@ -0,0 +1,13 @@
221+# This pushes curtin through a automatic installation
222+# where no storage configuration is necessary.
223+placeholder_simple_install: unused
224+showtrace: true
225+cloudconfig:
226+ curtin_vmtest:
227+ path: /etc/cloud/cloud.cfg.d/99-curtin-vmtest.cfg
228+ content:
229+ |
230+ #cloud-config
231+ snappy:
232+ email: raharper@gmail.com
233+ packages: [hello, part-numpy, part-cython]
234
235=== modified file 'tests/unittests/test_commands_block_meta.py'
236--- tests/unittests/test_commands_block_meta.py 2017-02-01 03:01:54 +0000
237+++ tests/unittests/test_commands_block_meta.py 2017-03-22 21:04:27 +0000
238@@ -1,5 +1,6 @@
239 from unittest import TestCase
240-from mock import patch
241+from mock import patch, call
242+from argparse import Namespace
243
244 from curtin.commands import block_meta
245
246@@ -17,6 +18,108 @@
247 setattr(self, attr, p)
248
249
250+class TestBlockMetaSimple(BlockMetaTestBase):
251+ def setUp(self):
252+ super(TestBlockMetaSimple, self).setUp()
253+ self.target = "my_target"
254+
255+ # block_meta
256+ basepath = 'curtin.commands.block_meta.'
257+ self.add_patch(basepath + 'get_bootpt_cfg', 'mock_bootpt_cfg')
258+ self.add_patch(basepath + 'get_partition_format_type',
259+ 'mock_part_fmt_type')
260+ # block
261+ self.add_patch('curtin.block.stop_all_unused_multipath_devices',
262+ 'mock_block_stop_mp')
263+ self.add_patch('curtin.block.get_installable_blockdevs',
264+ 'mock_block_get_installable_bdevs')
265+ self.add_patch('curtin.block.get_dev_name_entry',
266+ 'mock_block_get_dev_name_entry')
267+ self.add_patch('curtin.block.get_root_device',
268+ 'mock_block_get_root_device')
269+ self.add_patch('curtin.block.is_valid_device',
270+ 'mock_block_is_valid_device')
271+ # config
272+ self.add_patch('curtin.config.load_command_config',
273+ 'mock_config_load')
274+ # util
275+ self.add_patch('curtin.util.subp', 'mock_subp')
276+ self.add_patch('curtin.util.load_command_environment',
277+ 'mock_load_env')
278+
279+ def test_write_image_to_disk(self):
280+ source = {
281+ 'type': 'dd-xz',
282+ 'uri': 'http://myhost/curtin-unittest-dd.xz'
283+ }
284+ devname = "fakedisk1p1"
285+ devnode = "/dev/" + devname
286+ self.mock_block_get_dev_name_entry.return_value = (devname, devnode)
287+
288+ block_meta.write_image_to_disk(source, devname)
289+
290+ wget = ['sh', '-c',
291+ 'wget "$1" --progress=dot:mega -O - |xzcat| dd bs=4M of="$2"',
292+ '--', source['uri'], devnode]
293+ self.mock_block_get_dev_name_entry.assert_called_with(devname)
294+ self.mock_subp.assert_has_calls([call(args=wget),
295+ call(['partprobe', devnode]),
296+ call(['udevadm', 'settle'])])
297+ paths = ["curtin", "system-data/var/lib/snapd"]
298+ self.mock_block_get_root_device.assert_called_with([devname],
299+ paths=paths)
300+
301+ def test_write_image_to_disk_ddtgz(self):
302+ source = {
303+ 'type': 'dd-tgz',
304+ 'uri': 'http://myhost/curtin-unittest-dd.tgz'
305+ }
306+ devname = "fakedisk1p1"
307+ devnode = "/dev/" + devname
308+ self.mock_block_get_dev_name_entry.return_value = (devname, devnode)
309+
310+ block_meta.write_image_to_disk(source, devname)
311+
312+ wget = ['sh', '-c',
313+ 'wget "$1" --progress=dot:mega -O - |'
314+ 'tar -xOzf -| dd bs=4M of="$2"',
315+ '--', source['uri'], devnode]
316+ self.mock_block_get_dev_name_entry.assert_called_with(devname)
317+ self.mock_subp.assert_has_calls([call(args=wget),
318+ call(['partprobe', devnode]),
319+ call(['udevadm', 'settle'])])
320+ paths = ["curtin", "system-data/var/lib/snapd"]
321+ self.mock_block_get_root_device.assert_called_with([devname],
322+ paths=paths)
323+
324+ @patch('curtin.commands.block_meta.write_image_to_disk')
325+ def test_meta_simple_calls_write_img(self, mock_write_image):
326+ devname = "fakedisk1p1"
327+ devnode = "/dev/" + devname
328+ sources = {
329+ 'unittest': {'type': 'dd-xz',
330+ 'uri': 'http://myhost/curtin-unittest-dd.xz'}
331+ }
332+ config = {
333+ 'block-meta': {'devices': [devname]},
334+ 'sources': sources,
335+ }
336+ self.mock_config_load.return_value = config
337+ self.mock_load_env.return_value = {'target': self.target}
338+ self.mock_block_is_valid_device.return_value = True
339+ self.mock_block_get_dev_name_entry.return_value = (devname, devnode)
340+ mock_write_image.return_value = devname
341+
342+ args = Namespace(target=self.target, devices=None, mode=None,
343+ boot_fstype=None, fstype=None)
344+
345+ block_meta.meta_simple(args)
346+
347+ mock_write_image.assert_called_with(sources.get('unittest'), devname)
348+ self.mock_subp.assert_has_calls(
349+ [call(['mount', devname, self.target])])
350+
351+
352 class TestBlockMeta(BlockMetaTestBase):
353 def setUp(self):
354 super(TestBlockMeta, self).setUp()
355
356=== modified file 'tests/unittests/test_curthooks.py'
357--- tests/unittests/test_curthooks.py 2017-02-09 19:59:11 +0000
358+++ tests/unittests/test_curthooks.py 2017-03-22 21:04:27 +0000
359@@ -6,6 +6,7 @@
360
361 from curtin.commands import curthooks
362 from curtin import util
363+from curtin import config
364 from curtin.reporter import events
365
366
367@@ -175,4 +176,147 @@
368 self.assertEqual([], self.mock_install_packages.call_args_list)
369
370
371+class TestUbuntuCoreHooks(CurthooksBase):
372+ def setUp(self):
373+ super(TestUbuntuCoreHooks, self).setUp()
374+ self.target = None
375+
376+ def tearDown(self):
377+ if self.target:
378+ shutil.rmtree(self.target)
379+
380+ def test_target_is_ubuntu_core(self):
381+ self.target = tempfile.mkdtemp()
382+ ubuntu_core_path = os.path.join(self.target, 'system-data',
383+ 'var/lib/snapd')
384+ util.ensure_dir(ubuntu_core_path)
385+ self.assertTrue(os.path.isdir(ubuntu_core_path))
386+ is_core = curthooks.target_is_ubuntu_core(self.target)
387+ self.assertTrue(is_core)
388+
389+ def test_target_is_ubuntu_core_no_target(self):
390+ is_core = curthooks.target_is_ubuntu_core(self.target)
391+ self.assertFalse(is_core)
392+
393+ def test_target_is_ubuntu_core_noncore_target(self):
394+ self.target = tempfile.mkdtemp()
395+ non_core_path = os.path.join(self.target, 'curtin')
396+ util.ensure_dir(non_core_path)
397+ self.assertTrue(os.path.isdir(non_core_path))
398+ is_core = curthooks.target_is_ubuntu_core(self.target)
399+ self.assertFalse(is_core)
400+
401+ @patch('curtin.util.write_file')
402+ @patch('curtin.util.del_file')
403+ @patch('curtin.commands.curthooks.handle_cloudconfig')
404+ def test_curthooks_no_config(self, mock_handle_cc, mock_del_file,
405+ mock_write_file):
406+ self.target = tempfile.mkdtemp()
407+ cfg = {}
408+ curthooks.ubuntu_core_curthooks(cfg, target=self.target)
409+ self.assertEqual(len(mock_handle_cc.call_args_list), 0)
410+ self.assertEqual(len(mock_del_file.call_args_list), 0)
411+ self.assertEqual(len(mock_write_file.call_args_list), 0)
412+
413+ @patch('curtin.commands.curthooks.handle_cloudconfig')
414+ def test_curthooks_cloud_config_remove_disabled(self, mock_handle_cc):
415+ self.target = tempfile.mkdtemp()
416+ uc_cloud = os.path.join(self.target, 'system-data', 'etc/cloud')
417+ cc_disabled = os.path.join(uc_cloud, 'cloud-init.disabled')
418+ cc_path = os.path.join(uc_cloud, 'cloud.cfg.d')
419+
420+ util.ensure_dir(uc_cloud)
421+ util.write_file(cc_disabled, content="# disable cloud-init\n")
422+ cfg = {
423+ 'cloudconfig': {
424+ 'file1': {
425+ 'content': "Hello World!\n",
426+ }
427+ }
428+ }
429+ self.assertTrue(os.path.exists(cc_disabled))
430+ curthooks.ubuntu_core_curthooks(cfg, target=self.target)
431+
432+ mock_handle_cc.assert_called_with(cfg.get('cloudconfig'),
433+ target=cc_path)
434+ self.assertFalse(os.path.exists(cc_disabled))
435+
436+ @patch('curtin.util.write_file')
437+ @patch('curtin.util.del_file')
438+ @patch('curtin.commands.curthooks.handle_cloudconfig')
439+ def test_curthooks_cloud_config(self, mock_handle_cc, mock_del_file,
440+ mock_write_file):
441+ self.target = tempfile.mkdtemp()
442+ cfg = {
443+ 'cloudconfig': {
444+ 'file1': {
445+ 'content': "Hello World!\n",
446+ }
447+ }
448+ }
449+ curthooks.ubuntu_core_curthooks(cfg, target=self.target)
450+
451+ self.assertEqual(len(mock_del_file.call_args_list), 0)
452+ cc_path = os.path.join(self.target,
453+ 'system-data/etc/cloud/cloud.cfg.d')
454+ mock_handle_cc.assert_called_with(cfg.get('cloudconfig'),
455+ target=cc_path)
456+ self.assertEqual(len(mock_write_file.call_args_list), 0)
457+
458+ @patch('curtin.util.write_file')
459+ @patch('curtin.util.del_file')
460+ @patch('curtin.commands.curthooks.handle_cloudconfig')
461+ def test_curthooks_net_config(self, mock_handle_cc, mock_del_file,
462+ mock_write_file):
463+ self.target = tempfile.mkdtemp()
464+ cfg = {
465+ 'network': {
466+ 'version': '1',
467+ 'config': [{'type': 'physical',
468+ 'name': 'eth0', 'subnets': [{'type': 'dhcp4'}]}]
469+ }
470+ }
471+ curthooks.ubuntu_core_curthooks(cfg, target=self.target)
472+
473+ self.assertEqual(len(mock_del_file.call_args_list), 0)
474+ self.assertEqual(len(mock_handle_cc.call_args_list), 0)
475+ netcfg_path = os.path.join(self.target,
476+ 'system-data',
477+ 'etc/cloud/cloud.cfg.d',
478+ '50-network-config.cfg')
479+ netcfg = config.dump_config(cfg.get('network'))
480+ mock_write_file.assert_called_with(netcfg_path,
481+ content=netcfg)
482+ self.assertEqual(len(mock_del_file.call_args_list), 0)
483+
484+ @patch('curtin.commands.curthooks.write_files')
485+ def test_handle_cloudconfig(self, mock_write_files):
486+ cc_target = "tmpXXXX/systemd-data/etc/cloud/cloud.cfg.d"
487+ cloudconfig = {
488+ 'file1': {
489+ 'content': "Hello World!\n",
490+ },
491+ 'foobar': {
492+ 'path': '/sys/wark',
493+ 'content': "Engauge!\n",
494+ }
495+ }
496+
497+ expected_cfg = {
498+ 'write_files': {
499+ 'file1': {
500+ 'path': '50-cloudconfig-file1.cfg',
501+ 'content': cloudconfig['file1']['content']},
502+ 'foobar': {
503+ 'path': '50-cloudconfig-foobar.cfg',
504+ 'content': cloudconfig['foobar']['content']}
505+ }
506+ }
507+ curthooks.handle_cloudconfig(cloudconfig, target=cc_target)
508+ mock_write_files.assert_called_with(expected_cfg, cc_target)
509+
510+ def test_handle_cloudconfig_bad_config(self):
511+ with self.assertRaises(ValueError):
512+ curthooks.handle_cloudconfig([], target="foobar")
513+
514 # vi: ts=4 expandtab syntax=python
515
516=== modified file 'tests/vmtests/__init__.py'
517--- tests/vmtests/__init__.py 2017-03-01 16:18:42 +0000
518+++ tests/vmtests/__init__.py 2017-03-22 21:04:27 +0000
519@@ -47,6 +47,9 @@
520
521 _TOPDIR = None
522
523+UC16_IMAGE = os.path.join(IMAGE_DIR,
524+ 'ubuntu-core-16/amd64/20170217/root-image.xz')
525+
526
527 def remove_empty_dir(dirpath):
528 if os.path.exists(dirpath):
529@@ -356,6 +359,7 @@
530 target_distro = None
531 target_release = None
532 target_krel = None
533+ target_ftype = "vmtest.root-tgz"
534
535 def shortDescription(self):
536 return None
537@@ -375,14 +379,19 @@
538
539 # get local absolute filesystem paths for the OS tarball to be
540 # installed
541- img_verstr, found = get_images(
542- IMAGE_SRC_URL, IMAGE_DIR,
543- cls.target_distro if cls.target_distro else cls.distro,
544- cls.target_release if cls.target_release else cls.release,
545- cls.arch, krel=cls.target_krel, sync=CURTIN_VMTEST_IMAGE_SYNC,
546- ftypes=('vmtest.root-tgz',))
547- logger.debug("Target Tarball %s\n, ftypes: %s\n", img_verstr, found)
548- logger.info("Target Tarball: %s", img_verstr)
549+ if cls.target_ftype == "vmtest.root-tgz":
550+ img_verstr, found = get_images(
551+ IMAGE_SRC_URL, IMAGE_DIR,
552+ cls.target_distro if cls.target_distro else cls.distro,
553+ cls.target_release if cls.target_release else cls.release,
554+ cls.arch, krel=cls.target_krel, sync=CURTIN_VMTEST_IMAGE_SYNC,
555+ ftypes=('vmtest.root-tgz',))
556+ logger.debug("Target Tarball %s\n, ftypes: %s\n",
557+ img_verstr, found)
558+ logger.info("Target Tarball: %s", img_verstr)
559+ else:
560+ logger.info('get-testfiles UC16 hack!')
561+ found = {'root-image.xz': UC16_IMAGE}
562 ftypes.update(found)
563 return ftypes
564
565@@ -532,8 +541,12 @@
566 cmd.extend(["--append=" + cls.extra_kern_args])
567
568 # publish the root tarball
569- install_src = "PUBURL/" + os.path.basename(ftypes['vmtest.root-tgz'])
570- cmd.append("--publish=%s" % ftypes['vmtest.root-tgz'])
571+ install_src = "PUBURL/" + os.path.basename(ftypes[cls.target_ftype])
572+ if cls.target_ftype == 'vmtest.root-tgz':
573+ cmd.append("--publish=%s" % ftypes['vmtest.root-tgz'])
574+ else:
575+ cmd.append("--publish=%s::dd-xz" % ftypes[cls.target_ftype])
576+ logger.info("Publishing src as %s", cmd[-1])
577
578 # check for network configuration
579 cls.network_state = curtin_net.parse_net_config(cls.conf_file)
580@@ -1261,8 +1274,9 @@
581 failsafe_poweroff = textwrap.dedent("""#!/bin/sh -x
582 [ -e /etc/centos-release -o -e /etc/redhat-release ] &&
583 { shutdown -P now "Shutting down on centos"; }
584- [ "$(lsb_release -sc)" = "precise" ] &&
585- { shutdown -P now "Shutting down on precise"; }
586+ if grep -i -q precise /etc/os-release; then
587+ shutdown -P now "Shutting down on precise";
588+ fi
589 exit 0;
590 """)
591
592
593=== modified file 'tests/vmtests/releases.py'
594--- tests/vmtests/releases.py 2017-02-01 20:32:13 +0000
595+++ tests/vmtests/releases.py 2017-03-22 21:04:27 +0000
596@@ -15,6 +15,11 @@
597 target_distro = "centos"
598
599
600+class _UbuntuCoreUbuntuBase(_UbuntuBase):
601+ # base for installing UbuntuCore root-image.xz from ubuntu base
602+ target_distro = "ubuntu-core-16"
603+
604+
605 class _Centos70FromXenialBase(_CentosFromUbuntuBase):
606 # release for boot
607 release = "xenial"
608@@ -22,6 +27,13 @@
609 target_release = "centos70"
610
611
612+class _UbuntuCore16FromXenialBase(_UbuntuCoreUbuntuBase):
613+ # release for boot
614+ release = "xenial"
615+ # release for target
616+ target_release = "ubuntu-core-16"
617+
618+
619 class _Centos66FromXenialBase(_CentosFromUbuntuBase):
620 release = "xenial"
621 target_release = "centos66"
622@@ -106,7 +118,12 @@
623 centos66fromxenial = _Centos66FromXenialBase
624
625
626+class _UbuntuCoreReleases(object):
627+ uc16fromxenial = _UbuntuCore16FromXenialBase
628+
629+
630 base_vm_classes = _Releases
631 centos_base_vm_classes = _CentosReleases
632+ubuntu_core_base_vm_classes = _UbuntuCoreReleases
633
634 # vi: ts=4 expandtab syntax=python
635
636=== added file 'tests/vmtests/test_ubuntu_core.py'
637--- tests/vmtests/test_ubuntu_core.py 1970-01-01 00:00:00 +0000
638+++ tests/vmtests/test_ubuntu_core.py 2017-03-22 21:04:27 +0000
639@@ -0,0 +1,46 @@
640+from . import VMBaseClass
641+from .releases import ubuntu_core_base_vm_classes as relbase
642+
643+import textwrap
644+
645+
646+class TestUbuntuCoreAbs(VMBaseClass):
647+ target_ftype = "root-image.xz"
648+ interactive = False
649+ conf_file = "examples/tests/ubuntu_core.yaml"
650+ collect_scripts = [textwrap.dedent("""
651+ cd OUTPUT_COLLECT_D
652+ cat /proc/partitions > proc_partitions
653+ ls -al /dev/disk/by-uuid/ > ls_uuid
654+ cat /etc/fstab > fstab
655+ find /etc/network/interfaces.d > find_interfacesd
656+ snap list > snap_list
657+ cp -a /run/cloud-init ./run_cloud_init |:
658+ cp -a /etc/cloud ./etc_cloud |:
659+ cp -a /home . |:
660+ cp -a /var/lib/extrausers . |:
661+ """)]
662+
663+ def test_output_files_exist(self):
664+ self.output_files_exist(["snap_list"])
665+
666+ def test_ubuntu_core_snaps_installed(self):
667+ snap_list = self.load_collect_file('snap_list')
668+ print(snap_list)
669+ for snap in ['core', 'pc', 'pc-kernel', 'hello',
670+ 'part-cython', 'part-numpy']:
671+ print('check for "%s"' % snap)
672+ self.assertIn(snap, snap_list)
673+
674+ def test_ubuntu_core_extrausers(self):
675+ extrausers_passwd = self.load_collect_file('extrausers/passwd')
676+ self.assertIn('ubuntu', extrausers_passwd)
677+
678+ def test_ubuntu_core_ds_identify(self):
679+ run_ci_config = self.load_collect_file('run_cloud_init/cloud.cfg')
680+ expected_config = "datasource_list: [ NoCloud, None ]\n"
681+ self.assertEqual(expected_config, run_ci_config)
682+
683+
684+class UbuntuCore16TestUbuntuCore(relbase.uc16fromxenial, TestUbuntuCoreAbs):
685+ __test__ = False
686
687=== modified file 'tools/launch'
688--- tools/launch 2017-02-17 21:36:49 +0000
689+++ tools/launch 2017-03-22 21:04:27 +0000
690@@ -60,7 +60,7 @@
691
692 Example:
693 * boot myboot.img, and install my-root.tar.gz
694- ${0##*/} myboot.img --publish my-root.tar.gz curtin \
695+ ${0##*/} myboot.img --publish my-root.tar.gz curtin \\
696 install PUBURL/my-root.tar.gz
697
698 EOF
699@@ -563,18 +563,20 @@
700 ip=${_RET}
701
702 local tok src pub fpath
703- # tok in pubs looks like file[:pubname]
704+ # tok in pubs looks like file[:pubname][:stype]
705 # link them into the temp dir for publishing
706 for tok in "${pubs[@]}"; do
707- case "$tok" in
708- *:*) src="${tok%:*}"; pub="${tok##*:}";;
709- *) src=${tok}; pub="";;
710- esac
711+ src=$(echo "${tok}" | awk -F: '{print $1}')
712+ pub=$(echo "${tok}" | awk -F: '{print $2}')
713+ stype=$(echo "${tok}" | awk -F: '{print $3}')
714 fpath=$(readlink -f "$src") ||
715 { error "'$src': failed to get path"; return 1; }
716- if [ -n "$pub" ]; then
717+ if [ -z "$pub" ]; then
718 pub="${src##*/}"
719 fi
720+ if [ -z "$stype" ]; then
721+ stype="tgz"
722+ fi
723 ln -sf "$fpath" "${TEMP_D}/${pub}"
724 done
725
726@@ -582,9 +584,10 @@
727 { error "failed to start http service"; return 1; }
728 http_port=$_RET
729 burl="http://$ip:${http_port}/"
730- # now replace PUBURL anywhere in cmdargs
731+ # now replace PUBURL anywhere in cmdargs, and prefix with
732+ # source type (tgz or dd-tgz)
733 for((i=0;i<${#cmdargs[@]};i++)); do
734- cmdargs[$i]=${cmdargs[$i]//PUBURL/$burl}
735+ cmdargs[$i]=${cmdargs[$i]//PUBURL/${stype}:$burl}
736 done
737
738 local addargs="" f=""

Subscribers

People subscribed via source and target branches