aboutsummaryrefslogtreecommitdiffstats
path: root/tools/python/xen/xend/image.py
blob: 5abc121e86214e33091a0d0adf626de72d32a987 (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
import os, string

import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
from xen.xend import sxp
from xen.xend.XendError import VmError
from xen.xend.XendLogging import log
from xen.xend.xenstore import DBVar

from xen.xend.server import channel

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

    initDomain() is called to initialise the domain memory.
    
    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.
    
    """

    #======================================================================
    # Class vars and methods.

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

    def addImageHandlerClass(cls, h):
        """Add a handler class for an image type
        @param h:        handler: ImageHandler subclass
        """
        cls.imageHandlerClasses[h.ostype] = h

    addImageHandlerClass = classmethod(addImageHandlerClass)

    def findImageHandlerClass(cls, 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 = cls.imageHandlerClasses.get(ty)
        if imageClass is None:
            raise VmError('unknown image type: ' + ty)
        return imageClass

    findImageHandlerClass = classmethod(findImageHandlerClass)

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

        @param vm vm
        @param image image config
        @return ImageHandler instance
        """
        imageClass = cls.findImageHandlerClass(image)
        return imageClass(vm, image)

    create = classmethod(create)

    #======================================================================
    # Instance vars and methods.

    db = None
    ostype = None

    config = None
    kernel = None
    ramdisk = None
    cmdline = None
    flags = 0

    __exports__ = [
        DBVar('ostype',  ty='str'),
        DBVar('config',  ty='sxpr'),
        DBVar('kernel',  ty='str'),
        DBVar('ramdisk', ty='str'),
        DBVar('cmdline', ty='str'),
        DBVar('flags',   ty='int'),
        ]

    def __init__(self, vm, config):
        self.vm = vm
        self.db = vm.db.addChild('/image')
        self.config = config

    def exportToDB(self, save=False, sync=False):
        self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)

    def importFromDB(self):
        self.db.importFromDB(self, fields=self.__exports__)

    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 initDomain(self, dom, memory, ssidref, cpu, cpu_weight):
        """Initial domain create.

        @return domain id
        """

        mem_kb = self.getDomainMemory(memory)
        if not self.vm.restore:
            dom = xc.domain_create(dom = dom or 0, ssidref = ssidref)
            # if bootloader, unlink here. But should go after buildDomain() ?
            if self.vm.bootloader:
                self.unlink(self.kernel)
                self.unlink(self.ramdisk)
            if dom <= 0:
                raise VmError('Creating domain failed: name=%s' % self.vm.name)
        log.debug("initDomain: cpu=%d mem_kb=%d ssidref=%d dom=%d", cpu, mem_kb, ssidref, dom)
        # xc.domain_setuuid(dom, uuid)
        xc.domain_setcpuweight(dom, cpu_weight)
        xc.domain_setmaxmem(dom, mem_kb)
        xc.domain_memory_increase_reservation(dom, mem_kb)
        if cpu != -1:
            xc.domain_pincpu(dom, 0, 1<<int(cpu))
        return dom

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

    def configure(self):
        """Config actions common to all unix-like domains."""
        self.kernel = sxp.child_value(self.config, "kernel")
        self.cmdline = ""
        ip = sxp.child_value(self.config, "ip", None)
        if ip:
            self.cmdline += " ip=" + ip
        root = sxp.child_value(self.config, "root")
        if root:
            self.cmdline += " root=" + root
        args = sxp.child_value(self.config, "args")
        if args:
            self.cmdline += " " + args
        self.ramdisk = sxp.child_value(self.config, "ramdisk", '')
        
    def createDomain(self):
        """Build the domain boot image.
        """
        # Set params and call buildDomain().
        self.flags = 0
        if self.vm.netif_backend: self.flags |= SIF_NET_BE_DOMAIN
        if self.vm.blkif_backend: self.flags |= SIF_BLK_BE_DOMAIN

        if self.vm.recreate or self.vm.restore:
            return
        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) >= 256:
            log.warning('kernel cmdline too long, domain %d', self.vm.getDomain())
        
        log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype,
                 self.vm.getDomain(), self.vm.vcpus)
        err = self.buildDomain()
        if err != 0:
            raise VmError('Building domain failed: ostype=%s dom=%d err=%d'
                          % (self.ostype, self.vm.getDomain(), err))

    def getDomainMemory(self, mem_mb):
        """Memory (in KB) the domain will need for mem_mb (in MB)."""
        return mem_mb * 1024

    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

addImageHandlerClass = ImageHandler.addImageHandlerClass

class LinuxImageHandler(ImageHandler):

    ostype = "linux"

    def buildDomain(self):
        if self.vm.store_channel:
            store_evtchn = self.vm.store_channel.port2
        else:
            store_evtchn = 0
        ret = xc.linux_build(dom            = self.vm.getDomain(),
                             image          = self.kernel,
                             control_evtchn = self.vm.channel.getRemotePort(),
                             store_evtchn   = store_evtchn,
                             cmdline        = self.cmdline,
                             ramdisk        = self.ramdisk,
                             flags          = self.flags,
                             vcpus          = self.vm.vcpus)
        if isinstance(ret, dict):
            self.vm.store_mfn = ret.get('store_mfn')
            return 0
        return ret

class Plan9ImageHandler(ImageHandler):

    ostype = "plan9"

    def buildDomain(self):
        return xc.plan9_build(dom            = self.vm.getDomain(),
                              image          = self.kernel,
                              control_evtchn = self.vm.channel.getRemotePort(),
                              cmdline        = self.cmdline,
                              ramdisk        = self.ramdisk,
                              flags          = self.flags,
                              vcpus          = self.vm.vcpus)

class VmxImageHandler(ImageHandler):

    __exports__ = ImageHandler.__exports__ + [
        DBVar('memmap',        ty='str'),
        DBVar('memmap_value',  ty='sxpr'),
        # device channel?
        ]
    
    ostype = "vmx"
    memmap = None
    memmap_value = None
    device_channel = None

    def createImage(self):
        """Create a VM for the VMX environment.
        """
        self.configure()
        self.parseMemmap()
        self.createDomain()

    def buildDomain(self):
        return xc.vmx_build(dom            = self.vm.getDomain(),
                            image          = self.kernel,
                            control_evtchn = 0,
                            memsize        = self.vm.memory,
                            memmap         = self.memmap_value,
                            cmdline        = self.cmdline,
                            ramdisk        = self.ramdisk,
                            flags          = self.flags)

    def parseMemmap(self):
        self.memmap = sxp.child_value(self.vm.config, "memmap")
        if self.memmap is None:
            raise VmError("missing memmap")
        memmap = sxp.parse(open(self.memmap))[0]
        from xen.util.memmap import memmap_parse
        self.memmap_value = memmap_parse(memmap)
        
    def createDeviceModel_old(self):
        device_model = sxp.child_value(self.vm.config, 'device_model')
        if not device_model:
            raise VmError("vmx: missing device model")
        device_config = sxp.child_value(self.vm.config, 'device_config')
        if not device_config:
            raise VmError("vmx: missing device config")
        # Create an event channel.
        self.device_channel = channel.eventChannel(0, self.vm.getDomain())
        # Execute device model.
        #todo: Error handling
        os.system(device_model
                  + " -f %s" % device_config
                  + " -d %d" % self.vm.getDomain()
                  + " -p %d" % self.device_channel['port1']
                  + " -m %s" % self.vm.memory)

    def createDeviceModel(self):
        device_model = sxp.child_value(self.vm.config, 'device_model')
        if not device_model:
            raise VmError("vmx: missing device model")
        device_config = sxp.child_value(self.vm.config, 'device_config')
        if not device_config:
            raise VmError("vmx: missing device config")
        # Create an event channel
        self.device_channel = channel.eventChannel(0, self.vm.getDomain())
        # Execute device model.
        #todo: Error handling
        # XXX RN: note that the order of args matter!
        os.system(device_model
                  + " -f %s" % device_config
                  + self.vncParams()
                  + " -d %d" % self.vm.getDomain()
                  + " -p %d" % (int(self.device_channel.port1)-1)
                  + " -m %s" % self.vm.memory)

    def vncParams(self):
        # see if a vncviewer was specified
        # XXX RN: bit of a hack. should unify this, maybe stick in config space
        vncconnect=""
        image = self.config
        args = sxp.child_value(image, "args")
        if args:
            arg_list = string.split(args)
            for arg in arg_list:
                al = string.split(arg, '=')
                if al[0] == "VNC_VIEWER":
                    vncconnect=" -v %s" % al[1]
                    break
        return vncconnect

    def destroy(self):
        channel.eventChannelClose(self.device_channel)

    def getDomainMemory(self, mem_mb):
        return (mem_mb * 1024) + self.getPageTableSize(mem_mb)
            
    def getPageTableSize(self, mem_mb):
        """Return the size of memory needed for 1:1 page tables for physical
           mode.

        @param mem_mb: size in MB
        @return size in KB
        """
        # Logic x86-32 specific. 
        # 1 page for the PGD + 1 pte page for 4MB of memory (rounded)
        return (1 + ((mem_mb + 3) >> 2)) * 4