summaryrefslogtreecommitdiff
path: root/virtinst/install/installertreemedia.py
blob: 0151f14971c25a7eeeca055a73d7db011edf54f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#
# Copyright 2006-2009, 2013, 2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

import os

from . import urldetect
from . import urlfetcher
from .installerinject import perform_initrd_injections
from .. import progress
from ..devices import DeviceDisk
from ..logger import log
from ..osdict import OSDB


# Enum of the various install media types we can have
(MEDIA_DIR,
 MEDIA_ISO,
 MEDIA_URL,
 MEDIA_KERNEL) = range(1, 5)


def _is_url(url):
    return (url.startswith("http://") or
            url.startswith("https://") or
            url.startswith("ftp://"))


class _LocationData(object):
    def __init__(self, os_variant, kernel_pairs, os_media, os_tree):
        self.os_variant = os_variant
        self.kernel_pairs = kernel_pairs
        self.os_media = os_media
        self.os_tree = os_tree

        self.kernel_url_arg = None
        if self.os_variant:
            osobj = OSDB.lookup_os(self.os_variant)
            self.kernel_url_arg = osobj.get_kernel_url_arg()


class InstallerTreeMedia(object):
    """
    Class representing --location Tree media. Can be one of

      - A network URL: http://dl.fedoraproject.org/...
      - A local directory
      - A local .iso file, which will be accessed with isoinfo
    """

    @staticmethod
    def validate_path(conn, path):
        try:
            dev = DeviceDisk(conn)
            dev.device = dev.DEVICE_CDROM
            dev.set_source_path(path)
            dev.validate()
            return dev.get_source_path()
        except Exception as e:
            log.debug("Error validating install location", exc_info=True)
            if path.startswith("nfs:"):
                log.warning("NFS URL installs are no longer supported. "
                    "Access your install media over an alternate transport "
                    "like HTTP, or manually mount the NFS share and install "
                    "from the local directory mount point.")

            msg = (_("Validating install media '%(media)s' failed: %(error)s") %
                    {"media": str(path), "error": str(e)})
            raise ValueError(msg) from None

    @staticmethod
    def get_system_scratchdir(guest):
        """
        Return the tmpdir that's accessible by VMs on system libvirt URIs
        """
        if guest.conn.is_xen():
            return "/var/lib/xen"
        return "/var/lib/libvirt/boot"

    @staticmethod
    def make_scratchdir(guest):
        """
        Determine the scratchdir for this URI, create it if necessary.
        scratchdir is the directory that's accessible by VMs
        """
        user_scratchdir = os.path.join(
                guest.conn.get_app_cache_dir(), "boot")
        system_scratchdir = InstallerTreeMedia.get_system_scratchdir(guest)

        # If we are a session URI, or we don't have access to the system
        # scratchdir, make sure the session scratchdir exists and use that.
        if (guest.conn.is_unprivileged() or
            not os.path.exists(system_scratchdir) or
            not os.access(system_scratchdir, os.W_OK)):
            os.makedirs(user_scratchdir, 0o751, exist_ok=True)
            return user_scratchdir

        return system_scratchdir  # pragma: no cover

    def __init__(self, conn, location, location_kernel, location_initrd,
                install_kernel, install_initrd, install_kernel_args):
        self.conn = conn
        self.location = location
        self._location_kernel = location_kernel
        self._location_initrd = location_initrd
        self._install_kernel = install_kernel
        self._install_initrd = install_initrd
        self._install_kernel_args = install_kernel_args
        self._initrd_injections = []
        self._extra_args = []

        if location_kernel or location_initrd:
            if not location:
                raise ValueError(_("location kernel/initrd may only "
                    "be specified with a location URL/path"))
            if not (location_kernel and location_initrd):
                raise ValueError(_("location kernel/initrd must be "
                    "be specified as a pair"))

        self._cached_fetcher = None
        self._cached_data = None

        self._tmpfiles = []

        if self._install_kernel or self._install_initrd:
            self._media_type = MEDIA_KERNEL
        elif (not self.conn.is_remote() and
              os.path.exists(self.location) and
              os.path.isdir(self.location)):
            self.location = os.path.abspath(self.location)
            self._media_type = MEDIA_DIR
        elif _is_url(self.location):
            self._media_type = MEDIA_URL
        else:
            self._media_type = MEDIA_ISO

        if (self.conn.is_remote() and
                not self._media_type == MEDIA_URL and
            not self._media_type == MEDIA_KERNEL):
            raise ValueError(_("Cannot access install tree on remote "
                "connection: %s") % self.location)

        if self._media_type == MEDIA_ISO:
            InstallerTreeMedia.validate_path(self.conn, self.location)


    ########################
    # Install preparations #
    ########################

    def _get_fetcher(self, guest, meter):
        meter = progress.ensure_meter(meter)

        if not self._cached_fetcher:
            scratchdir = InstallerTreeMedia.make_scratchdir(guest)

            if self._media_type == MEDIA_KERNEL:
                self._cached_fetcher = urlfetcher.DirectFetcher(
                    None, scratchdir, meter)
            else:
                self._cached_fetcher = urlfetcher.fetcherForURI(
                    self.location, scratchdir, meter)

        self._cached_fetcher.meter = meter
        return self._cached_fetcher

    def _get_cached_data(self, guest, fetcher):
        if self._cached_data:
            return self._cached_data

        store = None
        os_variant = None
        os_media = None
        os_tree = None
        kernel_paths = []
        has_location_kernel = bool(
                self._location_kernel and self._location_initrd)

        if self._media_type == MEDIA_KERNEL:
            kernel_paths = [
                    (self._install_kernel, self._install_initrd)]
        else:
            store = urldetect.getDistroStore(guest, fetcher,
                    skip_error=has_location_kernel)

        if store:
            kernel_paths = store.get_kernel_paths()
            os_variant = store.get_osdict_info()
            os_media = store.get_os_media()
            os_tree = store.get_os_tree()
        if has_location_kernel:
            kernel_paths = [
                    (self._location_kernel, self._location_initrd)]

        self._cached_data = _LocationData(os_variant, kernel_paths,
                os_media, os_tree)
        return self._cached_data

    def _prepare_kernel_url(self, guest, cache, fetcher):
        ignore = guest

        def _check_kernel_pairs():
            for kpath, ipath in cache.kernel_pairs:
                if fetcher.hasFile(kpath) and fetcher.hasFile(ipath):
                    return kpath, ipath
            raise RuntimeError(  # pragma: no cover
                    _("Couldn't find kernel for install tree."))

        kernelpath, initrdpath = _check_kernel_pairs()
        kernel = fetcher.acquireFile(kernelpath)
        self._tmpfiles.append(kernel)
        initrd = fetcher.acquireFile(initrdpath)
        self._tmpfiles.append(initrd)

        perform_initrd_injections(initrd,
                                  self._initrd_injections,
                                  fetcher.scratchdir)

        return kernel, initrd


    ##############
    # Public API #
    ##############

    def _prepare_unattended_data(self, scripts):
        if not scripts:
            return

        for script in scripts:
            expected_filename = script.get_expected_filename()
            scriptpath = script.write()
            self._tmpfiles.append(scriptpath)
            self._initrd_injections.append((scriptpath, expected_filename))

    def _prepare_kernel_url_arg(self, guest, cache):
        os_variant = cache.os_variant or guest.osinfo.name
        osobj = OSDB.lookup_os(os_variant)
        return osobj.get_kernel_url_arg()

    def _prepare_kernel_args(self, guest, cache, unattended_scripts):
        install_args = None
        if unattended_scripts:
            args = []
            for unattended_script in unattended_scripts:
                cmdline = unattended_script.generate_cmdline()
                if cmdline:
                    args.append(cmdline)
            install_args = (" ").join(args)
            log.debug("Generated unattended cmdline: %s", install_args)
        elif self.is_network_url():
            kernel_url_arg = self._prepare_kernel_url_arg(guest, cache)
            if kernel_url_arg:
                install_args = "%s=%s" % (kernel_url_arg, self.location)

        if install_args:
            self._extra_args.append(install_args)

        if self._install_kernel_args:
            ret = self._install_kernel_args
        else:
            ret = " ".join(self._extra_args)

        if self._media_type == MEDIA_DIR and not ret:
            log.warning(_("Directory tree installs typically do not work "
                "unless extra kernel args are passed to point the "
                "installer at a network accessible install tree."))
        return ret

    def prepare(self, guest, meter, unattended_scripts):
        fetcher = self._get_fetcher(guest, meter)
        cache = self._get_cached_data(guest, fetcher)

        self._prepare_unattended_data(unattended_scripts)
        kernel_args = self._prepare_kernel_args(guest, cache, unattended_scripts)

        kernel, initrd = self._prepare_kernel_url(guest, cache, fetcher)
        return kernel, initrd, kernel_args

    def cleanup(self, guest):
        ignore = guest
        for f in self._tmpfiles:
            log.debug("Removing %s", str(f))
            os.unlink(f)

        self._tmpfiles = []

    def set_initrd_injections(self, initrd_injections):
        self._initrd_injections = initrd_injections

    def set_extra_args(self, extra_args):
        self._extra_args = extra_args

    def cdrom_path(self):
        if self._media_type in [MEDIA_ISO]:
            return self.location

    def is_network_url(self):
        if self._media_type in [MEDIA_URL]:
            return self.location

    def detect_distro(self, guest):
        fetcher = self._get_fetcher(guest, None)
        cache = self._get_cached_data(guest, fetcher)
        return cache.os_variant

    def get_os_media(self, guest, meter):
        fetcher = self._get_fetcher(guest, meter)
        cache = self._get_cached_data(guest, fetcher)
        return cache.os_media

    def get_os_tree(self, guest, meter):
        fetcher = self._get_fetcher(guest, meter)
        cache = self._get_cached_data(guest, fetcher)
        return cache.os_tree

    def requires_internet(self, guest, meter):
        if self._media_type in [MEDIA_URL, MEDIA_DIR]:
            return True

        os_media = self.get_os_media(guest, meter)
        if os_media:
            return os_media.is_netinst()
        return False