2
# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
1
#!/usr/bin/env python2.7
3
# Copyright 2014 Canonical Ltd. This software is licensed under the
3
4
# GNU Affero General Public License version 3 (see the file LICENSE).
5
# Download static files needed for net-booting nodes through TFTP:
6
# pre-boot loader, kernels, and initrd images.
8
# This script downloads the required files into the TFTP home directory
9
# (by default, /var/lib/maas/tftp). Run it with the necessarily privileges
10
# to write them there.
12
# Exit immediately if a command exits with a non-zero status.
14
# Treat unset variables as an error when substituting.
17
# Load settings if available.
18
settings="/etc/maas/import_pxe_files"
19
[ -r $settings ] && . $settings
21
# Location of the GPG keyring for the Ubuntu archive.
22
GPG_KEYRING="${GPG_KEYRING:-/usr/share/keyrings/ubuntu-archive-keyring.gpg}"
24
# Whether to skip checking GPG keys (necessary for testing of this script).
25
# Set this to a nonempty string to skip checking. This is needed for test
26
# programs, because they can't possibly sign with the right key.
27
IGNORE_GPG="${IGNORE_GPG:-}"
29
# Download locations for Ubuntu releases. When the cluster controller runs
30
# the import scripts, it provides settings from the server side.
31
MAIN_ARCHIVE=${MAIN_ARCHIVE:-http://archive.ubuntu.com/ubuntu/}
32
PORTS_ARCHIVE=${PORTS_ARCHIVE:-http://ports.ubuntu.com/ubuntu-ports/}
34
# Ubuntu releases that are to be downloaded.
35
SUPPORTED_RELEASES=$(distro-info --supported)
36
RELEASES=${RELEASES:-$SUPPORTED_RELEASES}
38
# The current Ubuntu release.
39
STABLE_RELEASE=$(distro-info --stable)
41
# Supported architectures.
42
ARCHES=${ARCHES:-amd64/generic i386/generic armhf/highbank armhf/generic}
44
# Command line to download a resource at a given URL into the current
45
# directory. A wget command line will work here, but curl will do as well.
46
DOWNLOAD=${DOWNLOAD:-wget --no-verbose}
48
# Whether to download ephemeral images as well: "1" for yes, "0" for no.
50
IMPORT_EPHEMERALS=${IMPORT_EPHEMERALS:-1}
59
# Show script usage/summary.
61
echo "Usage: ${0##*/}"
63
echo "This helper script downloads the relevant boot images from an "
64
echo "Ubuntu archive and uses 'maas' to provision them for PXE booting "
67
echo "This script takes no arguments, but you can adjust some parameters "
68
echo -e "by editing the config file found at \033[1m$settings\033[0m."
70
echo "MAAS homepage:<http://maas.ubuntu.com>"
74
# Return a URL that points to the images directory for the relevant
76
compose_installer_base_url() {
77
local arch=$1 release=$2
81
local installer_url="$MAIN_ARCHIVE/dists/$release/main/installer-${arch%%/*}"
82
echo "$installer_url/current/images/"
85
# No ARM server installers were available in precise, so always go for -updates for now
86
# A better general fix is LP: #1052397
87
if [ "$release" = "precise" ]; then
92
local installer_url="$PORTS_ARCHIVE/dists/${release}${updates}/main/installer-${arch%%/*}"
93
echo "$installer_url/current/images/"
96
echo "Unknown architecture: $arch" >&2
102
# Return the URL part that is appended to the base url that gives the location
104
compose_installer_download_url_postfix() {
109
echo "netboot/ubuntu-installer/${arch%%/*}/"
112
echo "${arch#*/}/netboot/"
115
echo "Unknown architecture: $arch" >&2
121
# Put together a full URL for where the installer files for architecture $1
122
# and release $2 can be downloaded.
123
compose_installer_download_url() {
124
local arch=$1 release=$2
126
base_url=$(compose_installer_base_url $arch $release)
127
postfix=$(compose_installer_download_url_postfix $arch)
129
echo "$base_url/$postfix"
132
# Fetch MD5SUMS file. This returns false (i.e. nonzero) in the case of
133
# survivable failure, so that the caller can skip this image and move on.
134
fetch_server_md5sums() {
137
if ! $DOWNLOAD "$base_url/MD5SUMS"
139
echo "Unable to download $base_url/MD5SUMS" >&2
143
if [ "x$IGNORE_GPG" == "x" ]
145
if ! $DOWNLOAD "$base_url/MD5SUMS.gpg"
147
echo "Unable to download $base_url/MD5SUMS.gpg" >&2
151
if ! gpg --keyring=$GPG_KEYRING --verify MD5SUMS.gpg MD5SUMS >/dev/null 2>/dev/null
153
echo "Failed to verify MD5SUMS via $GPG_KEYRING ($base_url/MD5SUMS)" >&2
160
get_md5sum_for_file() {
163
# The filename supplied in $1 must be the full path as seen in the
164
# MD5SUMS file. The files are rooted from a single place so the grepped
165
# string will only match once.
166
server_md5sum=$(grep $filename MD5SUMS|awk '{print $1}') ||
167
fail "failed to find checksum for $filename"
172
local server_md5sum=$1 file_on_disk=$2
175
md5sum=$(md5sum $file_on_disk|awk '{print $1}')
177
if [ "$md5sum" != "$server_md5sum" ]; then
178
fail "md5 checksum mismatch for $file_on_disk: expected $server_md5sum, got $md5sum"
182
# Return a list of files for architecture $1 and release $2 that need to be
184
compose_installer_download_files() {
185
local arch=$1 release=$2
189
echo "linux initrd.gz"
192
echo "vmlinuz initrd.gz"
195
echo "Unknown architecture: $arch" >&2
202
# Rename downloaded files for architecture $1 and release $2 into the form that
204
rename_installer_download_files() {
205
local arch=$1 release=$2
215
echo "Unknown architecture: $arch" >&2
222
# Copy the pre-boot loader pxelinux.0, and modules we need, from the
223
# installed syslinux version. Install it into the TFTP tree for
225
update_pre_boot_loader() {
226
for loader_file in pxelinux.0 chain.c32 ifcpu64.c32
228
maas-provision install-pxe-bootloader \
229
--loader="/usr/lib/syslinux/$loader_file"
234
# Download kernel/initrd for installing Ubuntu release $2 for
236
download_install_files() {
237
local arch=$1 release=$2
238
local files file url file_prefix filename_in_md5sums_file md5sum
240
files=$(compose_installer_download_files $arch $release)
241
url=$(compose_installer_download_url $arch $release)
244
pushd "install" >/dev/null
245
if ! fetch_server_md5sums $(compose_installer_base_url $arch $release)
247
echo "Failed to download MD5 sums for $arch $release." >&2
252
echo "MD5SUMS GPG signature OK for $arch $release"
255
if ! $DOWNLOAD $url/$file
257
# Download failed. Log error, and skip this image.
258
echo "Failed to download $url/$file" >&2
263
file_prefix=$(compose_installer_download_url_postfix $arch)
264
filename_in_md5sums_file=./$file_prefix$file
265
md5sum=$(get_md5sum_for_file $filename_in_md5sums_file)
266
check_checksum $md5sum $file
267
echo "'$file' md5sum OK"
269
rename_installer_download_files $arch $release
274
# Download kernel/initrd for installing Ubuntu release $2 for
275
# architecture $1, and install them into the TFTP directory hierarchy.
276
update_install_files() {
277
local arch=$1 release=$2
279
# Try the -updates pocket first, and fall back to release if it failed.
280
if ! download_install_files $arch ${release}-updates
282
echo "$release-updates not available, falling back to release"
283
if ! download_install_files $arch $release
289
maas-provision install-pxe-image \
290
--arch="${arch%%/*}" --subarch="${arch#*/}" \
291
--release=$release --purpose="install" \
296
# Download and install the "install" images.
297
import_install_images() {
298
local arch release DOWNLOAD_DIR
300
DOWNLOAD_DIR=$(mktemp -d)
301
echo "Downloading to temporary location $DOWNLOAD_DIR."
302
pushd -- $DOWNLOAD_DIR
306
for release in $RELEASES
308
update_install_files $arch $release
313
rm -rf -- $DOWNLOAD_DIR
317
# Download and install the ephemeral images.
318
import_ephemeral_images() {
319
if test "$IMPORT_EPHEMERALS" != "0"
321
maas-import-ephemerals
327
# All files we create here are public. The TFTP user will need to be
331
update_pre_boot_loader
332
import_install_images
333
import_ephemeral_images
336
# check for commandline arguments
340
"-h"|"--help") show_usage ; exit ;;
344
if [ ! -f "$GPG_KEYRING" ]; then
345
fail "gpg keyring $GPG_KEYRING is not a file"
6
"""Import boot resources into MAAS cluster controller."""
8
from __future__ import (
18
from provisioningserver.import_images.boot_resources import (
26
if __name__ == "__main__":
27
parser = make_arg_parser(__doc__)
28
args = parser.parse_args()
32
logger.error("Config file %s not found." % args.config_file)
35
# logger.exception() will log the error message, followed by the
36
# exception's stack trace. Using it here rather than allowing
37
# the exception to kill the process unhandled means we can be
38
# sure about where the exception ends up, rather than it
39
# potentially vanishing.
40
logger.exception("Unhandled exception; unable to continue.")