Merge ~smoser/curtin:feature/bug-1746348-fsimage-support into curtin:master
- Git
- lp:~smoser/curtin
- feature/bug-1746348-fsimage-support
- Merge into master
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merge reported by: | Ryan Harper | ||||||||
Merged at revision: | f0f1bcf227b1022c361f53b1b27fb35c0b802b93 | ||||||||
Proposed branch: | ~smoser/curtin:feature/bug-1746348-fsimage-support | ||||||||
Merge into: | curtin:master | ||||||||
Diff against target: |
583 lines (+256/-81) 8 files modified
curtin/commands/extract.py (+59/-31) curtin/url_helper.py (+94/-4) curtin/util.py (+8/-1) doc/topics/config.rst (+3/-0) tests/vmtests/__init__.py (+49/-35) tests/vmtests/releases.py (+13/-0) tests/vmtests/test_uefi_basic.py (+14/-0) tools/launch (+16/-10) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lee Trager (community) | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper (community) | Approve | ||
Review via email: mp+336872@code.launchpad.net |
Commit message
Add support for installing sources that are a filesystem image.
This adds support for sources that are a 'filesystem image' via local
file path or via http or https. It will work for any filesystem
image type that is mountable by the running kernel.
The end result is that this works:
sudo ./bin/curtin install --config=my.yaml \
http://
To accomplish this:
a.) if the source is a http/https url, then download it to temporary file.
b.) mount the file loopback (mount -o loop,ro).
c.) copy the contents to the target
d.) unmount
e.) if downloaded remove the file.
In order to do this we needed some mechanism for downloading a url
in pieces rather than all into memory as 'geturl' uses. So I've
added UrlReader and 'download'.
Also here, a fix for the default headers to include the actual curtin
version per version.
MAAS 2.3 and 2.4 plan to use this functionality to install Ubuntu 12.04
while booted in 16.04. There are two tests added that excercise that
path that will be enabled later. To support testing hwe here,
also have added explicit setting of target_
LP: #1746348
Description of the change
I was able to test with the proposed precise squashfs stream:
https:/
$ export IMAGE_DIR=
$ python3 tests/vmtests/
$ ./tools/
Scott Moser (smoser) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:7c3062aeb16
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- fb9128f... by Scott Moser
-
tools/launch: support --publish to a subdir
This allows:
--publish=/my/path/ to/image: somedir/ somefile Before you could only publish to the top level PUBDIR.
That issue would be exposed if you did:
--publish=/path/to/ boot/squashfs
--publish=/path/to/ target/ squashfs the second would silently override the first.
It still does, but now you can put into subdirs. - 88bc44c... by Scott Moser
-
vmtest: Enablement of precise installations using xenial boot.
This works a bit on vmtest base class to more cleanly support
installations of one OS while booted into another.Also enables some one test of Precise to verify.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:42341f78edd
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
The goal of this merge is to enable installation of a Precise squashfs
while booting either trusty or xenial. Since we do not currently have
a precise squashfs I instead worked on installing a trusty squashfs image
while booted in xenial.
The test I re-enabled is called PreciseUefiTest
It currently installs fine but fails boot.
The issue can be seen in the boot log at
http://
| [ 3.055946] SGI XFS with ACLs, security attributes, realtime, large block/inode numbers, no debug enabled
| [ 3.062047] XFS (vda3): Version 5 superblock detected. This kernel has EXPERIMENTAL support enabled!
| [ 3.062047] Use of these features in this kernel is at your own risk!
| [ 3.063697] XFS (vda3): Superblock has unknown read-only compatible features (0x1) enabled.
| [ 3.064568] XFS (vda3): Attempted to mount read-only compatible filesystem read-write.
| [ 3.064568] Filesystem can only be safely mounted read only.
| [ 3.068021] XFS (vda3): SB validate failed with error 22.
The issue here I believe is that Xenial's mkfs.xfs enabled some features
by default that were not supported in the Trusty (3.13) kernel. So the
install works fine, but when the target tries to boot it fails. We are
shielded from such mismatch issues currently as we booting and installing
the same release, so the default mkfs options match.
I can't be sure yet whether or not this would be a problem when booting
trusty and installing precise, but it is a general issue that could
definitely show itself.
To validate, the following "fixes" the boot problem:
--- a/examples/
+++ b/examples/
@@ -49,7 +49,7 @@ storage:
- id: id_home_format
label: home
type: format
- fstype: xfs
+ fstype: ext4
volume: id_disk0_part3
Scott Moser (smoser) wrote : | # |
another option...
http://
basically just allowing the storage_config to pass an array of options taht will go into the mkfs command.
that also produced actually booting system.
- a85f8b7... by Scott Moser
-
move downloading to a url_helper.download method that adds speed info.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:3202430df9d
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:2db25dd4665
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
OK, I'm happy with the current state of this branch for landing.
It doesn't handle the issue discussed above, but that is really
not related to support for installing a filesystem image.
The 'download' currently gives no status, but at the end does have a nice
message like:
[ 51.934046] cloud-init[1254]: Downloaded 287375360 bytes from http://
68.20:35549/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:49d2e849380
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) : | # |
Chad Smith (chad.smith) : | # |
Ryan Harper (raharper) wrote : | # |
The extract logic currently is like:
dd-
continue
cp://
copy_to_target
isfile:
extract_
file://
extract_
http[s]://
extract_
Can we just remove _tgz_ from the method name,
and add the squashfs handling in there? Something like
extract_
extract_
Then we could push some of the logic for source and format hanlding into those two functions.
Something like this:
if source[
continue
if source[
elif os.path.
elif (source[
else:
raise TypeError("do not know how to extract '%s'" % source['uri'])
def extract_
if source.
source = source[
if fmt == "fsimage":
return extract_
else:
def extract_
if fmt == "fsimage":
return extract_
else:
Scott Moser (smoser) : | # |
Scott Moser (smoser) : | # |
- 3541263... by Scott Moser
-
address some feedback from chad.
- 20e2684... by Scott Moser
-
explicitly set some things in PreciseBase
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:8c14f9d9a03
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:12b6fe82329
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 8af54f1... by Scott Moser
-
add returns to extract_root_tgz calls
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:a4ae3abf0a8
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- dd224f0... by Scott Moser
-
support specifying kernel package.
The get_kernel_config didn't work for precise kernel names.
linux-generic .. linux-generic-lts-trusty
So just specify them manually.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:a80b31ee661
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 1613ee0... by Scott Moser
-
address feedback / simplify
- e900c89... by Scott Moser
-
add urls that are just path, not file://<path> .
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:0125fb02a85
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
I've addressed the feedback i think. i haven't tested, but plealse re-review.
- f0525d8... by Scott Moser
-
disable PreciseUefi tests. Later enable when they work.
Ryan Harper (raharper) wrote : | # |
This looks great, just a few questions/comments in line.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:40bd1dc608d
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:a270cc82234
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Lee Trager (ltrager) wrote : | # |
The Precise image currently in the stream is a gzip compressed ext4 image. It seems this supports SquashFS files by mounting them and coping the files. Why not support mounting the Precise ext4 image so the streams don't have to be modified to add a SquashFS image?
If Curtin is supporting SquashFS why not use unsquashfs and skip mounting it?
Scott Moser (smoser) wrote : | # |
> The Precise image currently in the stream is a gzip compressed ext4 image. It
> seems this supports SquashFS files by mounting them and coping the files. Why
> not support mounting the Precise ext4 image so the streams don't have to be
> modified to add a SquashFS image?
>
We could support that, but I'd rather not. We'd have to download the
image (possibly decompressing as we read) and then mount it. That image
is 1.4G so we'd then be using 1.4G of tmpfs space. It seems
generally superior to make the squashfs image availalbe in the
stream.. Then we have
- consistency for all releases in v3 stream
- less memory required
> If Curtin is supporting SquashFS why not use unsquashfs and skip
> mounting it?
a.) That would certainly be one path, however it then requires a package
to do what the kernel can do just as well. We are guaranteed root to
run curtin so this seems more straight forward. We also then are able
to re-use the 'cp://' handler as we're mounting and copying from
an existing directory.
b.) the non-unsquashfs path supports *any* filesystem image that the
kernel booted can mount.
What is wrong with mounting an image?
If squashfs was "streamable" then you'd have a much better argument.
But to my knowledge it is not. Ie, you have to download the whole
image before you can start 'unsquashfs'. If we could
wget <url> -O - | unsquashfs --cd /target/dir
then that'd be nice. I dont think the squashfs filesystem format
would really allow it though.
Scott Moser (smoser) wrote : | # |
I put up
https:/
which removes xfs from the uefi basic test.
that will solve/avoid the problems i've discussed above.
the goal is to later enable a 'FileSystemStress' sort of test that
would verify support for each filesystem type and could be run
with precise installs from xenial.
- fe3fe29... by Scott Moser
-
disable PreciseUefi tests. Later enable when they work.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f07cae3b1a2
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:fdca31bdf7f
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 1e53905... by Scott Moser
-
change installation of PreciseBase to use precise.
- e7d6656... by Scott Moser
-
fix class name
- 7bb6fa1... by Scott Moser
-
sanitize_source: do not ever call 'squashfs' an image type
Ryan Harper (raharper) wrote : | # |
I think the commit message should clarify fsimage means squasfs-based image; especially regarding the comment about other image types.
If we want to keep fsimage as generic (it *could* be used to install the ext4 root-image) then maybe we should add a test (even if that's not what we'd use in practice). Thoughts?
Some more questions and items inline.
Scott Moser (smoser) wrote : | # |
fsimage does not mean 'squashfs-based image'.
it means filesystem image.
I'd like to keep it 'fsimage' as that is what is implemented.
Chad Smith (chad.smith) : | # |
- e685e5a... by Scott Moser
-
mention other file system types than squash
- 51b13c9... by Scott Moser
-
avoid valueerror if precise was not in the list
- 31c1664... by Scott Moser
-
update download per feedback.
- e4cf526... by Scott Moser
-
raise OSerror rather than runtimeerror.
- f0f1bcf... by Scott Moser
-
address suggested change for _path_from_file usage.
Ryan Harper (raharper) wrote : | # |
One more question on the curtin.util source sanitize w.r.t auto converting squash to fsimage types.
Scott Moser (smoser) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e7d6656fd09
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
I'm ok with leaving it in.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f0f1bcf227b
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Lee Trager (ltrager) wrote : | # |
Thanks for the explanation!
Preview Diff
1 | diff --git a/curtin/commands/extract.py b/curtin/commands/extract.py | |||
2 | index b9f1baf..69a9d18 100644 | |||
3 | --- a/curtin/commands/extract.py | |||
4 | +++ b/curtin/commands/extract.py | |||
5 | @@ -2,12 +2,14 @@ | |||
6 | 2 | 2 | ||
7 | 3 | import os | 3 | import os |
8 | 4 | import sys | 4 | import sys |
9 | 5 | import tempfile | ||
10 | 5 | 6 | ||
11 | 6 | import curtin.config | 7 | import curtin.config |
12 | 7 | from curtin.log import LOG | 8 | from curtin.log import LOG |
14 | 8 | import curtin.util | 9 | from curtin import util |
15 | 9 | from curtin.futil import write_files | 10 | from curtin.futil import write_files |
16 | 10 | from curtin.reporter import events | 11 | from curtin.reporter import events |
17 | 12 | from curtin import url_helper | ||
18 | 11 | 13 | ||
19 | 12 | from . import populate_one_subcmd | 14 | from . import populate_one_subcmd |
20 | 13 | 15 | ||
21 | @@ -31,27 +33,56 @@ def tar_xattr_opts(cmd=None): | |||
22 | 31 | if isinstance(cmd, str): | 33 | if isinstance(cmd, str): |
23 | 32 | cmd = [cmd] | 34 | cmd = [cmd] |
24 | 33 | 35 | ||
26 | 34 | (out, _err) = curtin.util.subp(cmd + ['--help'], capture=True) | 36 | (out, _err) = util.subp(cmd + ['--help'], capture=True) |
27 | 35 | 37 | ||
28 | 36 | if "xattr" in out: | 38 | if "xattr" in out: |
29 | 37 | return ['--xattrs', '--xattrs-include=*'] | 39 | return ['--xattrs', '--xattrs-include=*'] |
30 | 38 | return [] | 40 | return [] |
31 | 39 | 41 | ||
32 | 40 | 42 | ||
34 | 41 | def extract_root_tgz_url(source, target): | 43 | def extract_root_tgz_url(url, target): |
35 | 42 | # extract a -root.tar.gz url in the 'target' directory | 44 | # extract a -root.tar.gz url in the 'target' directory |
44 | 43 | # | 45 | path = _path_from_file_url(url) |
45 | 44 | # Uses smtar to avoid specifying the compression type | 46 | if path != url or os.path.isfile(path): |
46 | 45 | curtin.util.subp(args=['sh', '-cf', | 47 | util.subp(args=['tar', '-C', target] + tar_xattr_opts() + |
47 | 46 | ('wget "$1" --progress=dot:mega -O - |' | 48 | ['-Sxpzf', path, '--numeric-owner']) |
48 | 47 | 'smtar -C "$2" ' + ' '.join(tar_xattr_opts()) + | 49 | return |
41 | 48 | ' ' + '-Sxpf - --numeric-owner'), | ||
42 | 49 | '--', source, target]) | ||
43 | 50 | |||
49 | 51 | 50 | ||
53 | 52 | def extract_root_tgz_file(source, target): | 51 | # Uses smtar to avoid specifying the compression type |
54 | 53 | curtin.util.subp(args=['tar', '-C', target] + | 52 | util.subp(args=['sh', '-cf', |
55 | 54 | tar_xattr_opts() + ['-Sxpzf', source, '--numeric-owner']) | 53 | ('wget "$1" --progress=dot:mega -O - |' |
56 | 54 | 'smtar -C "$2" ' + ' '.join(tar_xattr_opts()) + | ||
57 | 55 | ' ' + '-Sxpf - --numeric-owner'), | ||
58 | 56 | '--', url, target]) | ||
59 | 57 | |||
60 | 58 | |||
61 | 59 | def extract_root_fsimage_url(url, target): | ||
62 | 60 | path = _path_from_file_url(url) | ||
63 | 61 | if path != url or os.path.isfile(path): | ||
64 | 62 | return _extract_root_fsimage(path(url), target) | ||
65 | 63 | |||
66 | 64 | wfp = tempfile.NamedTemporaryFile(suffix=".img", delete=False) | ||
67 | 65 | wfp.close() | ||
68 | 66 | try: | ||
69 | 67 | url_helper.download(url, wfp.name) | ||
70 | 68 | return _extract_root_fsimage(wfp.name, target) | ||
71 | 69 | finally: | ||
72 | 70 | os.unlink(wfp.name) | ||
73 | 71 | |||
74 | 72 | |||
75 | 73 | def _extract_root_fsimage(path, target): | ||
76 | 74 | mp = tempfile.mkdtemp() | ||
77 | 75 | try: | ||
78 | 76 | util.subp(['mount', '-o', 'loop,ro', path, mp], capture=True) | ||
79 | 77 | except util.ProcessExecutionError as e: | ||
80 | 78 | LOG.error("Failed to mount '%s' for extraction: %s", path, e) | ||
81 | 79 | os.rmdir(mp) | ||
82 | 80 | raise e | ||
83 | 81 | try: | ||
84 | 82 | return copy_to_target(mp, target) | ||
85 | 83 | finally: | ||
86 | 84 | util.subp(['umount', mp]) | ||
87 | 85 | os.rmdir(mp) | ||
88 | 55 | 86 | ||
89 | 56 | 87 | ||
90 | 57 | def copy_to_target(source, target): | 88 | def copy_to_target(source, target): |
91 | @@ -59,17 +90,21 @@ def copy_to_target(source, target): | |||
92 | 59 | source = source[5:] | 90 | source = source[5:] |
93 | 60 | source = os.path.abspath(source) | 91 | source = os.path.abspath(source) |
94 | 61 | 92 | ||
99 | 62 | curtin.util.subp(args=['sh', '-c', | 93 | util.subp(args=['sh', '-c', |
100 | 63 | ('mkdir -p "$2" && cd "$2" && ' | 94 | ('mkdir -p "$2" && cd "$2" && ' |
101 | 64 | 'rsync -aXHAS --one-file-system "$1/" .'), | 95 | 'rsync -aXHAS --one-file-system "$1/" .'), |
102 | 65 | '--', source, target]) | 96 | '--', source, target]) |
103 | 97 | |||
104 | 98 | |||
105 | 99 | def _path_from_file_url(url): | ||
106 | 100 | return url[7:] if url.startswith("file://") else url | ||
107 | 66 | 101 | ||
108 | 67 | 102 | ||
109 | 68 | def extract(args): | 103 | def extract(args): |
110 | 69 | if not args.target: | 104 | if not args.target: |
111 | 70 | raise ValueError("Target must be defined or set in environment") | 105 | raise ValueError("Target must be defined or set in environment") |
112 | 71 | 106 | ||
114 | 72 | state = curtin.util.load_command_environment() | 107 | state = util.load_command_environment() |
115 | 73 | cfg = curtin.config.load_command_config(args, state) | 108 | cfg = curtin.config.load_command_config(args, state) |
116 | 74 | 109 | ||
117 | 75 | sources = args.sources | 110 | sources = args.sources |
118 | @@ -82,6 +117,8 @@ def extract(args): | |||
119 | 82 | if isinstance(sources, dict): | 117 | if isinstance(sources, dict): |
120 | 83 | sources = [sources[k] for k in sorted(sources.keys())] | 118 | sources = [sources[k] for k in sorted(sources.keys())] |
121 | 84 | 119 | ||
122 | 120 | sources = [util.sanitize_source(s) for s in sources] | ||
123 | 121 | |||
124 | 85 | LOG.debug("Installing sources: %s to target at %s" % (sources, target)) | 122 | LOG.debug("Installing sources: %s to target at %s" % (sources, target)) |
125 | 86 | stack_prefix = state.get('report_stack_prefix', '') | 123 | stack_prefix = state.get('report_stack_prefix', '') |
126 | 87 | 124 | ||
127 | @@ -94,19 +131,10 @@ def extract(args): | |||
128 | 94 | continue | 131 | continue |
129 | 95 | if source['uri'].startswith("cp://"): | 132 | if source['uri'].startswith("cp://"): |
130 | 96 | copy_to_target(source['uri'], target) | 133 | copy_to_target(source['uri'], target) |
140 | 97 | elif os.path.isfile(source['uri']): | 134 | elif source['type'] == "fsimage": |
141 | 98 | extract_root_tgz_file(source['uri'], target) | 135 | extract_root_fsimage_url(source['uri'], target=target) |
133 | 99 | elif source['uri'].startswith("file://"): | ||
134 | 100 | extract_root_tgz_file( | ||
135 | 101 | source['uri'][len("file://"):], | ||
136 | 102 | target) | ||
137 | 103 | elif (source['uri'].startswith("http://") or | ||
138 | 104 | source['uri'].startswith("https://")): | ||
139 | 105 | extract_root_tgz_url(source['uri'], target) | ||
142 | 106 | else: | 136 | else: |
146 | 107 | raise TypeError( | 137 | extract_root_tgz_url(source['uri'], target=target) |
144 | 108 | "do not know how to extract '%s'" % | ||
145 | 109 | source['uri']) | ||
147 | 110 | 138 | ||
148 | 111 | if cfg.get('write_files'): | 139 | if cfg.get('write_files'): |
149 | 112 | LOG.info("Applying write_files from config.") | 140 | LOG.info("Applying write_files from config.") |
150 | diff --git a/curtin/url_helper.py b/curtin/url_helper.py | |||
151 | index 75ea633..0221ad5 100644 | |||
152 | --- a/curtin/url_helper.py | |||
153 | +++ b/curtin/url_helper.py | |||
154 | @@ -9,6 +9,8 @@ import time | |||
155 | 9 | import uuid | 9 | import uuid |
156 | 10 | from functools import partial | 10 | from functools import partial |
157 | 11 | 11 | ||
158 | 12 | from curtin import version | ||
159 | 13 | |||
160 | 12 | try: | 14 | try: |
161 | 13 | from urllib import request as _u_re # pylint: disable=no-name-in-module | 15 | from urllib import request as _u_re # pylint: disable=no-name-in-module |
162 | 14 | from urllib import error as _u_e # pylint: disable=no-name-in-module | 16 | from urllib import error as _u_e # pylint: disable=no-name-in-module |
163 | @@ -25,6 +27,8 @@ from .log import LOG | |||
164 | 25 | 27 | ||
165 | 26 | error = urllib_error | 28 | error = urllib_error |
166 | 27 | 29 | ||
167 | 30 | DEFAULT_HEADERS = {'User-Agent': 'Curtin/' + version.version_string()} | ||
168 | 31 | |||
169 | 28 | 32 | ||
170 | 29 | class _ReRaisedException(Exception): | 33 | class _ReRaisedException(Exception): |
171 | 30 | exc = None | 34 | exc = None |
172 | @@ -34,14 +38,100 @@ class _ReRaisedException(Exception): | |||
173 | 34 | self.exc = exc | 38 | self.exc = exc |
174 | 35 | 39 | ||
175 | 36 | 40 | ||
178 | 37 | def _geturl(url, headers=None, headers_cb=None, exception_cb=None, data=None): | 41 | class UrlReader(object): |
179 | 38 | def_headers = {'User-Agent': 'Curtin/0.1'} | 42 | fp = None |
180 | 43 | |||
181 | 44 | def __init__(self, url, headers=None, data=None): | ||
182 | 45 | headers = _get_headers(headers) | ||
183 | 46 | self.url = url | ||
184 | 47 | try: | ||
185 | 48 | req = urllib_request.Request(url=url, data=data, headers=headers) | ||
186 | 49 | self.fp = urllib_request.urlopen(req) | ||
187 | 50 | except urllib_error.HTTPError as exc: | ||
188 | 51 | raise UrlError(exc, code=exc.code, headers=exc.headers, url=url, | ||
189 | 52 | reason=exc.reason) | ||
190 | 53 | except Exception as exc: | ||
191 | 54 | raise UrlError(exc, code=None, headers=None, url=url, | ||
192 | 55 | reason="unknown") | ||
193 | 56 | |||
194 | 57 | self.info = self.fp.info() | ||
195 | 58 | self.size = self.info.get('content-length', -1) | ||
196 | 59 | |||
197 | 60 | def read(self, buflen): | ||
198 | 61 | try: | ||
199 | 62 | return self.fp.read(buflen) | ||
200 | 63 | except urllib_error.HTTPError as exc: | ||
201 | 64 | raise UrlError(exc, code=exc.code, headers=exc.headers, | ||
202 | 65 | url=self.url, reason=exc.reason) | ||
203 | 66 | except Exception as exc: | ||
204 | 67 | raise UrlError(exc, code=None, headers=None, url=self.url, | ||
205 | 68 | reason="unknown") | ||
206 | 69 | |||
207 | 70 | def close(self): | ||
208 | 71 | if not self.fp: | ||
209 | 72 | return | ||
210 | 73 | try: | ||
211 | 74 | self.fp.close() | ||
212 | 75 | finally: | ||
213 | 76 | self.fp = None | ||
214 | 39 | 77 | ||
215 | 78 | def __enter__(self): | ||
216 | 79 | return self | ||
217 | 80 | |||
218 | 81 | def __exit__(self, etype, value, trace): | ||
219 | 82 | self.close() | ||
220 | 83 | |||
221 | 84 | |||
222 | 85 | def download(url, path, reporthook=None, data=None): | ||
223 | 86 | """Download url to path. | ||
224 | 87 | |||
225 | 88 | reporthook is compatible with py3 urllib.request.urlretrieve. | ||
226 | 89 | urlretrieve does not exist in py2.""" | ||
227 | 90 | |||
228 | 91 | buflen = 8192 | ||
229 | 92 | wfp = open(path, "wb") | ||
230 | 93 | |||
231 | 94 | try: | ||
232 | 95 | buf = None | ||
233 | 96 | blocknum = 0 | ||
234 | 97 | fsize = 0 | ||
235 | 98 | start = time.time() | ||
236 | 99 | with UrlReader(url) as rfp: | ||
237 | 100 | if reporthook: | ||
238 | 101 | reporthook(blocknum, buflen, rfp.size) | ||
239 | 102 | |||
240 | 103 | while True: | ||
241 | 104 | buf = rfp.read(buflen) | ||
242 | 105 | if not buf: | ||
243 | 106 | break | ||
244 | 107 | blocknum += 1 | ||
245 | 108 | if reporthook: | ||
246 | 109 | reporthook(blocknum, buflen, rfp.size) | ||
247 | 110 | rlen = len(buf) | ||
248 | 111 | wlen = wfp.write(buf) | ||
249 | 112 | if rlen != wlen: | ||
250 | 113 | raise OSError( | ||
251 | 114 | "Short write to %s. Tried write of %d bytes " | ||
252 | 115 | "but wrote only %d" % (path, rlen, wlen)) | ||
253 | 116 | fsize += rlen | ||
254 | 117 | timedelta = time.time() - start | ||
255 | 118 | LOG.debug("Downloaded %d bytes from %s to %s in %.2fs (%.2fMbps)", | ||
256 | 119 | fsize, url, path, timedelta, fsize / timedelta / 1024 / 1024) | ||
257 | 120 | return path, rfp.info | ||
258 | 121 | finally: | ||
259 | 122 | wfp.close() | ||
260 | 123 | |||
261 | 124 | |||
262 | 125 | def _get_headers(headers=None): | ||
263 | 126 | allheaders = DEFAULT_HEADERS.copy() | ||
264 | 40 | if headers is not None: | 127 | if headers is not None: |
266 | 41 | def_headers.update(headers) | 128 | allheaders.update(headers) |
267 | 129 | return allheaders | ||
268 | 42 | 130 | ||
269 | 43 | headers = def_headers | ||
270 | 44 | 131 | ||
271 | 132 | def _geturl(url, headers=None, headers_cb=None, exception_cb=None, data=None): | ||
272 | 133 | |||
273 | 134 | headers = _get_headers(headers) | ||
274 | 45 | if headers_cb: | 135 | if headers_cb: |
275 | 46 | headers.update(headers_cb(url)) | 136 | headers.update(headers_cb(url)) |
276 | 47 | 137 | ||
277 | diff --git a/curtin/util.py b/curtin/util.py | |||
278 | index d343339..12a5446 100644 | |||
279 | --- a/curtin/util.py | |||
280 | +++ b/curtin/util.py | |||
281 | @@ -1084,13 +1084,20 @@ def sanitize_source(source): | |||
282 | 1084 | # already sanitized? | 1084 | # already sanitized? |
283 | 1085 | return source | 1085 | return source |
284 | 1086 | supported = ['tgz', 'dd-tgz', 'dd-tbz', 'dd-txz', 'dd-tar', 'dd-bz2', | 1086 | supported = ['tgz', 'dd-tgz', 'dd-tbz', 'dd-txz', 'dd-tar', 'dd-bz2', |
286 | 1087 | 'dd-gz', 'dd-xz', 'dd-raw'] | 1087 | 'dd-gz', 'dd-xz', 'dd-raw', 'fsimage'] |
287 | 1088 | deftype = 'tgz' | 1088 | deftype = 'tgz' |
288 | 1089 | for i in supported: | 1089 | for i in supported: |
289 | 1090 | prefix = i + ":" | 1090 | prefix = i + ":" |
290 | 1091 | if source.startswith(prefix): | 1091 | if source.startswith(prefix): |
291 | 1092 | return {'type': i, 'uri': source[len(prefix):]} | 1092 | return {'type': i, 'uri': source[len(prefix):]} |
292 | 1093 | 1093 | ||
293 | 1094 | # translate squashfs: to fsimage type. | ||
294 | 1095 | if source.startswith("squashfs:"): | ||
295 | 1096 | return {'type': 'fsimage', 'uri': source[len("squashfs:")]} | ||
296 | 1097 | |||
297 | 1098 | if source.endswith("squashfs") or source.endswith("squash"): | ||
298 | 1099 | return {'type': 'fsimage', 'uri': source} | ||
299 | 1100 | |||
300 | 1094 | LOG.debug("unknown type for url '%s', assuming type '%s'", source, deftype) | 1101 | LOG.debug("unknown type for url '%s', assuming type '%s'", source, deftype) |
301 | 1095 | # default to tgz for unknown types | 1102 | # default to tgz for unknown types |
302 | 1096 | return {'type': deftype, 'uri': source} | 1103 | return {'type': deftype, 'uri': source} |
303 | diff --git a/doc/topics/config.rst b/doc/topics/config.rst | |||
304 | index 0f11b53..fdc524f 100644 | |||
305 | --- a/doc/topics/config.rst | |||
306 | +++ b/doc/topics/config.rst | |||
307 | @@ -435,6 +435,9 @@ configures the method used to copy the image to the target system. | |||
308 | 435 | - **cp://**: Use ``rsync`` command to copy source directory to target. | 435 | - **cp://**: Use ``rsync`` command to copy source directory to target. |
309 | 436 | - **file://**: Use ``tar`` command to extract source to target. | 436 | - **file://**: Use ``tar`` command to extract source to target. |
310 | 437 | - **http[s]://**: Use ``wget | tar`` commands to extract source to target. | 437 | - **http[s]://**: Use ``wget | tar`` commands to extract source to target. |
311 | 438 | - **fsimage://**: mount filesystem image and copy contents to target. | ||
312 | 439 | Local file or url are supported. Filesystem can be any filesystem type | ||
313 | 440 | mountable by the running kernel. | ||
314 | 438 | 441 | ||
315 | 439 | **Example Cloud-image**:: | 442 | **Example Cloud-image**:: |
316 | 440 | 443 | ||
317 | diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py | |||
318 | index f6515af..60882a2 100644 | |||
319 | --- a/tests/vmtests/__init__.py | |||
320 | +++ b/tests/vmtests/__init__.py | |||
321 | @@ -392,6 +392,7 @@ class VMBaseClass(TestCase): | |||
322 | 392 | target_release = None | 392 | target_release = None |
323 | 393 | target_krel = None | 393 | target_krel = None |
324 | 394 | target_ftype = "squashfs" | 394 | target_ftype = "squashfs" |
325 | 395 | target_kernel_package = None | ||
326 | 395 | 396 | ||
327 | 396 | _debian_packages = None | 397 | _debian_packages = None |
328 | 397 | 398 | ||
329 | @@ -414,31 +415,32 @@ class VMBaseClass(TestCase): | |||
330 | 414 | subarch=cls.subarch if cls.subarch else None, | 415 | subarch=cls.subarch if cls.subarch else None, |
331 | 415 | sync=CURTIN_VMTEST_IMAGE_SYNC, | 416 | sync=CURTIN_VMTEST_IMAGE_SYNC, |
332 | 416 | ftypes=('boot-initrd', 'boot-kernel', cls.ephemeral_ftype)) | 417 | ftypes=('boot-initrd', 'boot-kernel', cls.ephemeral_ftype)) |
335 | 417 | logger.debug("Install Image %s\n, ftypes: %s\n", | 418 | |
334 | 418 | eph_img_verstr, ftypes) | ||
336 | 419 | if not cls.target_krel and cls.krel: | 419 | if not cls.target_krel and cls.krel: |
337 | 420 | cls.target_krel = cls.krel | 420 | cls.target_krel = cls.krel |
338 | 421 | 421 | ||
343 | 422 | # get local absolute filesystem paths for the OS tarball to be | 422 | tftype = cls.target_ftype |
344 | 423 | # installed | 423 | if tftype in ["root-image.xz"]: |
345 | 424 | if cls.target_ftype in ["vmtest.root-tgz", "squashfs"]: | 424 | logger.info('get-testfiles UC16 hack!') |
346 | 425 | target_img_verstr, found = get_images( | 425 | target_ftypes = {'root-image.xz': UC16_IMAGE} |
347 | 426 | target_img_verstr = "UbuntuCore 16" | ||
348 | 427 | elif cls.target_release == cls.release: | ||
349 | 428 | target_ftypes = ftypes.copy() | ||
350 | 429 | target_img_verstr = eph_img_verstr | ||
351 | 430 | else: | ||
352 | 431 | target_img_verstr, target_ftypes = get_images( | ||
353 | 426 | IMAGE_SRC_URL, IMAGE_DIR, | 432 | IMAGE_SRC_URL, IMAGE_DIR, |
354 | 427 | cls.target_distro if cls.target_distro else cls.distro, | 433 | cls.target_distro if cls.target_distro else cls.distro, |
355 | 428 | cls.target_release if cls.target_release else cls.release, | 434 | cls.target_release if cls.target_release else cls.release, |
356 | 429 | cls.arch, subarch=cls.subarch if cls.subarch else None, | 435 | cls.arch, subarch=cls.subarch if cls.subarch else None, |
357 | 430 | kflavor=cls.kflavor if cls.kflavor else None, | 436 | kflavor=cls.kflavor if cls.kflavor else None, |
358 | 431 | krel=cls.target_krel, sync=CURTIN_VMTEST_IMAGE_SYNC, | 437 | krel=cls.target_krel, sync=CURTIN_VMTEST_IMAGE_SYNC, |
369 | 432 | ftypes=(cls.target_ftype,)) | 438 | ftypes=(tftype,)) |
370 | 433 | logger.debug("Target Tarball %s\n, ftypes: %s\n", | 439 | |
371 | 434 | target_img_verstr, found) | 440 | ftypes["target/%s" % tftype] = target_ftypes[tftype] |
372 | 435 | elif cls.target_ftype in ["root-image.xz"]: | 441 | logger.debug( |
373 | 436 | logger.info('get-testfiles UC16 hack!') | 442 | "Install Image Version = %s\n, Target Image Version = %s\n" |
374 | 437 | found = {'root-image.xz': UC16_IMAGE} | 443 | "ftypes: %s", eph_img_verstr, target_img_verstr, ftypes) |
365 | 438 | target_img_verstr = "UbuntuCore 16" | ||
366 | 439 | logger.info("Ephemeral Image Version:[%s] Target Image Version:[%s]", | ||
367 | 440 | eph_img_verstr, target_img_verstr) | ||
368 | 441 | ftypes.update(found) | ||
375 | 442 | return ftypes | 444 | return ftypes |
376 | 443 | 445 | ||
377 | 444 | @classmethod | 446 | @classmethod |
378 | @@ -621,6 +623,9 @@ class VMBaseClass(TestCase): | |||
379 | 621 | @classmethod | 623 | @classmethod |
380 | 622 | def get_kernel_package(cls): | 624 | def get_kernel_package(cls): |
381 | 623 | """ Return the kernel package name for this class """ | 625 | """ Return the kernel package name for this class """ |
382 | 626 | if cls.target_kernel_package: | ||
383 | 627 | return cls.target_kernel_package | ||
384 | 628 | |||
385 | 624 | package = 'linux-image-' + cls.kflavor | 629 | package = 'linux-image-' + cls.kflavor |
386 | 625 | if cls.subarch is None or cls.subarch.startswith('ga'): | 630 | if cls.subarch is None or cls.subarch.startswith('ga'): |
387 | 626 | return package | 631 | return package |
388 | @@ -634,6 +639,9 @@ class VMBaseClass(TestCase): | |||
389 | 634 | 639 | ||
390 | 635 | Returns: config dictionary only for hwe, or edge subclass | 640 | Returns: config dictionary only for hwe, or edge subclass |
391 | 636 | """ | 641 | """ |
392 | 642 | if cls.target_kernel_package: | ||
393 | 643 | return {'kernel': {'package': cls.target_kernel_package}} | ||
394 | 644 | |||
395 | 637 | if cls.subarch is None or cls.subarch.startswith('ga'): | 645 | if cls.subarch is None or cls.subarch.startswith('ga'): |
396 | 638 | return None | 646 | return None |
397 | 639 | 647 | ||
398 | @@ -689,12 +697,12 @@ class VMBaseClass(TestCase): | |||
399 | 689 | cmd.extend(["--append=" + cls.extra_kern_args]) | 697 | cmd.extend(["--append=" + cls.extra_kern_args]) |
400 | 690 | 698 | ||
401 | 691 | ftypes = cls.get_test_files() | 699 | ftypes = cls.get_test_files() |
402 | 700 | root_pubpath = "root/" + cls.ephemeral_ftype | ||
403 | 692 | # trusty can't yet use root=URL due to LP:#1735046 | 701 | # trusty can't yet use root=URL due to LP:#1735046 |
404 | 693 | if cls.release in ['trusty']: | 702 | if cls.release in ['trusty']: |
405 | 694 | root_url = "/dev/disk/by-id/virtio-boot-disk" | 703 | root_url = "/dev/disk/by-id/virtio-boot-disk" |
406 | 695 | else: | 704 | else: |
409 | 696 | root_url = "squash:PUBURL/%s" % ( | 705 | root_url = "squash:PUBURL/" + root_pubpath |
408 | 697 | os.path.basename(ftypes[cls.ephemeral_ftype])) | ||
410 | 698 | # configure ephemeral boot environment | 706 | # configure ephemeral boot environment |
411 | 699 | cmd.extend([ | 707 | cmd.extend([ |
412 | 700 | "--root-arg=root=%s" % root_url, | 708 | "--root-arg=root=%s" % root_url, |
413 | @@ -710,16 +718,17 @@ class VMBaseClass(TestCase): | |||
414 | 710 | cmd.extend(["--append=iscsi_auto"]) | 718 | cmd.extend(["--append=iscsi_auto"]) |
415 | 711 | 719 | ||
416 | 712 | # publish the ephemeral image (used in root=URL) | 720 | # publish the ephemeral image (used in root=URL) |
418 | 713 | cmd.append("--publish=%s" % ftypes[cls.ephemeral_ftype]) | 721 | cmd.append("--publish=%s:%s" % (ftypes[cls.ephemeral_ftype], |
419 | 722 | root_pubpath)) | ||
420 | 714 | logger.info("Publishing ephemeral image as %s", cmd[-1]) | 723 | logger.info("Publishing ephemeral image as %s", cmd[-1]) |
421 | 715 | # publish the target image | ||
422 | 716 | cmd.append("--publish=%s" % ftypes[cls.target_ftype]) | ||
423 | 717 | logger.info("Publishing target image as %s", cmd[-1]) | ||
424 | 718 | 724 | ||
425 | 719 | # set curtin install source | 725 | # set curtin install source |
428 | 720 | install_src = cls.get_install_source(ftypes) | 726 | install_src, publishes = cls.get_install_source(ftypes) |
427 | 721 | |||
429 | 722 | logger.info("Curtin install source URI: %s", install_src) | 727 | logger.info("Curtin install source URI: %s", install_src) |
430 | 728 | if len(publishes): | ||
431 | 729 | cmd.extend(['--publish=%s' % p for p in publishes]) | ||
432 | 730 | logger.info("Publishing install sources: %s", | ||
433 | 731 | cmd[-len(publishes):]) | ||
434 | 723 | 732 | ||
435 | 724 | # check for network configuration | 733 | # check for network configuration |
436 | 725 | cls.network_state = curtin_net.parse_net_config(cls.conf_file) | 734 | cls.network_state = curtin_net.parse_net_config(cls.conf_file) |
437 | @@ -1082,19 +1091,21 @@ class VMBaseClass(TestCase): | |||
438 | 1082 | 1091 | ||
439 | 1083 | @classmethod | 1092 | @classmethod |
440 | 1084 | def get_install_source(cls, ftypes): | 1093 | def get_install_source(cls, ftypes): |
444 | 1085 | if cls.target_ftype == 'squashfs': | 1094 | """Return install uri and a list of files needed to be published.""" |
445 | 1086 | # If we're installing from squashfs source then direct | 1095 | # if release (install environment) is the same as target |
446 | 1087 | # curtin to install from the read-only undermount | 1096 | # target (thing to install) then install via cp:// |
447 | 1097 | if cls.release == cls.target_release: | ||
448 | 1088 | install_src = "cp:///media/root-ro" | 1098 | install_src = "cp:///media/root-ro" |
456 | 1089 | else: | 1099 | return install_src, [] |
450 | 1090 | if cls.target_ftype == 'root-image.xz': | ||
451 | 1091 | stype = "dd-xz" | ||
452 | 1092 | else: | ||
453 | 1093 | stype = "tgz" | ||
454 | 1094 | src = os.path.basename(ftypes[cls.target_ftype]) | ||
455 | 1095 | install_src = "%s:PUBURL/%s" % (stype, src) | ||
457 | 1096 | 1100 | ||
459 | 1097 | return install_src | 1101 | # publish the file to target/<ftype> |
460 | 1102 | # and set the install source to <type>:PUBURL/target/<ftype> | ||
461 | 1103 | ttype2stype = {'root-image.xz': 'dd-xz', 'squashfs': 'fsimage'} | ||
462 | 1104 | ftype = cls.target_ftype | ||
463 | 1105 | pubpath = "target/" + ftype | ||
464 | 1106 | src = "%s:PUBURL/%s" % (ttype2stype.get(ftype, 'tgz'), pubpath) | ||
465 | 1107 | publishes = [ftypes["target/" + ftype] + ":" + pubpath] | ||
466 | 1108 | return src, publishes | ||
467 | 1098 | 1109 | ||
468 | 1099 | @classmethod | 1110 | @classmethod |
469 | 1100 | def tearDownClass(cls): | 1111 | def tearDownClass(cls): |
470 | @@ -1711,6 +1722,9 @@ def is_unsupported_ubuntu(release): | |||
471 | 1711 | elif util.which(udi): | 1722 | elif util.which(udi): |
472 | 1712 | _UNSUPPORTED_UBUNTU = util.subp( | 1723 | _UNSUPPORTED_UBUNTU = util.subp( |
473 | 1713 | [udi, '--unsupported'], capture=True)[0].splitlines() | 1724 | [udi, '--unsupported'], capture=True)[0].splitlines() |
474 | 1725 | # precise ESM support. | ||
475 | 1726 | if 'precise' in _UNSUPPORTED_UBUNTU: | ||
476 | 1727 | _UNSUPPORTED_UBUNTU.remove('precise') | ||
477 | 1714 | else: | 1728 | else: |
478 | 1715 | # no way to tell. | 1729 | # no way to tell. |
479 | 1716 | return None | 1730 | return None |
480 | diff --git a/tests/vmtests/releases.py b/tests/vmtests/releases.py | |||
481 | index f365824..7c133db 100644 | |||
482 | --- a/tests/vmtests/releases.py | |||
483 | +++ b/tests/vmtests/releases.py | |||
484 | @@ -46,6 +46,17 @@ class _Centos66FromXenialBase(_CentosFromUbuntuBase): | |||
485 | 46 | target_release = "centos66" | 46 | target_release = "centos66" |
486 | 47 | 47 | ||
487 | 48 | 48 | ||
488 | 49 | class _PreciseBase(_UbuntuBase): | ||
489 | 50 | release = "xenial" | ||
490 | 51 | target_release = "precise" | ||
491 | 52 | target_distro = "ubuntu" | ||
492 | 53 | target_ftype = "squashfs" | ||
493 | 54 | |||
494 | 55 | |||
495 | 56 | class _PreciseHWET(_PreciseBase): | ||
496 | 57 | target_kernel_package = 'linux-generic-lts-trusty' | ||
497 | 58 | |||
498 | 59 | |||
499 | 49 | class _TrustyBase(_UbuntuBase): | 60 | class _TrustyBase(_UbuntuBase): |
500 | 50 | release = "trusty" | 61 | release = "trusty" |
501 | 51 | 62 | ||
502 | @@ -105,6 +116,8 @@ class _BionicBase(_UbuntuBase): | |||
503 | 105 | 116 | ||
504 | 106 | class _Releases(object): | 117 | class _Releases(object): |
505 | 107 | trusty = _TrustyBase | 118 | trusty = _TrustyBase |
506 | 119 | precise = _PreciseBase | ||
507 | 120 | precise_hwe_t = _PreciseHWET | ||
508 | 108 | trusty_hwe_u = _TrustyHWEU | 121 | trusty_hwe_u = _TrustyHWEU |
509 | 109 | trusty_hwe_v = _TrustyHWEV | 122 | trusty_hwe_v = _TrustyHWEV |
510 | 110 | trusty_hwe_w = _TrustyHWEW | 123 | trusty_hwe_w = _TrustyHWEW |
511 | diff --git a/tests/vmtests/test_uefi_basic.py b/tests/vmtests/test_uefi_basic.py | |||
512 | index 712075c..d6a58eb 100644 | |||
513 | --- a/tests/vmtests/test_uefi_basic.py | |||
514 | +++ b/tests/vmtests/test_uefi_basic.py | |||
515 | @@ -78,6 +78,20 @@ class TestBasicAbs(VMBaseClass): | |||
516 | 78 | self.assertEqual(self.disk_block_size, size) | 78 | self.assertEqual(self.disk_block_size, size) |
517 | 79 | 79 | ||
518 | 80 | 80 | ||
519 | 81 | class PreciseUefiTestBasic(relbase.precise, TestBasicAbs): | ||
520 | 82 | __test__ = False | ||
521 | 83 | |||
522 | 84 | def test_ptable(self): | ||
523 | 85 | print("test_ptable does not work for Precise") | ||
524 | 86 | |||
525 | 87 | def test_dname(self): | ||
526 | 88 | print("test_dname does not work for Precise") | ||
527 | 89 | |||
528 | 90 | |||
529 | 91 | class PreciseHWETUefiTestBasic(relbase.precise_hwe_t, PreciseUefiTestBasic): | ||
530 | 92 | __test__ = False | ||
531 | 93 | |||
532 | 94 | |||
533 | 81 | class TrustyUefiTestBasic(relbase.trusty, TestBasicAbs): | 95 | class TrustyUefiTestBasic(relbase.trusty, TestBasicAbs): |
534 | 82 | __test__ = True | 96 | __test__ = True |
535 | 83 | 97 | ||
536 | diff --git a/tools/launch b/tools/launch | |||
537 | index 179dd82..b273de4 100755 | |||
538 | --- a/tools/launch | |||
539 | +++ b/tools/launch | |||
540 | @@ -624,7 +624,12 @@ main() { | |||
541 | 624 | debug 1 "added $ip to existing no_proxy (${no_proxy})";; | 624 | debug 1 "added $ip to existing no_proxy (${no_proxy})";; |
542 | 625 | esac | 625 | esac |
543 | 626 | 626 | ||
545 | 627 | local tok tok_split src pub fpath | 627 | start_http "${TEMP_D}" "$ip" "$http_port" "${HTTP_TRIES}" </dev/null || |
546 | 628 | { error "failed to start http service"; return 1; } | ||
547 | 629 | http_port=$_RET | ||
548 | 630 | burl="http://$ip:${http_port}" | ||
549 | 631 | |||
550 | 632 | local tok tok_split src pub fpath rdir | ||
551 | 628 | # tok in pubs looks like file[:pubname] | 633 | # tok in pubs looks like file[:pubname] |
552 | 629 | # link them into the temp dir for publishing | 634 | # link them into the temp dir for publishing |
553 | 630 | for tok in "${pubs[@]}"; do | 635 | for tok in "${pubs[@]}"; do |
554 | @@ -633,19 +638,20 @@ main() { | |||
555 | 633 | pub=${tok_split[1]} | 638 | pub=${tok_split[1]} |
556 | 634 | fpath=$(readlink -f "$src") || | 639 | fpath=$(readlink -f "$src") || |
557 | 635 | { error "'$src': failed to get path"; return 1; } | 640 | { error "'$src': failed to get path"; return 1; } |
559 | 636 | if [ -z "$pub" ]; then | 641 | if [ -n "$pub" ]; then |
560 | 642 | rdir=$(dirname "$pub") | ||
561 | 643 | [ -d "${TEMP_D}/$rdir" ] || mkdir -p "${TEMP_D}/$rdir" || { | ||
562 | 644 | error "Failed to make <pubdir>/$rdir for publish of $pub"; | ||
563 | 645 | return 1; | ||
564 | 646 | } | ||
565 | 647 | else | ||
566 | 637 | pub="${src##*/}" | 648 | pub="${src##*/}" |
567 | 638 | fi | 649 | fi |
570 | 639 | ln -sf "$fpath" "${TEMP_D}/${pub}" | 650 | ln -sf "$fpath" "${TEMP_D}/${pub}" || |
571 | 640 | debug 1 "publishing: $fpath to ${TEMP_D}/${pub}" | 651 | { error "failed to link $fpath to <pubdir>/$pub"; return 1; } |
572 | 652 | debug 1 "publishing: $fpath to $burl/${pub}" | ||
573 | 641 | done | 653 | done |
574 | 642 | 654 | ||
575 | 643 | start_http "${TEMP_D}" "$ip" "$http_port" "${HTTP_TRIES}" </dev/null || | ||
576 | 644 | { error "failed to start http service"; return 1; } | ||
577 | 645 | http_port=$_RET | ||
578 | 646 | burl="http://$ip:${http_port}" | ||
579 | 647 | |||
580 | 648 | |||
581 | 649 | local addargs="" f="" | 655 | local addargs="" f="" |
582 | 650 | addargs=( ) | 656 | addargs=( ) |
583 | 651 | for f in "${addfiles[@]}"; do | 657 | for f in "${addfiles[@]}"; do |
no tests yet, but tested installation using both url and file from xenial squashfs image on http:// cloud-images. ubuntu. com as described in /gist.github. com/smoser/ 375123ef1ef098b e23cc856a5772c5 c8
https:/