aboutsummaryrefslogtreecommitdiffstats
path: root/tools/python/xen/xend/image.py
blob: 64fb8109442b0f00b86577859320c9d73c21987d (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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#============================================================================
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#============================================================================
# Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
# Copyright (C) 2005 XenSource Ltd
#============================================================================


import os, string
import re
import math

import xen.lowlevel.xc
from xen.xend import sxp
from xen.xend.XendError import VmError
from xen.xend.XendLogging import log
from xen.xend.server.netif import randomMAC
from xen.xend.xenstore.xswatch import xswatch


xc = xen.lowlevel.xc.xc()


MAX_GUEST_CMDLINE = 1024


def create(vm, imageConfig, deviceConfig):
    """Create an image handler for a vm.

    @return ImageHandler instance
    """
    return findImageHandlerClass(imageConfig)(vm, imageConfig, deviceConfig)


class ImageHandler:
    """Abstract base class for image handlers.

    createImage() is called to configure and build the domain from its
    kernel image and ramdisk etc.

    The method buildDomain() is used to build the domain, and must be
    defined in a subclass.  Usually this is the only method that needs
    defining in a subclass.

    The method createDeviceModel() is called to create the domain device
    model if it needs one.  The default is to do nothing.

    The method destroy() is called when the domain is destroyed.
    The default is to do nothing.
    """

    ostype = None


    def __init__(self, vm, imageConfig, deviceConfig):
        self.vm = vm

        self.kernel = None
        self.ramdisk = None
        self.cmdline = None

        self.configure(imageConfig, deviceConfig)

    def configure(self, imageConfig, _):
        """Config actions common to all unix-like domains."""

        def get_cfg(name, default = None):
            return sxp.child_value(imageConfig, name, default)

        self.kernel = get_cfg("kernel")
        self.cmdline = ""
        ip = get_cfg("ip")
        if ip:
            self.cmdline += " ip=" + ip
        root = get_cfg("root")
        if root:
            self.cmdline += " root=" + root
        args = get_cfg("args")
        if args:
            self.cmdline += " " + args
        self.ramdisk = get_cfg("ramdisk", '')
        
        self.vm.storeVm(("image/ostype", self.ostype),
                        ("image/kernel", self.kernel),
                        ("image/cmdline", self.cmdline),
                        ("image/ramdisk", self.ramdisk))


    def cleanupBootloading(self):
        self.unlink(self.kernel)
        self.unlink(self.ramdisk)


    def unlink(self, f):
        if not f: return
        try:
            os.unlink(f)
        except OSError, ex:
            log.warning("error removing bootloader file '%s': %s", f, ex)


    def createImage(self):
        """Entry point to create domain memory image.
        Override in subclass  if needed.
        """
        return self.createDomain()


    def createDomain(self):
        """Build the domain boot image.
        """
        # Set params and call buildDomain().

        if not os.path.isfile(self.kernel):
            raise VmError('Kernel image does not exist: %s' % self.kernel)
        if self.ramdisk and not os.path.isfile(self.ramdisk):
            raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
        if len(self.cmdline) >= MAX_GUEST_CMDLINE:
            log.warning('kernel cmdline too long, domain %d',
                        self.vm.getDomid())
        
        log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
                 self.vm.getDomid(), self.vm.getVCpuCount())

        result = self.buildDomain()

        if isinstance(result, dict):
            return result
        else:
            raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
                          % (self.ostype, self.vm.getDomid(), str(result)))


    def getDomainMemory(self, mem_kb):
        """@return The memory required, in KiB, by the domain to store the
        given amount, also in KiB."""
        if os.uname()[4] != 'ia64':
            # A little extra because auto-ballooning is broken w.r.t. HVM
            # guests. Also, slack is necessary for live migration since that
            # uses shadow page tables.
            if 'hvm' in xc.xeninfo()['xen_caps']:
                mem_kb += 4*1024;
        return mem_kb

    def buildDomain(self):
        """Build the domain. Define in subclass."""
        raise NotImplementedError()

    def createDeviceModel(self):
        """Create device model for the domain (define in subclass if needed)."""
        pass
    
    def destroy(self):
        """Extra cleanup on domain destroy (define in subclass if needed)."""
        pass


class LinuxImageHandler(ImageHandler):

    ostype = "linux"

    def buildDomain(self):
        store_evtchn = self.vm.getStorePort()
        console_evtchn = self.vm.getConsolePort()

        log.debug("dom            = %d", self.vm.getDomid())
        log.debug("image          = %s", self.kernel)
        log.debug("store_evtchn   = %d", store_evtchn)
        log.debug("console_evtchn = %d", console_evtchn)
        log.debug("cmdline        = %s", self.cmdline)
        log.debug("ramdisk        = %s", self.ramdisk)
        log.debug("vcpus          = %d", self.vm.getVCpuCount())
        log.debug("features       = %s", self.vm.getFeatures())

        return xc.linux_build(dom            = self.vm.getDomid(),
                              image          = self.kernel,
                              store_evtchn   = store_evtchn,
                              console_evtchn = console_evtchn,
                              cmdline        = self.cmdline,
                              ramdisk        = self.ramdisk,
                              features       = self.vm.getFeatures())

class HVMImageHandler(ImageHandler):

    ostype = "hvm"

    def configure(self, imageConfig, deviceConfig):
        ImageHandler.configure(self, imageConfig, deviceConfig)

        info = xc.xeninfo()
        if not 'hvm' in info['xen_caps']:
            raise VmError("Not an HVM capable platform, we stop creating!")

        self.dmargs = self.parseDeviceModelArgs(imageConfig, deviceConfig)
        self.device_model = sxp.child_value(imageConfig, 'device_model')
        if not self.device_model:
            raise VmError("hvm: missing device model")
        self.display = sxp.child_value(imageConfig, 'display')
        self.xauthority = sxp.child_value(imageConfig, 'xauthority')
        self.vncconsole = sxp.child_value(imageConfig, 'vncconsole')

        self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
                        ("image/device-model", self.device_model),
                        ("image/display", self.display))

        self.pid = 0

        self.dmargs += self.configVNC(imageConfig)

        self.pae  = int(sxp.child_value(imageConfig, 'pae', 0))

        self.acpi = int(sxp.child_value(imageConfig, 'acpi', 0))
        self.apic = int(sxp.child_value(imageConfig, 'apic', 0))

    def buildDomain(self):
        store_evtchn = self.vm.getStorePort()

        log.debug("dom            = %d", self.vm.getDomid())
        log.debug("image          = %s", self.kernel)
        log.debug("store_evtchn   = %d", store_evtchn)
        log.debug("memsize        = %d", self.vm.getMemoryTarget() / 1024)
        log.debug("vcpus          = %d", self.vm.getVCpuCount())
        log.debug("pae            = %d", self.pae)
        log.debug("acpi           = %d", self.acpi)
        log.debug("apic           = %d", self.apic)

        self.register_shutdown_watch()

        return xc.hvm_build(dom            = self.vm.getDomid(),
                            image          = self.kernel,
                            store_evtchn   = store_evtchn,
                            memsize        = self.vm.getMemoryTarget() / 1024,
                            vcpus          = self.vm.getVCpuCount(),
                            pae            = self.pae,
                            acpi           = self.acpi,
                            apic           = self.apic)

    # Return a list of cmd line args to the device models based on the
    # xm config file
    def parseDeviceModelArgs(self, imageConfig, deviceConfig):
        dmargs = [ 'boot', 'fda', 'fdb', 'soundhw',
                   'localtime', 'serial', 'stdvga', 'isa', 'vcpus',
                   'acpi', 'usb', 'usbdevice']
        ret = []
        for a in dmargs:
            v = sxp.child_value(imageConfig, a)

            # python doesn't allow '-' in variable names
            if a == 'stdvga': a = 'std-vga'

            # Handle booleans gracefully
            if a in ['localtime', 'std-vga', 'isa', 'usb', 'acpi']:
                if v != None: v = int(v)
                if v: ret.append("-%s" % a)
            else:
                if v:
                    ret.append("-%s" % a)
                    ret.append("%s" % v)
            log.debug("args: %s, val: %s" % (a,v))

        # Handle disk/network related options
        mac = None
        ret = ret + ["-domain-name", "%s" % self.vm.info['name']]
        nics = 0
        for (name, info) in deviceConfig:
            if name == 'vbd':
                uname = sxp.child_value(info, 'uname')
                if 'file:' in uname:
                    (_, vbdparam) = string.split(uname, ':', 1)
                    if not os.path.isfile(vbdparam):
                        raise VmError('Disk image does not exist: %s' %
                                      vbdparam)
            if name == 'vif':
                type = sxp.child_value(info, 'type')
                if type != 'ioemu':
                    continue
                nics += 1
                mac = sxp.child_value(info, 'mac')
                if mac == None:
                    mac = randomMAC()
                bridge = sxp.child_value(info, 'bridge', 'xenbr0')
                model = sxp.child_value(info, 'model', 'rtl8139')
                ret.append("-net")
                ret.append("nic,vlan=%d,macaddr=%s,model=%s" %
                           (nics, mac, model))
                ret.append("-net")
                ret.append("tap,vlan=%d,bridge=%s" % (nics, bridge))
            if name == 'vtpm':
                instance = sxp.child_value(info, 'pref_instance')
                ret.append("-instance")
                ret.append("%s" % instance)
        return ret

    def configVNC(self, config):
        # Handle graphics library related options
        vnc = sxp.child_value(config, 'vnc')
        sdl = sxp.child_value(config, 'sdl')
        ret = []
        nographic = sxp.child_value(config, 'nographic')
        if nographic:
            ret.append('-nographic')
            return ret
        if vnc:
            vncdisplay = sxp.child_value(config, 'vncdisplay',
                                         int(self.vm.getDomid()))
            ret = ret + ['-vnc', '%d' % vncdisplay, '-k', 'en-us']
            vncunused = sxp.child_value(config, 'vncunused')
            if vncunused:
                ret += ['-vncunused']
        return ret

    def createDeviceModel(self):
        if self.pid:
            return
        # Execute device model.
        #todo: Error handling
        args = [self.device_model]
        args = args + ([ "-d",  "%d" % self.vm.getDomid(),
                  "-m", "%s" % (self.vm.getMemoryTarget() / 1024)])
        args = args + self.dmargs
        env = dict(os.environ)
        if self.display:
            env['DISPLAY'] = self.display
        if self.xauthority:
            env['XAUTHORITY'] = self.xauthority
        if self.vncconsole:
            args = args + ([ "-vncviewer" ])
        log.info("spawning device models: %s %s", self.device_model, args)
        self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
        log.info("device model pid: %d", self.pid)

    def destroy(self):
        self.unregister_shutdown_watch();
        import signal
        if not self.pid:
            return
        os.kill(self.pid, signal.SIGKILL)
        os.waitpid(self.pid, 0)
        self.pid = 0

    def getDomainMemory(self, mem_kb):
        """@see ImageHandler.getDomainMemory"""
        if os.uname()[4] == 'ia64':
            page_kb = 16
            # ROM size for guest firmware, ioreq page and xenstore page
            extra_pages = 1024 + 2
        else:
            page_kb = 4
            # This was derived emperically:
            #   2.4 MB overhead per 1024 MB RAM + 8 MB constant
            #   + 4 to avoid low-memory condition
            extra_mb = (2.4/1024) * (mem_kb/1024.0) + 12;
            extra_pages = int( math.ceil( extra_mb*1024 / page_kb ))
        return mem_kb + extra_pages * page_kb

    def register_shutdown_watch(self):
        """ add xen store watch on control/shutdown """
        self.shutdownWatch = xswatch(self.vm.dompath + "/control/shutdown", \
                                    self.hvm_shutdown)
        log.debug("hvm shutdown watch registered")

    def unregister_shutdown_watch(self):
        """Remove the watch on the control/shutdown, if any. Nothrow
        guarantee."""

        try:
            if self.shutdownWatch:
                self.shutdownWatch.unwatch()
        except:
            log.exception("Unwatching hvm shutdown watch failed.")
        self.shutdownWatch = None
        log.debug("hvm shutdown watch unregistered")

    def hvm_shutdown(self, _):
        """ watch call back on node control/shutdown,
            if node changed, this function will be called
        """
        from xen.xend.XendDomainInfo import shutdown_reasons
        xd = xen.xend.XendDomain.instance()
        vm = xd.domain_lookup( self.vm.getDomid() )

        reason = vm.readDom('control/shutdown')
        log.debug("hvm_shutdown fired, shutdown reason=%s", reason)
        for x in shutdown_reasons.keys():
            if shutdown_reasons[x] == reason:
                vm.info['shutdown'] = 1
                vm.info['shutdown_reason'] = x
                vm.refreshShutdown(vm.info)

        return 1 # Keep watching

"""Table of image handler classes for virtual machine images.  Indexed by
image type.
"""
imageHandlerClasses = {}


for h in LinuxImageHandler, HVMImageHandler:
    imageHandlerClasses[h.ostype] = h


def findImageHandlerClass(image):
    """Find the image handler class for an image config.

    @param image config
    @return ImageHandler subclass or None
    """
    ty = sxp.name(image)
    if ty is None:
        raise VmError('missing image type')
    imageClass = imageHandlerClasses.get(ty)
    if imageClass is None:
        raise VmError('unknown image type: ' + ty)
    return imageClass