source: trunk/packages/xen-common/xen-common/tools/python/xen/xend/image.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

File size: 19.4 KB
Line 
1#============================================================================
2# This library is free software; you can redistribute it and/or
3# modify it under the terms of version 2.1 of the GNU Lesser General Public
4# License as published by the Free Software Foundation.
5#
6# This library is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9# Lesser General Public License for more details.
10#
11# You should have received a copy of the GNU Lesser General Public
12# License along with this library; if not, write to the Free Software
13# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14#============================================================================
15# Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
16# Copyright (C) 2005-2007 XenSource Ltd
17#============================================================================
18
19
20import os, string
21import re
22import math
23import signal
24
25import xen.lowlevel.xc
26from xen.xend.XendConstants import REVERSE_DOMAIN_SHUTDOWN_REASONS
27from xen.xend.XendError import VmError, XendError, HVMRequired
28from xen.xend.XendLogging import log
29from xen.xend.XendOptions import instance as xenopts
30from xen.xend.server.netif import randomMAC
31from xen.xend.xenstore.xswatch import xswatch
32from xen.xend import arch
33
34xc = xen.lowlevel.xc.xc()
35
36MAX_GUEST_CMDLINE = 1024
37
38
39def create(vm, vmConfig):
40    """Create an image handler for a vm.
41
42    @return ImageHandler instance
43    """
44    return findImageHandlerClass(vmConfig)(vm, vmConfig)
45
46
47class ImageHandler:
48    """Abstract base class for image handlers.
49
50    createImage() is called to configure and build the domain from its
51    kernel image and ramdisk etc.
52
53    The method buildDomain() is used to build the domain, and must be
54    defined in a subclass.  Usually this is the only method that needs
55    defining in a subclass.
56
57    The method createDeviceModel() is called to create the domain device
58    model if it needs one.  The default is to do nothing.
59
60    The method destroy() is called when the domain is destroyed.
61    The default is to do nothing.
62    """
63
64    ostype = None
65
66
67    def __init__(self, vm, vmConfig):
68        self.vm = vm
69
70        self.bootloader = False
71        self.kernel = None
72        self.ramdisk = None
73        self.cmdline = None
74
75        self.configure(vmConfig)
76
77    def configure(self, vmConfig):
78        """Config actions common to all unix-like domains."""
79        if '_temp_using_bootloader' in vmConfig:
80            self.bootloader = True
81            self.kernel = vmConfig['_temp_kernel']
82            self.cmdline = vmConfig['_temp_args']
83            self.ramdisk = vmConfig['_temp_ramdisk']
84        else:
85            self.kernel = vmConfig['PV_kernel']
86            self.cmdline = vmConfig['PV_args']
87            self.ramdisk = vmConfig['PV_ramdisk']
88        self.vm.storeVm(("image/ostype", self.ostype),
89                        ("image/kernel", self.kernel),
90                        ("image/cmdline", self.cmdline),
91                        ("image/ramdisk", self.ramdisk))
92
93
94    def cleanupBootloading(self):
95        if self.bootloader:
96            self.unlink(self.kernel)
97            self.unlink(self.ramdisk)
98
99
100    def unlink(self, f):
101        if not f: return
102        try:
103            os.unlink(f)
104        except OSError, ex:
105            log.warning("error removing bootloader file '%s': %s", f, ex)
106
107
108    def createImage(self):
109        """Entry point to create domain memory image.
110        Override in subclass  if needed.
111        """
112        return self.createDomain()
113
114
115    def createDomain(self):
116        """Build the domain boot image.
117        """
118        # Set params and call buildDomain().
119
120        if not os.path.isfile(self.kernel):
121            raise VmError('Kernel image does not exist: %s' % self.kernel)
122        if self.ramdisk and not os.path.isfile(self.ramdisk):
123            raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk)
124        if len(self.cmdline) >= MAX_GUEST_CMDLINE:
125            log.warning('kernel cmdline too long, domain %d',
126                        self.vm.getDomid())
127       
128        log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
129                 self.vm.getDomid(), self.vm.getVCpuCount())
130
131        result = self.buildDomain()
132
133        if isinstance(result, dict):
134            return result
135        else:
136            raise VmError('Building domain failed: ostype=%s dom=%d err=%s'
137                          % (self.ostype, self.vm.getDomid(), str(result)))
138
139    def getRequiredAvailableMemory(self, mem_kb):
140        """@param mem_kb The configured maxmem or memory, in KiB.
141        @return The corresponding required amount of memory for the domain,
142        also in KiB.  This is normally the given mem_kb, but architecture- or
143        image-specific code may override this to add headroom where
144        necessary."""
145        return mem_kb
146
147    def getRequiredInitialReservation(self):
148        """@param mem_kb The configured memory, in KiB.
149        @return The corresponding required amount of memory to be free, also
150        in KiB. This is normally the same as getRequiredAvailableMemory, but
151        architecture- or image-specific code may override this to
152        add headroom where necessary."""
153        return self.getRequiredAvailableMemory(self.vm.getMemoryTarget())
154
155    def getRequiredMaximumReservation(self):
156        """@param mem_kb The maximum possible memory, in KiB.
157        @return The corresponding required amount of memory to be free, also
158        in KiB. This is normally the same as getRequiredAvailableMemory, but
159        architecture- or image-specific code may override this to
160        add headroom where necessary."""
161        return self.getRequiredAvailableMemory(self.vm.getMemoryMaximum())
162
163    def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
164        """@param shadow_mem_kb The configured shadow memory, in KiB.
165        @param maxmem_kb The configured maxmem, in KiB.
166        @return The corresponding required amount of shadow memory, also in
167        KiB."""
168        # PV domains don't need any shadow memory
169        return 0
170
171    def buildDomain(self):
172        """Build the domain. Define in subclass."""
173        raise NotImplementedError()
174
175    def createDeviceModel(self, restore = False):
176        """Create device model for the domain (define in subclass if needed)."""
177        pass
178   
179    def destroy(self):
180        """Extra cleanup on domain destroy (define in subclass if needed)."""
181        pass
182
183
184    def recreate(self):
185        pass
186
187
188class LinuxImageHandler(ImageHandler):
189
190    ostype = "linux"
191
192    def buildDomain(self):
193        store_evtchn = self.vm.getStorePort()
194        console_evtchn = self.vm.getConsolePort()
195
196        mem_mb = self.getRequiredInitialReservation() / 1024
197
198        log.debug("domid          = %d", self.vm.getDomid())
199        log.debug("memsize        = %d", mem_mb)
200        log.debug("image          = %s", self.kernel)
201        log.debug("store_evtchn   = %d", store_evtchn)
202        log.debug("console_evtchn = %d", console_evtchn)
203        log.debug("cmdline        = %s", self.cmdline)
204        log.debug("ramdisk        = %s", self.ramdisk)
205        log.debug("vcpus          = %d", self.vm.getVCpuCount())
206        log.debug("features       = %s", self.vm.getFeatures())
207
208        return xc.linux_build(domid          = self.vm.getDomid(),
209                              memsize        = mem_mb,
210                              image          = self.kernel,
211                              store_evtchn   = store_evtchn,
212                              console_evtchn = console_evtchn,
213                              cmdline        = self.cmdline,
214                              ramdisk        = self.ramdisk,
215                              features       = self.vm.getFeatures())
216
217class PPC_LinuxImageHandler(LinuxImageHandler):
218
219    ostype = "linux"
220   
221    def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
222        """@param shadow_mem_kb The configured shadow memory, in KiB.
223        @param maxmem_kb The configured maxmem, in KiB.
224        @return The corresponding required amount of shadow memory, also in
225        KiB.
226        PowerPC currently uses "shadow memory" to refer to the hash table."""
227        return max(maxmem_kb / 64, shadow_mem_kb)
228
229
230
231class HVMImageHandler(ImageHandler):
232
233    ostype = "hvm"
234
235    def __init__(self, vm, vmConfig):
236        ImageHandler.__init__(self, vm, vmConfig)
237        self.shutdownWatch = None
238        self.rebootFeatureWatch = None
239
240    def configure(self, vmConfig):
241        ImageHandler.configure(self, vmConfig)
242
243        if not self.kernel:
244            self.kernel = '/usr/lib/xen/boot/hvmloader'
245
246        info = xc.xeninfo()
247        if 'hvm' not in info['xen_caps']:
248            raise HVMRequired()
249
250        self.dmargs = self.parseDeviceModelArgs(vmConfig)
251        self.device_model = vmConfig['platform'].get('device_model')
252        if not self.device_model:
253            raise VmError("hvm: missing device model")
254       
255        self.display = vmConfig['platform'].get('display')
256        self.xauthority = vmConfig['platform'].get('xauthority')
257        self.vncconsole = vmConfig['platform'].get('vncconsole')
258
259        rtc_timeoffset = vmConfig['platform'].get('rtc_timeoffset')
260
261        self.vm.storeVm(("image/dmargs", " ".join(self.dmargs)),
262                        ("image/device-model", self.device_model),
263                        ("image/display", self.display))
264        self.vm.storeVm(("rtc/timeoffset", rtc_timeoffset))
265
266        self.pid = None
267
268        self.pae  = int(vmConfig['platform'].get('pae',  0))
269        self.apic = int(vmConfig['platform'].get('apic', 0))
270        self.acpi = int(vmConfig['platform'].get('acpi', 0))
271       
272
273    def buildDomain(self):
274        store_evtchn = self.vm.getStorePort()
275
276        mem_mb = self.getRequiredInitialReservation() / 1024
277
278        log.debug("domid          = %d", self.vm.getDomid())
279        log.debug("image          = %s", self.kernel)
280        log.debug("store_evtchn   = %d", store_evtchn)
281        log.debug("memsize        = %d", mem_mb)
282        log.debug("vcpus          = %d", self.vm.getVCpuCount())
283        log.debug("pae            = %d", self.pae)
284        log.debug("acpi           = %d", self.acpi)
285        log.debug("apic           = %d", self.apic)
286
287        rc = xc.hvm_build(domid          = self.vm.getDomid(),
288                          image          = self.kernel,
289                          store_evtchn   = store_evtchn,
290                          memsize        = mem_mb,
291                          vcpus          = self.vm.getVCpuCount(),
292                          pae            = self.pae,
293                          acpi           = self.acpi,
294                          apic           = self.apic)
295        rc['notes'] = { 'SUSPEND_CANCEL': 1 }
296        return rc
297
298    # Return a list of cmd line args to the device models based on the
299    # xm config file
300    def parseDeviceModelArgs(self, vmConfig):
301        dmargs = [ 'boot', 'fda', 'fdb', 'soundhw',
302                   'localtime', 'serial', 'stdvga', 'isa',
303                   'acpi', 'usb', 'usbdevice', 'keymap' ]
304       
305        ret = ['-vcpus', str(self.vm.getVCpuCount())]
306
307        for a in dmargs:
308            v = vmConfig['platform'].get(a)
309
310            # python doesn't allow '-' in variable names
311            if a == 'stdvga': a = 'std-vga'
312            if a == 'keymap': a = 'k'
313
314            # Handle booleans gracefully
315            if a in ['localtime', 'std-vga', 'isa', 'usb', 'acpi']:
316                try:
317                    if v != None: v = int(v)
318                    if v: ret.append("-%s" % a)
319                except (ValueError, TypeError):
320                    pass # if we can't convert it to a sane type, ignore it
321            else:
322                if v:
323                    ret.append("-%s" % a)
324                    ret.append("%s" % v)
325
326            if a in ['fda', 'fdb']:
327                if v:
328                    if not os.path.isabs(v):
329                        raise VmError("Floppy file %s does not exist." % v)
330            log.debug("args: %s, val: %s" % (a,v))
331
332        # Handle disk/network related options
333        mac = None
334        ret = ret + ["-domain-name", str(self.vm.info['name_label'])]
335        nics = 0
336       
337        for devuuid in vmConfig['vbd_refs']:
338            devinfo = vmConfig['devices'][devuuid][1]
339            uname = devinfo.get('uname')
340            if uname is not None and 'file:' in uname:
341                (_, vbdparam) = string.split(uname, ':', 1)
342                if not os.path.isfile(vbdparam):
343                    raise VmError('Disk image does not exist: %s' %
344                                  vbdparam)
345
346        for devuuid in vmConfig['vif_refs']:
347            devinfo = vmConfig['devices'][devuuid][1]
348            dtype = devinfo.get('type', 'ioemu')
349            if dtype != 'ioemu':
350                continue
351            nics += 1
352            mac = devinfo.get('mac')
353            if mac is None:
354                mac = randomMAC()
355            bridge = devinfo.get('bridge', 'xenbr0')
356            model = devinfo.get('model', 'rtl8139')
357            ret.append("-net")
358            ret.append("nic,vlan=%d,macaddr=%s,model=%s" %
359                       (nics, mac, model))
360            ret.append("-net")
361            ret.append("tap,vlan=%d,bridge=%s" % (nics, bridge))
362
363
364        #
365        # Find RFB console device, and if it exists, make QEMU enable
366        # the VNC console.
367        #
368        if int(vmConfig['platform'].get('nographic', 0)) != 0:
369            # skip vnc init if nographic is set
370            ret.append('-nographic')
371            return ret
372
373        vnc_config = {}
374        has_vnc = int(vmConfig['platform'].get('vnc', 0)) != 0
375        has_sdl = int(vmConfig['platform'].get('sdl', 0)) != 0
376        for dev_uuid in vmConfig['console_refs']:
377            dev_type, dev_info = vmConfig['devices'][dev_uuid]
378            if dev_type == 'vfb':
379                vnc_config = dev_info.get('other_config', {})
380                has_vnc = True
381                break
382
383        if has_vnc:
384            if not vnc_config:
385                for key in ('vncunused', 'vnclisten', 'vncdisplay',
386                            'vncpasswd'):
387                    if key in vmConfig['platform']:
388                        vnc_config[key] = vmConfig['platform'][key]
389
390            if not vnc_config.get('vncunused', 0) and \
391                   vnc_config.get('vncdisplay', 0):
392                vncdisplay = vnc_config.get('vncdisplay')
393                ret.append('-vnc')
394                ret.append(str(vncdisplay))
395            else:
396                ret.append('-vncunused')
397
398            vnclisten = vnc_config.get('vnclisten',
399                                       xenopts().get_vnclisten_address())
400            ret.append('-vnclisten')
401            ret.append(str(vnclisten))
402
403            # Store vncpassword in xenstore
404            vncpasswd = vnc_config.get('vncpasswd')
405            if not vncpasswd:
406                vncpasswd = xenopts().get_vncpasswd_default()
407
408            if vncpasswd is None:
409                raise VmError('vncpasswd is not setup in vmconfig or '
410                              'xend-config.sxp')
411
412            if vncpasswd != '':
413                self.vm.storeVm('vncpasswd', vncpasswd)
414        elif has_sdl:
415            # SDL is default in QEMU.
416            pass
417        else:
418            ret.append('-nographic')
419
420        if int(vmConfig['platform'].get('monitor', 0)) != 0:
421            ret = ret + ['-monitor', 'vc']
422        return ret
423
424    def createDeviceModel(self, restore = False):
425        if self.pid:
426            return
427        # Execute device model.
428        #todo: Error handling
429        args = [self.device_model]
430        args = args + ([ "-d",  "%d" % self.vm.getDomid() ])
431        if arch.type == "ia64":
432            args = args + ([ "-m", "%s" %
433                             (self.getRequiredInitialReservation() / 1024) ])
434        args = args + self.dmargs
435        if restore:
436            args = args + ([ "-loadvm", "/tmp/xen.qemu-dm.%d" %
437                             self.vm.getDomid() ])
438        env = dict(os.environ)
439        if self.display:
440            env['DISPLAY'] = self.display
441        if self.xauthority:
442            env['XAUTHORITY'] = self.xauthority
443        if self.vncconsole:
444            args = args + ([ "-vncviewer" ])
445        log.info("spawning device models: %s %s", self.device_model, args)
446        # keep track of pid and spawned options to kill it later
447        self.pid = os.spawnve(os.P_NOWAIT, self.device_model, args, env)
448        self.vm.storeDom("image/device-model-pid", self.pid)
449        log.info("device model pid: %d", self.pid)
450
451    def recreate(self):
452        self.pid = self.vm.gatherDom(('image/device-model-pid', int))
453
454    def destroy(self, suspend = False):
455        if self.pid:
456            try:
457                sig = signal.SIGKILL
458                if suspend:
459                    log.info("use sigusr1 to signal qemu %d", self.pid)
460                    sig = signal.SIGUSR1
461                os.kill(self.pid, sig)
462            except OSError, exn:
463                log.exception(exn)
464            try:
465                os.waitpid(self.pid, 0)
466            except OSError, exn:
467                # This is expected if Xend has been restarted within the
468                # life of this domain.  In this case, we can kill the process,
469                # but we can't wait for it because it's not our child.
470                pass
471            self.pid = None
472
473
474class IA64_HVM_ImageHandler(HVMImageHandler):
475
476    def getRequiredAvailableMemory(self, mem_kb):
477        page_kb = 16
478        # ROM size for guest firmware, ioreq page, pio page and xenstore page
479        extra_pages = 1024 + 4
480        return mem_kb + extra_pages * page_kb
481
482    def getRequiredInitialReservation(self):
483        return self.vm.getMemoryTarget()
484
485    def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
486        # Explicit shadow memory is not a concept
487        return 0
488
489class X86_HVM_ImageHandler(HVMImageHandler):
490
491    def getRequiredAvailableMemory(self, mem_kb):
492        # Add 8 MiB overhead for QEMU's video RAM.
493        return mem_kb + 8192
494
495    def getRequiredInitialReservation(self):
496        return self.vm.getMemoryTarget()
497
498    def getRequiredMaximumReservation(self):
499        return self.vm.getMemoryMaximum()
500
501    def getRequiredShadowMemory(self, shadow_mem_kb, maxmem_kb):
502        # 256 pages (1MB) per vcpu,
503        # plus 1 page per MiB of RAM for the P2M map,
504        # plus 1 page per MiB of RAM to shadow the resident processes. 
505        # This is higher than the minimum that Xen would allocate if no value
506        # were given (but the Xen minimum is for safety, not performance).
507        return max(4 * (256 * self.vm.getVCpuCount() + 2 * (maxmem_kb / 1024)),
508                   shadow_mem_kb)
509
510class X86_Linux_ImageHandler(LinuxImageHandler):
511
512    def buildDomain(self):
513        # set physical mapping limit
514        # add an 8MB slack to balance backend allocations.
515        mem_kb = self.getRequiredMaximumReservation() + (8 * 1024)
516        xc.domain_set_memmap_limit(self.vm.getDomid(), mem_kb)
517        return LinuxImageHandler.buildDomain(self)
518
519_handlers = {
520    "powerpc": {
521        "linux": PPC_LinuxImageHandler,
522    },
523    "ia64": {
524        "linux": LinuxImageHandler,
525        "hvm": IA64_HVM_ImageHandler,
526    },
527    "x86": {
528        "linux": X86_Linux_ImageHandler,
529        "hvm": X86_HVM_ImageHandler,
530    },
531}
532
533def findImageHandlerClass(image):
534    """Find the image handler class for an image config.
535
536    @param image config
537    @return ImageHandler subclass or None
538    """
539    image_type = image.image_type()
540    try:
541        return _handlers[arch.type][image_type]
542    except KeyError:
543        raise VmError('unknown image type: ' + image_type)
Note: See TracBrowser for help on using the repository browser.