launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #15575
[Merge] lp:~andreserl/maas/trunk-fpi into lp:maas
Andres Rodriguez has proposed merging lp:~andreserl/maas/trunk-fpi into lp:maas.
Commit message:
Merge Fast Path Installer.
Requested reviews:
Andres Rodriguez (andreserl)
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~andreserl/maas/trunk-fpi/+merge/163382
--
https://code.launchpad.net/~andreserl/maas/trunk-fpi/+merge/163382
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~andreserl/maas/trunk-fpi into lp:maas.
=== added file 'contrib/maas-cluster-http.conf'
--- contrib/maas-cluster-http.conf 1970-01-01 00:00:00 +0000
+++ contrib/maas-cluster-http.conf 2013-05-10 22:54:27 +0000
@@ -0,0 +1,6 @@
+# Server static files for tftp images as FPI
+# installer needs them
+Alias /MAAS/static/images/ /var/lib/maas/tftp/
+<Directory /var/lib/maas/tftp/>
+ SetHandler None
+</Directory>
=== modified file 'contrib/preseeds_v2/generic'
--- contrib/preseeds_v2/generic 2012-11-14 10:32:25 +0000
+++ contrib/preseeds_v2/generic 2013-05-10 22:54:27 +0000
@@ -1,4 +1,8 @@
+{{if node.should_use_traditional_installer() }}
{{inherit "preseed_master"}}
+{{else}}
+{{inherit "preseed_xinstall"}}
+{{endif}}
{{def proxy}}
d-i mirror/country string manual
=== added file 'contrib/preseeds_v2/preseed_xinstall'
--- contrib/preseeds_v2/preseed_xinstall 1970-01-01 00:00:00 +0000
+++ contrib/preseeds_v2/preseed_xinstall 2013-05-10 22:54:27 +0000
@@ -0,0 +1,1 @@
+# Disabled by default
=== modified file 'scripts/maas-import-ephemerals'
--- scripts/maas-import-ephemerals 2012-11-23 11:00:04 +0000
+++ scripts/maas-import-ephemerals 2013-05-10 22:54:27 +0000
@@ -239,6 +239,9 @@
tar -Sxzf - -C "$exdir" < "$tarball" ||
{ error "failed to extract tarball from $furl"; return 1; }
+ # Create root disk
+ uec2roottar "$tarball" || { error "failed to create root image"; return 1; }
+
# Look for our files in the extracted tarball.
local x="" img="" kernel="" initrd=""
for x in "$exdir/"*.img; do
@@ -342,12 +345,14 @@
return 1
copy_first_available "$src/initrd.gz" "$src/initrd" "$tmpdir/initrd.gz" ||
return 1
+ copy_first_available "$src/dist-root.tar.gz" "" "$tmpdir/root.tar.gz" ||
+ return 1
fi
local cmd out=""
cmd=( maas-provision install-pxe-image
"--arch=$arch" "--subarch=$subarch" "--release=$release"
- --purpose="commissioning" --image="$tmpdir" )
+ --purpose="commissioning" --image="$tmpdir" --symlink="xinstall")
debug 2 "${cmd[@]}"
out=$("${cmd[@]}" 2>&1) ||
{ error "cmd failed:" "${cmd[@]}"; error "$out"; return 1; }
=== added file 'scripts/uec2roottar'
--- scripts/uec2roottar 1970-01-01 00:00:00 +0000
+++ scripts/uec2roottar 2013-05-10 22:54:27 +0000
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+TEMP_D=""
+MP=""
+
+Usage() {
+cat <<EOF
+ ${0##*/} converts an ubuntu ephemeral image into a root
+ disk image tarball.
+
+ Usage: ${0##*/} uec-tarball [output]
+EOF
+}
+error() { echo "$(date -R):" "$@" 1>&2; }
+fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
+cleanup() {
+ [ -z "$MP" ] || umount "$MP"
+ [ -z "$TEMP_D" -o ! -d "$TEMP_D" ] || rm -Rf "${TEMP_D}";
+}
+
+[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; }
+[ $# -eq 1 -o $# -eq 2 ] || { Usage 1>&2; exit 1; }
+[ -f "$1" ] || { Usage 1>&2; error "$1: not a file"; exit 1; }
+
+[ "$(id -u)" = "0" ] || fail "must be root"
+
+TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || fail "failed mktemp"
+trap cleanup EXIT
+
+uec="$1"
+output=${2}
+
+[ -n "$output" ] || output="${uec%.tar.gz}-root.tar.gz";
+error "converting ${uec} to ${output}"
+
+error "extracting *.img from ${uec}"
+tar -C ${TEMP_D} --wildcards "*.img" -Sxvzf "$uec" ||
+ fail "failed extract $uec"
+
+img=""
+for cand in "${TEMP_D}/"*.img; do
+ [ -z "$img" ] || fail "multiple .img files in $uec"
+ [ -f "$cand" ] && img="$cand"
+done
+
+[ -n "$img" ] || fail "failed to find image in $uec"
+
+mkdir "${TEMP_D}/mp" && mount -o ro "$img" "${TEMP_D}/mp" &&
+ MP="$TEMP_D/mp" || fail "failed to mount $img"
+
+error "copying contents of ${img#${TEMP_D}/} in ${uec} to ${output}"
+tar -C "$MP" -cpSzf "${output}" --numeric-owner . ||
+ fail "failed to create ${output}"
+
+error "finished. wrote to ${output}"
+
=== modified file 'setup.py'
--- setup.py 2012-12-18 17:06:43 +0000
+++ setup.py 2013-05-10 22:54:27 +0000
@@ -62,6 +62,7 @@
'etc/maas/import_ephemerals',
'etc/maas/import_pxe_files',
'contrib/maas-http.conf',
+ 'contrib/maas-cluster-http.conf',
'contrib/maas_local_settings.py']),
('/usr/share/maas',
['contrib/wsgi.py',
@@ -73,10 +74,13 @@
'contrib/preseeds_v2/enlist',
'contrib/preseeds_v2/generic',
'contrib/preseeds_v2/enlist_userdata',
+ 'contrib/preseeds_v2/preseed_xinstall',
'contrib/preseeds_v2/preseed_master']),
('/usr/sbin',
['scripts/maas-import-ephemerals',
'scripts/maas-import-pxe-files']),
+ ('/usr/bin',
+ ['scripts/uec2roottar']),
],
install_requires=[
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2013-05-08 23:03:51 +0000
+++ src/maasserver/api.py 2013-05-10 22:54:27 +0000
@@ -1753,7 +1753,10 @@
elif node.status == NODE_STATUS.ALLOCATED:
# Install the node if netboot is enabled, otherwise boot locally.
if node.netboot:
- return "install"
+ if node.should_use_traditional_installer():
+ return "install"
+ else:
+ return "xinstall"
else:
return "local" # TODO: Investigate.
else:
=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py 2012-11-23 09:54:47 +0000
+++ src/maasserver/models/nodegroup.py 2013-05-10 22:54:27 +0000
@@ -204,6 +204,11 @@
self.api_key = api_token.key
return super(NodeGroup, self).save(*args, **kwargs)
+ def get_any_interface(self):
+ for interface in self.nodegroupinterface_set.all():
+ return interface
+ return None
+
def get_managed_interface(self):
"""Return the interface for which MAAS managed the DHCP service.
=== modified file 'src/maasserver/preseed.py'
--- src/maasserver/preseed.py 2013-04-08 19:56:00 +0000
+++ src/maasserver/preseed.py 2013-05-10 22:54:27 +0000
@@ -232,7 +232,13 @@
else:
base_url = nodegroup.maas_url
cluster_if = nodegroup.get_managed_interface()
- cluster_host = None if cluster_if is None else cluster_if.ip
+ any_cluster_if = nodegroup.get_any_interface()
+ cluster_host = None
+ if cluster_if is None:
+ if any_cluster_if is not None:
+ cluster_host = any_cluster_if.ip
+ else:
+ cluster_host = cluster_if.ip
return {
'main_archive_hostname': main_archive_hostname,
'main_archive_directory': main_archive_directory,
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2013-05-08 23:03:51 +0000
+++ src/maasserver/tests/test_api.py 2013-05-10 22:54:27 +0000
@@ -3776,11 +3776,14 @@
("poweroff", {"status": NODE_STATUS.READY}),
("poweroff", {"status": NODE_STATUS.RESERVED}),
("install", {"status": NODE_STATUS.ALLOCATED, "netboot": True}),
+ ("xinstall", {"status": NODE_STATUS.ALLOCATED, "netboot": True}),
("local", {"status": NODE_STATUS.ALLOCATED, "netboot": False}),
("poweroff", {"status": NODE_STATUS.RETIRED}),
]
node = factory.make_node()
for purpose, parameters in options:
+ if purpose == "xinstall":
+ node.use_fastpath_installer()
for name, value in parameters.items():
setattr(node, name, value)
self.assertEqual(purpose, api.get_boot_purpose(node))
=== modified file 'src/maasserver/tests/test_preseed.py'
--- src/maasserver/tests/test_preseed.py 2013-02-21 23:32:20 +0000
+++ src/maasserver/tests/test_preseed.py 2013-05-10 22:54:27 +0000
@@ -368,17 +368,19 @@
nodegroup.get_managed_interface().ip,
context["cluster_host"])
- def test_preseed_context_null_cluster_host_if_unmanaged(self):
- # If the nodegroup has no managed interface recorded, which is
- # possible in the data model but would be a bit weird, the
- # cluster_host context variable is present, but None.
+ def test_preseed_context_cluster_host_if_unmanaged(self):
+ # If the nodegroup has no managed interface recorded, the cluster_host
+ # context variable is still present and derived from the nodegroup.
release = factory.getRandomString()
nodegroup = factory.make_node_group(maas_url=factory.getRandomString())
for interface in nodegroup.nodegroupinterface_set.all():
interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED
interface.save()
context = get_preseed_context(release, nodegroup)
- self.assertIsNone(context["cluster_host"])
+ self.assertIsNotNone(context["cluster_host"])
+ self.assertEqual(
+ nodegroup.get_any_interface().ip,
+ context["cluster_host"])
def test_preseed_context_null_cluster_host_if_does_not_exist(self):
# If there's no nodegroup, the cluster_host context variable is
@@ -525,6 +527,12 @@
preseed = get_preseed(node)
self.assertIn('preseed/late_command', preseed)
+ def test_get_preseed_returns_xinstall_preseed(self):
+ node = factory.make_node()
+ node.use_fastpath_installer()
+ preseed = get_preseed(node)
+ self.assertIn('# Disabled by default', preseed)
+
def test_get_enlist_preseed_returns_enlist_preseed(self):
preseed = get_enlist_preseed()
self.assertTrue(preseed.startswith('#cloud-config'))
=== modified file 'src/provisioningserver/kernel_opts.py'
--- src/provisioningserver/kernel_opts.py 2013-02-01 01:07:25 +0000
+++ src/provisioningserver/kernel_opts.py 2013-05-10 22:54:27 +0000
@@ -124,11 +124,11 @@
def compose_purpose_opts(params):
"""Return the list of the purpose-specific kernel options."""
- if params.purpose == "commissioning":
+ if params.purpose == "commissioning" or params.purpose == "xinstall":
# These are kernel parameters read by the ephemeral environment.
tname = "%s:%s" % (ISCSI_TARGET_NAME_PREFIX,
get_ephemeral_name(params.release, params.arch))
- return [
+ kernel_params = [
# Read by the open-iscsi initramfs code.
"iscsi_target_name=%s" % tname,
"iscsi_target_ip=%s" % params.fs_host,
@@ -145,6 +145,9 @@
# Read by cloud-init.
"cloud-config-url=%s" % params.preseed_url,
]
+ if params.purpose == "xinstall":
+ kernel_params.append("ds=nocloud-net")
+ return kernel_params
else:
# These are options used by the Debian Installer.
return [
=== added file 'src/provisioningserver/pxe/config.xinstall.template'
--- src/provisioningserver/pxe/config.xinstall.template 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/pxe/config.xinstall.template 2013-05-10 22:54:27 +0000
@@ -0,0 +1,20 @@
+DEFAULT execute
+
+SAY Booting under MAAS direction...
+SAY {{kernel_params() | kernel_command}}
+
+LABEL execute
+ KERNEL ifcpu64.c32
+ APPEND amd64 -- i386
+
+LABEL amd64
+ KERNEL {{kernel_params(arch="amd64") | kernel_path }}
+ INITRD {{kernel_params(arch="amd64") | initrd_path }}
+ APPEND {{kernel_params(arch="amd64") | kernel_command}}
+ IPAPPEND 2
+
+LABEL i386
+ KERNEL {{kernel_params(arch="i386") | kernel_path }}
+ INITRD {{kernel_params(arch="i386") | initrd_path }}
+ APPEND {{kernel_params(arch="i386") | kernel_command}}
+ IPAPPEND 2
=== modified file 'src/provisioningserver/pxe/install_image.py'
--- src/provisioningserver/pxe/install_image.py 2012-08-31 17:21:01 +0000
+++ src/provisioningserver/pxe/install_image.py 2013-05-10 22:54:27 +0000
@@ -70,7 +70,7 @@
return False
-def install_dir(new, old):
+def install_dir(new, old, symlink=None):
"""Install directory `new`, replacing directory `old` if it exists.
This works as atomically as possible, but isn't entirely. Moreover,
@@ -121,6 +121,13 @@
# Now delete the old image directory at leisure.
rmtree('%s.old' % old, ignore_errors=True)
+ # Symlink the new image directory to 'symlink'.
+ if symlink is not None:
+ sdest = "%s/%s" % (os.path.dirname(old), symlink)
+ if os.path.exists(sdest) or os.path.islink(sdest):
+ os.unlink(sdest)
+ os.symlink(old, sdest)
+
def add_arguments(parser):
parser.add_argument(
@@ -138,6 +145,9 @@
parser.add_argument(
'--image', dest='image', default=None,
help="Netboot image directory, containing kernel & initrd.")
+ parser.add_argument(
+ '--symlink', dest='symlink', default=None,
+ help="Destination directory to symlink the installed images to.")
def run(args):
@@ -154,5 +164,5 @@
tftproot, args.arch, args.subarch, args.release, args.purpose)
if not are_identical_dirs(destination, args.image):
# Image has changed. Move the new version into place.
- install_dir(args.image, destination)
+ install_dir(args.image, destination, args.symlink)
rmtree(args.image, ignore_errors=True)
=== modified file 'src/provisioningserver/pxe/tests/test_config.py'
--- src/provisioningserver/pxe/tests/test_config.py 2012-09-19 13:51:15 +0000
+++ src/provisioningserver/pxe/tests/test_config.py 2013-05-10 22:54:27 +0000
@@ -223,14 +223,22 @@
self.assertIn("chain.c32", output)
self.assertNotIn("LOCALBOOT", output)
- def test_render_pxe_config_for_commissioning(self):
+class TestRenderPXEConfigScenarios(TestCase):
+ """Tests for `provisioningserver.pxe.config.render_pxe_config`."""
+
+ scenarios = [
+ ("commissioning", dict(purpose="commissioning")),
+ ("xinstall", dict(purpose="xinstall")),
+ ]
+
+ def test_render_pxe_config_scenarios(self):
# The commissioning config uses an extra PXELINUX module to auto
# select between i386 and amd64.
get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
get_ephemeral_name.return_value = factory.make_name("ephemeral")
options = {
"kernel_params":
- make_kernel_parameters(purpose="commissioning"),
+ make_kernel_parameters(purpose=self.purpose),
}
output = render_pxe_config(**options)
config = parse_pxe_config(output)
=== modified file 'src/provisioningserver/pxe/tests/test_install_image.py'
--- src/provisioningserver/pxe/tests/test_install_image.py 2012-08-31 15:41:18 +0000
+++ src/provisioningserver/pxe/tests/test_install_image.py 2013-05-10 22:54:27 +0000
@@ -209,3 +209,38 @@
self.assertEqual(
"rw-r--r--",
target_dir.child("image").getPermissions().shorthand())
+
+ def test_install_dir_moves_dir_into_place_with_symlink(self):
+ download_image = os.path.join(self.make_dir(), 'download-image')
+ published_image = os.path.join(self.make_dir(), 'published-image')
+ base_path = os.path.dirname(published_image)
+ symlink_dest = 'xinstall'
+ contents = factory.getRandomString()
+ os.makedirs(download_image)
+ sample_file = factory.make_file(download_image, contents=contents)
+ install_dir(download_image, published_image, symlink_dest)
+ self.assertThat(
+ os.path.join(published_image, os.path.basename(sample_file)),
+ FileContains(contents))
+ self.assertThat(
+ os.path.join(base_path, symlink_dest, os.path.basename(sample_file)),
+ FileContains(contents))
+
+ def test_install_dir_replaces_existing_dir_with_symlink(self):
+ download_image = os.path.join(self.make_dir(), 'download-image')
+ published_image = os.path.join(self.make_dir(), 'published-image')
+ base_path = os.path.dirname(published_image)
+ symlink_dest = 'xinstall'
+ os.makedirs(download_image)
+ sample_file = factory.make_file(download_image)
+ os.makedirs(published_image)
+ os.symlink(published_image, os.path.join(base_path, symlink_dest))
+ obsolete_file = factory.make_file(published_image)
+ install_dir(download_image, published_image, symlink_dest)
+ self.assertThat(
+ os.path.join(published_image, os.path.basename(sample_file)),
+ FileExists())
+ self.assertThat(obsolete_file, Not(FileExists()))
+ self.assertThat(
+ os.path.join(base_path, symlink_dest, os.path.basename(sample_file)),
+ FileExists())
=== modified file 'src/provisioningserver/tests/test_kernel_opts.py'
--- src/provisioningserver/tests/test_kernel_opts.py 2013-02-01 01:07:25 +0000
+++ src/provisioningserver/tests/test_kernel_opts.py 2013-05-10 22:54:27 +0000
@@ -118,6 +118,22 @@
"netcfg/choose_interface=auto",
compose_kernel_command_line(params))
+ def test_xinstall_compose_kernel_command_line_inc_purpose_opts(self):
+ # The result of compose_kernel_command_line includes the purpose
+ # options for a non "xinstall" node.
+ get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
+ get_ephemeral_name.return_value = "RELEASE-ARCH"
+ params = make_kernel_parameters(purpose="xinstall")
+ cmdline = compose_kernel_command_line(params)
+ self.assertThat(
+ cmdline,
+ ContainsAll([
+ "ds=nocloud-net",
+ "root=/dev/disk/by-path/ip-",
+ "iscsi_initiator=",
+ "overlayroot=tmpfs",
+ "ip=::::%s:BOOTIF" % params.hostname]))
+
def test_commissioning_compose_kernel_command_line_inc_purpose_opts(self):
# The result of compose_kernel_command_line includes the purpose
# options for a non "commissioning" node.
@@ -147,8 +163,8 @@
self.assertNotIn(cmdline, "None")
def test_compose_kernel_command_line_inc_common_opts(self):
- # Test that some kernel arguments appear on both commissioning
- # and install command lines.
+ # Test that some kernel arguments appear on commissioning, install
+ # and xinstall command lines.
get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
get_ephemeral_name.return_value = "RELEASE-ARCH"
expected = ["nomodeset"]
@@ -159,6 +175,11 @@
self.assertThat(cmdline, ContainsAll(expected))
params = make_kernel_parameters(
+ purpose="xinstall", arch="i386")
+ cmdline = compose_kernel_command_line(params)
+ self.assertThat(cmdline, ContainsAll(expected))
+
+ params = make_kernel_parameters(
purpose="install", arch="i386")
cmdline = compose_kernel_command_line(params)
self.assertThat(cmdline, ContainsAll(expected))
@@ -182,6 +203,23 @@
factory.make_file(
ephemeral_dir, name='info', contents=ephemeral_info)
+ def test_compose_kernel_command_line_inc_purpose_opts_xinstall_node(self):
+ # The result of compose_kernel_command_line includes the purpose
+ # options for a "xinstall" node.
+ ephemeral_name = factory.make_name("ephemeral")
+ params = make_kernel_parameters(purpose="xinstall")
+ self.create_ephemeral_info(
+ ephemeral_name, params.arch, params.release)
+ self.assertThat(
+ compose_kernel_command_line(params),
+ ContainsAll([
+ "ds=nocloud-net",
+ "iscsi_target_name=%s:%s" % (
+ ISCSI_TARGET_NAME_PREFIX, ephemeral_name),
+ "iscsi_target_port=3260",
+ "iscsi_target_ip=%s" % params.fs_host,
+ ]))
+
def test_compose_kernel_command_line_inc_purpose_opts_comm_node(self):
# The result of compose_kernel_command_line includes the purpose
# options for a "commissioning" node.
Follow ups