~lutostag/ubuntu/trusty/maas/1.5.2+packagefix

« back to all changes in this revision

Viewing changes to src/provisioningserver/boot/uefi.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2014-03-28 10:43:53 UTC
  • mto: This revision was merged to the branch mainline in revision 57.
  • Revision ID: package-import@ubuntu.com-20140328104353-ekpolg0pm5xnvq2s
Tags: upstream-1.5+bzr2204
ImportĀ upstreamĀ versionĀ 1.5+bzr2204

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2012-2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""UEFI Boot Method"""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = [
 
16
    'UEFIBootMethod',
 
17
    ]
 
18
 
 
19
from itertools import repeat
 
20
import os.path
 
21
import re
 
22
from textwrap import dedent
 
23
import urllib2
 
24
 
 
25
from provisioningserver.boot import (
 
26
    BootMethod,
 
27
    BootMethodInstallError,
 
28
    get_parameters,
 
29
    utils,
 
30
    )
 
31
from provisioningserver.boot.install_bootloader import (
 
32
    install_bootloader,
 
33
    make_destination,
 
34
    )
 
35
from provisioningserver.utils import (
 
36
    call_and_check,
 
37
    tempdir,
 
38
    )
 
39
 
 
40
 
 
41
ARCHIVE_URL = "http://archive.ubuntu.com/ubuntu/dists/"
 
42
ARCHIVE_PATH = "/main/uefi/grub2-amd64/current/grubnetx64.efi.signed"
 
43
 
 
44
CONFIG_FILE = dedent("""
 
45
    # MAAS GRUB2 pre-loader configuration file
 
46
 
 
47
    # Load based on MAC address first.
 
48
    configfile (pxe)/grub/grub.cfg-${net_default_mac}
 
49
 
 
50
    # Failed to load based on MAC address.
 
51
    # Load amd64 by default, UEFI only supported by 64-bit
 
52
    configfile (pxe)/grub/grub.cfg-default-amd64
 
53
    """)
 
54
 
 
55
# GRUB EFINET represents a MAC address in IEEE 802 colon-seperated
 
56
# format. Required for UEFI as GRUB2 only presents the MAC address
 
57
# in colon-seperated format.
 
58
re_mac_address_octet = r'[0-9a-f]{2}'
 
59
re_mac_address = re.compile(
 
60
    ':'.join(repeat(re_mac_address_octet, 6)))
 
61
 
 
62
# Match the grub/grub.cfg-* request for UEFI (aka. GRUB2)
 
63
re_config_file = r'''
 
64
    # Optional leading slash(es).
 
65
    ^/*
 
66
    grub/grub[.]cfg   # UEFI (aka. GRUB2) expects this.
 
67
    -
 
68
    (?: # either a MAC
 
69
        (?P<mac>{re_mac_address.pattern}) # Capture UEFI MAC.
 
70
    | # or "default"
 
71
        default
 
72
          (?: # perhaps with specified arch, with a separator of '-'
 
73
            [-](?P<arch>\w+) # arch
 
74
            (?:-(?P<subarch>\w+))? # optional subarch
 
75
          )?
 
76
    )
 
77
    $
 
78
'''
 
79
 
 
80
re_config_file = re_config_file.format(
 
81
    re_mac_address=re_mac_address)
 
82
re_config_file = re.compile(re_config_file, re.VERBOSE)
 
83
 
 
84
 
 
85
def archive_grubnet_urls():
 
86
    """Paths to try to download grubnetx64.efi.signed."""
 
87
    release = utils.get_distro_release()
 
88
    # grubnetx64 will not work below version trusty, as efinet is broken
 
89
    # when loading kernel, force trusty. Note: This is only the grub version
 
90
    # this should not block any of the previous release from running.
 
91
    if release in ['lucid', 'precise', 'quantal', 'saucy']:
 
92
        release = 'trusty'
 
93
    for dist in ['%s-updates' % release, release]:
 
94
        yield "%s/%s/%s" % (
 
95
            ARCHIVE_URL.rstrip("/"),
 
96
            dist,
 
97
            ARCHIVE_PATH.rstrip("/"))
 
98
 
 
99
 
 
100
def download_grubnet(destination):
 
101
    """Downloads grubnetx64.efi.signed from the archive."""
 
102
    for url in archive_grubnet_urls():
 
103
        try:
 
104
            response = urllib2.urlopen(url)
 
105
        # Okay, if it fails as the updates area might not hold
 
106
        # that file.
 
107
        except urllib2.URLError:
 
108
            continue
 
109
 
 
110
        with open(destination, 'wb') as stream:
 
111
            stream.write(response.read())
 
112
        return True
 
113
    return False
 
114
 
 
115
 
 
116
class UEFIBootMethod(BootMethod):
 
117
 
 
118
    name = "uefi"
 
119
    template_subdir = "uefi"
 
120
    bootloader_path = "bootx64.efi"
 
121
    arch_octet = "00:07"  # AMD64 EFI
 
122
 
 
123
    def match_config_path(self, path):
 
124
        """Checks path for the configuration file that needs to be
 
125
        generated.
 
126
 
 
127
        :param path: requested path
 
128
        :returns: dict of match params from path, None if no match
 
129
        """
 
130
        match = re_config_file.match(path)
 
131
        if match is None:
 
132
            return None
 
133
        params = get_parameters(match)
 
134
 
 
135
        # MAC address is in the wrong format, fix it
 
136
        mac = params.get("mac")
 
137
        if mac is not None:
 
138
            params["mac"] = mac.replace(':', '-')
 
139
 
 
140
        return params
 
141
 
 
142
    def render_config(self, kernel_params, **extra):
 
143
        """Render a configuration file as a unicode string.
 
144
 
 
145
        :param kernel_params: An instance of `KernelParameters`.
 
146
        :param extra: Allow for other arguments. This is a safety valve;
 
147
            parameters generated in another component (for example, see
 
148
            `TFTPBackend.get_config_reader`) won't cause this to break.
 
149
        """
 
150
        template = self.get_template(
 
151
            kernel_params.purpose, kernel_params.arch,
 
152
            kernel_params.subarch)
 
153
        namespace = self.compose_template_namespace(kernel_params)
 
154
        return template.substitute(namespace)
 
155
 
 
156
    def install_bootloader(self, destination):
 
157
        """Installs the required files for UEFI booting into the
 
158
        tftproot.
 
159
        """
 
160
        with tempdir() as tmp:
 
161
            # Download the shim-signed package
 
162
            data, filename = utils.get_updates_package(
 
163
                'shim-signed', 'http://archive.ubuntu.com/ubuntu',
 
164
                'main', 'amd64')
 
165
            if data is None:
 
166
                raise BootMethodInstallError(
 
167
                    'Failed to download shim-signed package from '
 
168
                    'the archive.')
 
169
            shim_output = os.path.join(tmp, filename)
 
170
            with open(shim_output, 'wb') as stream:
 
171
                stream.write(data)
 
172
 
 
173
            # Extract the package with dpkg, and install the shim
 
174
            call_and_check(["dpkg", "-x", shim_output, tmp])
 
175
            install_bootloader(
 
176
                os.path.join(tmp, 'usr', 'lib', 'shim', 'shim.efi.signed'),
 
177
                os.path.join(destination, self.bootloader_path))
 
178
 
 
179
            # Download grubnetx64 from the archive and install
 
180
            grub_tmp = os.path.join(tmp, 'grubnetx64.efi.signed')
 
181
            if download_grubnet(grub_tmp) is False:
 
182
                raise BootMethodInstallError(
 
183
                    'Failed to download grubnetx64.efi.signed '
 
184
                    'from the archive.')
 
185
            grub_dst = os.path.join(destination, 'grubx64.efi')
 
186
            install_bootloader(grub_tmp, grub_dst)
 
187
 
 
188
        config_path = os.path.join(destination, 'grub')
 
189
        config_dst = os.path.join(config_path, 'grub.cfg')
 
190
        make_destination(config_path)
 
191
        with open(config_dst, 'wb') as stream:
 
192
            stream.write(CONFIG_FILE.encode("utf-8"))