summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorJosh Kearney <josh@jk0.org>2013-07-18 13:57:54 -0500
committerRick Harris <rconradharris@gmail.com>2013-08-21 22:17:33 +0000
commit614c4a2359123ad0ecac7ac0d168be8e246a5f9d (patch)
tree4e35e66ffc1591bc4283b9b9d4e1d5ddfbb83405 /plugins
parent0ee03b07305d7d0e6c910579e8c66d53741cf9c0 (diff)
downloadnova-614c4a2359123ad0ecac7ac0d168be8e246a5f9d.tar.gz
xenapi: Added iPXE ISO boot support
This patch adds support for a new kind of Glance image, a specially crafted ISO which supports iPXE booting, giving customers a means to roll their own image. Two virt-layer modifications were needed. The first was adding configurations for the iPXE ISO feature (network to use, boot menu, mkisofs_cmd). The second was the ability to inject networking info into the ISO after it was downloaded. To use this feature, operators should enable the `ipxe_boot` image-property. DocImpact Implements blueprint xenapi-ipxe-iso-boot-support Change-Id: I33acf9dfdff0a5ed9797723a142bc451348e8549
Diffstat (limited to 'plugins')
-rw-r--r--plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec1
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe134
-rw-r--r--plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py12
3 files changed, 146 insertions, 1 deletions
diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
index 85c2d1c05d..84578cf540 100644
--- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
+++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
@@ -34,6 +34,7 @@ rm -rf $RPM_BUILD_ROOT
/etc/xapi.d/plugins/config_file
/etc/xapi.d/plugins/console
/etc/xapi.d/plugins/glance
+/etc/xapi.d/plugins/ipxe
/etc/xapi.d/plugins/kernel
/etc/xapi.d/plugins/migration
/etc/xapi.d/plugins/pluginlib_nova.py
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe b/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe
new file mode 100755
index 0000000000..a41acb448e
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/ipxe
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2013 Openstack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Inject network configuration into iPXE ISO for boot."""
+
+import os
+import shutil
+
+import utils
+
+#FIXME(sirp): should this use pluginlib from 5.6?
+from pluginlib_nova import *
+configure_logging('ipxe')
+
+
+ISOLINUX_CFG = """SAY iPXE ISO boot image
+TIMEOUT 30
+DEFAULT ipxe.krn
+LABEL ipxe.krn
+ KERNEL ipxe.krn
+ INITRD netcfg.ipxe
+"""
+
+NETCFG_IPXE = """#!ipxe
+:start
+imgfree
+ifclose net0
+set net0/ip %(ip_address)s
+set net0/netmask %(netmask)s
+set net0/gateway %(gateway)s
+set dns %(dns)s
+ifopen net0
+goto menu
+
+:menu
+chain %(boot_menu_url)s
+goto boot
+
+:boot
+sanboot --no-describe --drive 0x80
+"""
+
+
+def _write_file(filename, data):
+ # If the ISO was tampered with such that the destination is a symlink,
+ # that could allow a malicious user to write to protected areas of the
+ # dom0 filesystem. /HT to comstud for pointing this out.
+ #
+ # Short-term, checking that the destination is not a symlink should be
+ # sufficient.
+ #
+ # Long-term, we probably want to perform all file manipulations within a
+ # chroot jail to be extra safe.
+ if os.path.islink(filename):
+ raise RuntimeError('SECURITY: Cannot write to symlinked destination')
+
+ logging.debug("Writing to file '%s'" % filename)
+ f = open(filename, 'w')
+ try:
+ f.write(data)
+ finally:
+ f.close()
+
+
+def _unbundle_iso(sr_path, filename, path):
+ logging.debug("Unbundling ISO '%s'" % filename)
+ read_only_path = utils.make_staging_area(sr_path)
+ try:
+ utils.run_command(['mount', '-o', 'loop', filename, read_only_path])
+ try:
+ shutil.copytree(read_only_path, path)
+ finally:
+ utils.run_command(['umount', read_only_path])
+ finally:
+ utils.cleanup_staging_area(read_only_path)
+
+
+def _create_iso(mkisofs_cmd, filename, path):
+ logging.debug("Creating ISO '%s'..." % filename)
+ orig_dir = os.getcwd()
+ os.chdir(path)
+ try:
+ utils.run_command([mkisofs_cmd, '-quiet', '-l', '-o', filename,
+ '-c', 'boot.cat', '-b', 'isolinux.bin',
+ '-no-emul-boot', '-boot-load-size', '4',
+ '-boot-info-table', '.'])
+ finally:
+ os.chdir(orig_dir)
+
+
+def inject(session, sr_path, vdi_uuid, boot_menu_url, ip_address, netmask,
+ gateway, dns, mkisofs_cmd):
+
+ iso_filename = '%s.img' % os.path.join(sr_path, 'iso', vdi_uuid)
+
+ # Create staging area so we have a unique path but remove it since
+ # shutil.copytree will recreate it
+ staging_path = utils.make_staging_area(sr_path)
+ utils.cleanup_staging_area(staging_path)
+
+ try:
+ _unbundle_iso(sr_path, iso_filename, staging_path)
+
+ # Write Configs
+ _write_file(os.path.join(staging_path, 'netcfg.ipxe'),
+ NETCFG_IPXE % {"ip_address": ip_address,
+ "netmask": netmask,
+ "gateway": gateway,
+ "dns": dns,
+ "boot_menu_url": boot_menu_url})
+
+ _write_file(os.path.join(staging_path, 'isolinux.cfg'),
+ ISOLINUX_CFG)
+
+ _create_iso(mkisofs_cmd, iso_filename, staging_path)
+ finally:
+ utils.cleanup_staging_area(staging_path)
+
+
+if __name__ == "__main__":
+ utils.register_plugin_calls(inject)
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py
index 8404fd961b..a9002ad245 100644
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py
@@ -28,6 +28,10 @@ LOG = logging.getLogger(__name__)
CHUNK_SIZE = 8192
+class CommandNotFound(Exception):
+ pass
+
+
def delete_if_exists(path):
try:
os.unlink(path)
@@ -65,7 +69,13 @@ def make_subprocess(cmdline, stdout=False, stderr=False, stdin=False,
kwargs['stdin'] = stdin and subprocess.PIPE or None
kwargs['universal_newlines'] = universal_newlines
kwargs['close_fds'] = close_fds
- proc = subprocess.Popen(cmdline, **kwargs)
+ try:
+ proc = subprocess.Popen(cmdline, **kwargs)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ raise CommandNotFound
+ else:
+ raise
return proc