File: DevController.py

package info (click to toggle)
xen-3.0 3.0.3-0-2
  • links: PTS
  • area: main
  • in suites: etch-m68k
  • size: 31,772 kB
  • ctags: 70,362
  • sloc: ansic: 417,153; python: 28,855; asm: 23,892; sh: 5,157; makefile: 4,830; objc: 613; perl: 372; xml: 351
file content (481 lines) | stat: -rw-r--r-- 17,354 bytes parent folder | download | duplicates (2)
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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#============================================================================
# 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) 2004, 2005 Mike Wray <mike.wray@hp.com>
# Copyright (C) 2005 XenSource Ltd
#============================================================================

from threading import Event

from xen.xend import sxp
from xen.xend.XendError import VmError
from xen.xend.XendLogging import log

from xen.xend.xenstore.xstransact import xstransact, complete
from xen.xend.xenstore.xswatch import xswatch

DEVICE_CREATE_TIMEOUT = 100
HOTPLUG_STATUS_NODE = "hotplug-status"
HOTPLUG_ERROR_NODE  = "hotplug-error"
HOTPLUG_STATUS_ERROR = "error"
HOTPLUG_STATUS_BUSY  = "busy"

Connected = 1
Error     = 2
Missing   = 3
Timeout   = 4
Busy      = 5

xenbusState = {
    'Unknown'      : 0,
    'Initialising' : 1,
    'InitWait'     : 2,
    'Initialised'  : 3,
    'Connected'    : 4,
    'Closing'      : 5,
    'Closed'       : 6,
    }

xenbusState.update(dict(zip(xenbusState.values(), xenbusState.keys())))


class DevController:
    """Abstract base class for a device controller.  Device controllers create
    appropriate entries in the store to trigger the creation, reconfiguration,
    and destruction of devices in guest domains.  Each subclass of
    DevController is responsible for a particular device-class, and
    understands the details of configuration specific to that device-class.

    DevController itself provides the functionality common to all device
    creation tasks, as well as providing an interface to XendDomainInfo for
    triggering those events themselves.
    """

    # Set when registered.
    deviceClass = None


    ## public:

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


    def createDevice(self, config):
        """Trigger the creation of a device with the given configuration.

        @return The ID for the newly created device.
        """
        (devid, back, front) = self.getDeviceDetails(config)
        if devid is None:
            return 0

        (backpath, frontpath) = self.addStoreEntries(config, devid, back,
                                                     front)

        import xen.xend.XendDomain
        xd = xen.xend.XendDomain.instance()
        backdom_name = sxp.child_value(config, 'backend')
        if backdom_name is None:
            backdom = xen.xend.XendDomain.PRIV_DOMAIN
        else:
            bd = xd.domain_lookup_by_name_or_id_nr(backdom_name)
            backdom = bd.getDomid()
        count = 0
        while True:
            t = xstransact()
            try:
                if devid in self.deviceIDs(t):
                    if 'dev' in back:
                        dev_str = '%s (%d, %s)' % (back['dev'], devid,
                                                   self.deviceClass)
                    else:
                        dev_str = '%s (%s)' % (devid, self.deviceClass)
                    
                    raise VmError("Device %s is already connected." % dev_str)

                if count == 0:
                    log.debug('DevController: writing %s to %s.', str(front),
                              frontpath)
                    log.debug('DevController: writing %s to %s.', str(back),
                              backpath)
                elif count % 50 == 0:
                    log.debug(
                      'DevController: still waiting to write device entries.')

                t.remove(frontpath)
                t.remove(backpath)

                t.mkdir(backpath)
                t.set_permissions(backpath,
                                  {'dom': backdom },
                                  {'dom'  : self.vm.getDomid(),
                                   'read' : True })
                t.mkdir(frontpath)
                t.set_permissions(frontpath,
                                  {'dom': self.vm.getDomid()},
                                  {'dom': backdom, 'read': True})

                t.write2(frontpath, front)
                t.write2(backpath,  back)

                if t.commit():
                    return devid

                count += 1
            except:
                t.abort()
                raise


    def waitForDevices(self):
        log.debug("Waiting for devices %s.", self.deviceClass)
        
        return map(self.waitForDevice, self.deviceIDs())


    def waitForDevice(self, devid):
        log.debug("Waiting for %s.", devid)
        
        status = self.waitForBackend(devid)

        if status == Timeout:
            self.destroyDevice(devid)
            raise VmError("Device %s (%s) could not be connected. "
                          "Hotplug scripts not working." %
                          (devid, self.deviceClass))

        elif status == Error:
            self.destroyDevice(devid)
            raise VmError("Device %s (%s) could not be connected. "
                          "Backend device not found." %
                          (devid, self.deviceClass))

        elif status == Missing:
            # Don't try to destroy the device; it's already gone away.
            raise VmError("Device %s (%s) could not be connected. "
                          "Device not found." % (devid, self.deviceClass))

        elif status == Busy:
            err = None
            frontpath = self.frontendPath(devid)
            backpath = xstransact.Read(frontpath, "backend")
            if backpath:
                err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
            if not err:
                err = "Busy."
                
            self.destroyDevice(devid)
            raise VmError("Device %s (%s) could not be connected.\n%s" %
                          (devid, self.deviceClass, err))



    def reconfigureDevice(self, devid, config):
        """Reconfigure the specified device.

        The implementation here just raises VmError.  This may be overridden
        by those subclasses that can reconfigure their devices.
        """
        raise VmError('%s devices may not be reconfigured' % self.deviceClass)


    def destroyDevice(self, devid):
        """Destroy the specified device.

        @param devid The device ID, or something device-specific from which
        the device ID can be determined (such as a guest-side device name).

        The implementation here simply deletes the appropriate paths from the
        store.  This may be overridden by subclasses who need to perform other
        tasks on destruction.  Further, the implementation here can only
        accept integer device IDs, or values that can be converted to
        integers.  Subclasses may accept other values and convert them to
        integers before passing them here.
        """

        devid = int(devid)

        # Modify online status /before/ updating state (latter is watched by
        # drivers, so this ordering avoids a race).
        self.writeBackend(devid, 'online', "0")
        self.writeBackend(devid, 'state', str(xenbusState['Closing']))


    def configurations(self):
        return map(self.configuration, self.deviceIDs())


    def configuration(self, devid):
        """@return an s-expression giving the current configuration of the
        specified device.  This would be suitable for giving to {@link
        #createDevice} in order to recreate that device."""

        backdomid = xstransact.Read(self.frontendPath(devid), "backend-id")
        if backdomid is None:
            raise VmError("Device %s not connected" % devid)
        
        return [self.deviceClass, ['backend', int(backdomid)]]


    def sxprs(self):
        """@return an s-expression describing all the devices of this
        controller's device-class.
        """
        return xstransact.ListRecursive(self.frontendRoot())


    def sxpr(self, devid):
        """@return an s-expression describing the specified device.
        """
        return [self.deviceClass, ['dom', self.vm.getDomid(),
                                   'id', devid]]


    ## protected:

    def getDeviceDetails(self, config):
        """Compute the details for creation of a device corresponding to the
        given configuration.  These details consist of a tuple of (devID,
        backDetails, frontDetails), where devID is the ID for the new device,
        and backDetails and frontDetails are the device configuration
        specifics for the backend and frontend respectively.

        backDetails and frontDetails should be dictionaries, the keys and
        values of which will be used as paths in the store.  There is no need
        for these dictionaries to include the references from frontend to
        backend, nor vice versa, as these will be handled by DevController.

        Abstract; must be implemented by every subclass.

        @return (devID, backDetails, frontDetails), as specified above.
        """

        raise NotImplementedError()

    def migrate(self, deviceConfig, network, dst, step, domName):
        """ Migration of a device. The 'network' parameter indicates
            whether the device is network-migrated (True). 'dst' then gives
            the hostname of the machine to migrate to.
        This function is called for 4 steps:
        If step == 0: Check whether the device is ready to be migrated
                      or can at all be migrated; return a '-1' if
                      the device is NOT ready, a '0' otherwise. If it is
                      not ready ( = not possible to migrate this device),
                      migration will not take place.
           step == 1: Called immediately after step 0; migration
                      of the kernel has started;
           step == 2: Called after the suspend has been issued
                      to the domain and the domain is not scheduled anymore.
                      Synchronize with what was started in step 1, if necessary.
                      Now the device should initiate its transfer to the
                      given target. Since there might be more than just
                      one device initiating a migration, this step should
                      put the process performing the transfer into the
                      background and return immediately to achieve as much
                      concurrency as possible.
           step == 3: Synchronize with the migration of the device that
                      was initiated in step 2.
                      Make sure that the migration has finished and only
                      then return from the call.
        """
        return 0


    def recover_migrate(self, deviceConfig, network, dst, step, domName):
        """ Recover from device migration. The given step was the
            last one that was successfully executed.
        """
        return 0


    def getDomid(self):
        """Stub to {@link XendDomainInfo.getDomid}, for use by our
        subclasses.
        """
        return self.vm.getDomid()


    def allocateDeviceID(self):
        """Allocate a device ID, allocating them consecutively on a
        per-domain, per-device-class basis, and using the store to record the
        next available ID.

        This method is available to our subclasses, though it is not
        compulsory to use it; subclasses may prefer to allocate IDs based upon
        the device configuration instead.
        """
        path = self.frontendMiscPath()
        return complete(path, self._allocateDeviceID)


    def _allocateDeviceID(self, t):
        result = t.read("nextDeviceID")
        if result:
            result = int(result)
        else:
            result = 0
        t.write("nextDeviceID", str(result + 1))
        return result


    def readBackend(self, devid, *args):
        frontpath = self.frontendPath(devid)
        backpath = xstransact.Read(frontpath, "backend")
        if backpath:
            return xstransact.Read(backpath, *args)
        else:
            raise VmError("Device %s not connected" % devid)

    def readFrontend(self, devid, *args):
        return xstransact.Read(self.frontendPath(devid), *args)

    def deviceIDs(self, transaction = None):
        """@return The IDs of each of the devices currently configured for
        this instance's deviceClass.
        """
        fe = self.backendRoot()
        if transaction:
            return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
        else:
            return map(int, xstransact.List(fe))


    def writeBackend(self, devid, *args):
        frontpath = self.frontendPath(devid)
        backpath = xstransact.Read(frontpath, "backend")

        if backpath:
            xstransact.Write(backpath, *args)
        else:
            raise VmError("Device %s not connected" % devid)


## private:

    def addStoreEntries(self, config, devid, backDetails, frontDetails):
        """Add to backDetails and frontDetails the entries to be written in
        the store to trigger creation of a device.  The backend domain ID is
        taken from the given config, paths for frontend and backend are
        computed, and these are added to the backDetails and frontDetails
        dictionaries for writing to the store, including references from
        frontend to backend and vice versa.

        @return A pair of (backpath, frontpath).  backDetails and frontDetails
        will have been updated appropriately, also.

        @param config The configuration of the device, as given to
        {@link #createDevice}.
        @param devid        As returned by {@link #getDeviceDetails}.
        @param backDetails  As returned by {@link #getDeviceDetails}.
        @param frontDetails As returned by {@link #getDeviceDetails}.
        """

        import xen.xend.XendDomain
        xd = xen.xend.XendDomain.instance()

        backdom_name = sxp.child_value(config, 'backend')
        if backdom_name:
            backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
        else:
            backdom = xd.privilegedDomain()

        if not backdom:
            raise VmError("Cannot configure device for unknown backend %s" %
                          backdom_name)

        frontpath = self.frontendPath(devid)
        backpath  = self.backendPath(backdom, devid)
        
        frontDetails.update({
            'backend' : backpath,
            'backend-id' : "%i" % backdom.getDomid(),
            'state' : str(xenbusState['Initialising'])
            })


        backDetails.update({
            'domain' : self.vm.getName(),
            'frontend' : frontpath,
            'frontend-id' : "%i" % self.vm.getDomid(),
            'state' : str(xenbusState['Initialising']),
            'online' : "1"
            })

        return (backpath, frontpath)


    def waitForBackend(self, devid):

        frontpath = self.frontendPath(devid)
        backpath = xstransact.Read(frontpath, "backend")

        if backpath:
            statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
            ev = Event()
            result = { 'status': Timeout }
            
            xswatch(statusPath, hotplugStatusCallback, ev, result)

            ev.wait(DEVICE_CREATE_TIMEOUT)
            return result['status']
        else:
            return Missing


    def backendPath(self, backdom, devid):
        """@param backdom [XendDomainInfo] The backend domain info."""

        return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
                                        self.deviceClass,
                                        self.vm.getDomid(), devid)


    def frontendPath(self, devid):
        return "%s/%d" % (self.frontendRoot(), devid)


    def frontendRoot(self):
        return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)

    def backendRoot(self):
        import xen.xend.XendDomain
	from xen.xend.xenstore.xsutil import GetDomainPath
        backdom = xen.xend.XendDomain.PRIV_DOMAIN
        return "%s/backend/%s/%s" % (GetDomainPath(backdom), self.deviceClass, self.vm.getDomid())

    def frontendMiscPath(self):
        return "%s/device-misc/%s" % (self.vm.getDomainPath(),
                                      self.deviceClass)


def hotplugStatusCallback(statusPath, ev, result):
    log.debug("hotplugStatusCallback %s.", statusPath)

    status = xstransact.Read(statusPath)

    if status is not None:
        if status == HOTPLUG_STATUS_ERROR:
            result['status'] = Error
        elif status == HOTPLUG_STATUS_BUSY:
            result['status'] = Busy
        else:
            result['status'] = Connected
    else:
        return 1

    log.debug("hotplugStatusCallback %d.", result['status'])

    ev.set()
    return 0