ia64/xen-unstable

view tools/python/xen/xend/server/DevController.py @ 18615:0c64c5fe261b

xend: Fix dev backend path construction.

Signed-off-by: Jim Fehlig <jfehlig@novell.com>
author Keir Fraser <keir.fraser@citrix.com>
date Mon Oct 13 10:03:36 2008 +0100 (2008-10-13)
parents 616eea24aefa
children 5c48ab6b1977
line source
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) 2004, 2005 Mike Wray <mike.wray@hp.com>
16 # Copyright (C) 2005 XenSource Ltd
17 #============================================================================
19 from threading import Event
20 import types
22 from xen.xend import sxp, XendOptions
23 from xen.xend.XendError import VmError
24 from xen.xend.XendLogging import log
25 import xen.xend.XendConfig
27 from xen.xend.xenstore.xstransact import xstransact, complete
28 from xen.xend.xenstore.xswatch import xswatch
30 import os
32 DEVICE_CREATE_TIMEOUT = 100
33 DEVICE_DESTROY_TIMEOUT = 100
34 HOTPLUG_STATUS_NODE = "hotplug-status"
35 HOTPLUG_ERROR_NODE = "hotplug-error"
36 HOTPLUG_STATUS_ERROR = "error"
37 HOTPLUG_STATUS_BUSY = "busy"
39 Connected = 1
40 Error = 2
41 Missing = 3
42 Timeout = 4
43 Busy = 5
44 Disconnected = 6
46 xenbusState = {
47 'Unknown' : 0,
48 'Initialising' : 1,
49 'InitWait' : 2,
50 'Initialised' : 3,
51 'Connected' : 4,
52 'Closing' : 5,
53 'Closed' : 6,
54 'Reconfiguring': 7,
55 'Reconfigured' : 8,
56 }
58 xoptions = XendOptions.instance()
60 xenbusState.update(dict(zip(xenbusState.values(), xenbusState.keys())))
63 class DevController:
64 """Abstract base class for a device controller. Device controllers create
65 appropriate entries in the store to trigger the creation, reconfiguration,
66 and destruction of devices in guest domains. Each subclass of
67 DevController is responsible for a particular device-class, and
68 understands the details of configuration specific to that device-class.
70 DevController itself provides the functionality common to all device
71 creation tasks, as well as providing an interface to XendDomainInfo for
72 triggering those events themselves.
73 """
75 # Set when registered.
76 deviceClass = None
79 ## public:
81 def __init__(self, vm):
82 self.vm = vm
83 self.hotplug = True
85 def createDevice(self, config):
86 """Trigger the creation of a device with the given configuration.
88 @return The ID for the newly created device.
89 """
90 (devid, back, front) = self.getDeviceDetails(config)
91 if devid is None:
92 return 0
94 self.setupDevice(config)
96 (backpath, frontpath) = self.addStoreEntries(config, devid, back,
97 front)
99 import xen.xend.XendDomain
100 xd = xen.xend.XendDomain.instance()
101 backdom_name = config.get('backend')
102 if backdom_name is None:
103 backdom = xen.xend.XendDomain.DOM0_ID
104 else:
105 bd = xd.domain_lookup_nr(backdom_name)
106 backdom = bd.getDomid()
107 count = 0
108 while True:
109 t = xstransact()
110 try:
111 if devid in self.deviceIDs(t):
112 if 'dev' in back:
113 dev_str = '%s (%d, %s)' % (back['dev'], devid,
114 self.deviceClass)
115 else:
116 dev_str = '%s (%s)' % (devid, self.deviceClass)
118 raise VmError("Device %s is already connected." % dev_str)
120 if count == 0:
121 log.debug('DevController: writing %s to %s.',
122 str(front), frontpath)
123 log.debug('DevController: writing %s to %s.',
124 str(xen.xend.XendConfig.scrub_password(back)), backpath)
125 elif count % 50 == 0:
126 log.debug(
127 'DevController: still waiting to write device entries.')
129 devpath = self.devicePath(devid)
131 t.remove(frontpath)
132 t.remove(backpath)
133 t.remove(devpath)
135 t.mkdir(backpath)
136 t.set_permissions(backpath,
137 {'dom': backdom },
138 {'dom' : self.vm.getDomid(),
139 'read' : True })
140 t.mkdir(frontpath)
141 t.set_permissions(frontpath,
142 {'dom': self.vm.getDomid()},
143 {'dom': backdom, 'read': True})
145 t.write2(frontpath, front)
146 t.write2(backpath, back)
148 t.mkdir(devpath)
149 t.write2(devpath, {
150 'backend' : backpath,
151 'backend-id' : "%i" % backdom,
152 'frontend' : frontpath,
153 'frontend-id' : "%i" % self.vm.getDomid()
154 })
156 if t.commit():
157 return devid
159 count += 1
160 except:
161 t.abort()
162 raise
165 def waitForDevices(self):
166 log.debug("Waiting for devices %s.", self.deviceClass)
167 return map(self.waitForDevice, self.deviceIDs())
170 def waitForDevice(self, devid):
171 log.debug("Waiting for %s.", devid)
173 if not self.hotplug:
174 return
176 (status, err) = self.waitForBackend(devid)
178 if status == Timeout:
179 self.destroyDevice(devid, False)
180 raise VmError("Device %s (%s) could not be connected. "
181 "Hotplug scripts not working." %
182 (devid, self.deviceClass))
184 elif status == Error:
185 self.destroyDevice(devid, False)
186 if err is None:
187 raise VmError("Device %s (%s) could not be connected. "
188 "Backend device not found." %
189 (devid, self.deviceClass))
190 else:
191 raise VmError("Device %s (%s) could not be connected. "
192 "%s" % (devid, self.deviceClass, err))
193 elif status == Missing:
194 # Don't try to destroy the device; it's already gone away.
195 raise VmError("Device %s (%s) could not be connected. "
196 "Device not found." % (devid, self.deviceClass))
198 elif status == Busy:
199 self.destroyDevice(devid, False)
200 if err is None:
201 err = "Busy."
202 raise VmError("Device %s (%s) could not be connected.\n%s" %
203 (devid, self.deviceClass, err))
206 def waitForDevice_destroy(self, devid, backpath):
207 log.debug("Waiting for %s - destroyDevice.", devid)
209 if not self.hotplug:
210 return
212 status = self.waitForBackend_destroy(backpath)
214 if status == Timeout:
215 raise VmError("Device %s (%s) could not be disconnected. " %
216 (devid, self.deviceClass))
218 def waitForDevice_reconfigure(self, devid):
219 log.debug("Waiting for %s - reconfigureDevice.", devid)
221 (status, err) = self.waitForBackend_reconfigure(devid)
223 if status == Timeout:
224 raise VmError("Device %s (%s) could not be reconfigured. " %
225 (devid, self.deviceClass))
228 def reconfigureDevice(self, devid, config):
229 """Reconfigure the specified device.
231 The implementation here just raises VmError. This may be overridden
232 by those subclasses that can reconfigure their devices.
233 """
234 raise VmError('%s devices may not be reconfigured' % self.deviceClass)
237 def destroyDevice(self, devid, force):
238 """Destroy the specified device.
240 @param devid The device ID, or something device-specific from which
241 the device ID can be determined (such as a guest-side device name).
243 The implementation here simply deletes the appropriate paths from the
244 store. This may be overridden by subclasses who need to perform other
245 tasks on destruction. The implementation here accepts integer device
246 IDs or paths containg integer deviceIDs, e.g. vfb/0. Subclasses may
247 accept other values and convert them to integers before passing them
248 here.
249 """
251 dev = self.convertToDeviceNumber(devid)
253 # Modify online status /before/ updating state (latter is watched by
254 # drivers, so this ordering avoids a race).
255 self.writeBackend(dev, 'online', "0")
256 self.writeBackend(dev, 'state', str(xenbusState['Closing']))
258 if force:
259 frontpath = self.frontendPath(dev)
260 backpath = self.readVm(dev, "backend")
261 if backpath:
262 xstransact.Remove(backpath)
263 xstransact.Remove(frontpath)
265 # xstransact.Remove(self.devicePath()) ?? Below is the same ?
266 self.vm._removeVm("device/%s/%d" % (self.deviceClass, dev))
268 def configurations(self, transaction = None):
269 return map(lambda x: self.configuration(x, transaction), self.deviceIDs(transaction))
272 def configuration(self, devid, transaction = None):
273 """@return an s-expression giving the current configuration of the
274 specified device. This would be suitable for giving to {@link
275 #createDevice} in order to recreate that device."""
276 configDict = self.getDeviceConfiguration(devid, transaction)
277 sxpr = [self.deviceClass]
278 for key, val in configDict.items():
279 if isinstance(val, (types.ListType, types.TupleType)):
280 for v in val:
281 if v != None:
282 sxpr.append([key, v])
283 else:
284 if val != None:
285 sxpr.append([key, val])
286 return sxpr
288 def sxprs(self):
289 """@return an s-expression describing all the devices of this
290 controller's device-class.
291 """
292 return xstransact.ListRecursive(self.frontendRoot())
295 def sxpr(self, devid):
296 """@return an s-expression describing the specified device.
297 """
298 return [self.deviceClass, ['dom', self.vm.getDomid(),
299 'id', devid]]
302 def getDeviceConfiguration(self, devid, transaction = None):
303 """Returns the configuration of a device.
305 @note: Similar to L{configuration} except it returns a dict.
306 @return: dict
307 """
308 if transaction is None:
309 backdomid = xstransact.Read(self.devicePath(devid), "backend-id")
310 else:
311 backdomid = transaction.read(self.devicePath(devid) + "/backend-id")
313 if backdomid is None:
314 raise VmError("Device %s not connected" % devid)
316 return {'backend': int(backdomid)}
318 def getAllDeviceConfigurations(self):
319 all_configs = {}
320 for devid in self.deviceIDs():
321 config_dict = self.getDeviceConfiguration(devid)
322 all_configs[devid] = config_dict
323 return all_configs
326 def convertToDeviceNumber(self, devid):
327 try:
328 return int(devid)
329 except ValueError:
330 # Does devid contain devicetype/deviceid?
331 # Propogate exception if unable to find an integer devid
332 return int(type(devid) is str and devid.split('/')[-1] or None)
334 ## protected:
336 def getDeviceDetails(self, config):
337 """Compute the details for creation of a device corresponding to the
338 given configuration. These details consist of a tuple of (devID,
339 backDetails, frontDetails), where devID is the ID for the new device,
340 and backDetails and frontDetails are the device configuration
341 specifics for the backend and frontend respectively.
343 backDetails and frontDetails should be dictionaries, the keys and
344 values of which will be used as paths in the store. There is no need
345 for these dictionaries to include the references from frontend to
346 backend, nor vice versa, as these will be handled by DevController.
348 Abstract; must be implemented by every subclass.
350 @return (devID, backDetails, frontDetails), as specified above.
351 """
353 raise NotImplementedError()
355 def setupDevice(self, config):
356 """ Setup device from config.
357 """
358 return
360 def migrate(self, deviceConfig, network, dst, step, domName):
361 """ Migration of a device. The 'network' parameter indicates
362 whether the device is network-migrated (True). 'dst' then gives
363 the hostname of the machine to migrate to.
364 This function is called for 4 steps:
365 If step == 0: Check whether the device is ready to be migrated
366 or can at all be migrated; return a '-1' if
367 the device is NOT ready, a '0' otherwise. If it is
368 not ready ( = not possible to migrate this device),
369 migration will not take place.
370 step == 1: Called immediately after step 0; migration
371 of the kernel has started;
372 step == 2: Called after the suspend has been issued
373 to the domain and the domain is not scheduled anymore.
374 Synchronize with what was started in step 1, if necessary.
375 Now the device should initiate its transfer to the
376 given target. Since there might be more than just
377 one device initiating a migration, this step should
378 put the process performing the transfer into the
379 background and return immediately to achieve as much
380 concurrency as possible.
381 step == 3: Synchronize with the migration of the device that
382 was initiated in step 2.
383 Make sure that the migration has finished and only
384 then return from the call.
385 """
386 tool = xoptions.get_external_migration_tool()
387 if tool:
388 log.info("Calling external migration tool for step %d" % step)
389 fd = os.popen("%s -type %s -step %d -host %s -domname %s" %
390 (tool, self.deviceClass, step, dst, domName))
391 for line in fd:
392 log.info(line.rstrip())
393 rc = fd.close()
394 if rc:
395 raise VmError('Migration tool returned %d' % (rc >> 8))
396 return 0
399 def recover_migrate(self, deviceConfig, network, dst, step, domName):
400 """ Recover from device migration. The given step was the
401 last one that was successfully executed.
402 """
403 tool = xoptions.get_external_migration_tool()
404 if tool:
405 log.info("Calling external migration tool")
406 fd = os.popen("%s -type %s -step %d -host %s -domname %s -recover" %
407 (tool, self.deviceClass, step, dst, domName))
408 for line in fd:
409 log.info(line.rstrip())
410 rc = fd.close()
411 if rc:
412 raise VmError('Migration tool returned %d' % (rc >> 8))
413 return 0
416 def getDomid(self):
417 """Stub to {@link XendDomainInfo.getDomid}, for use by our
418 subclasses.
419 """
420 return self.vm.getDomid()
423 def allocateDeviceID(self):
424 """Allocate a device ID, allocating them consecutively on a
425 per-domain, per-device-class basis, and using the store to record the
426 next available ID.
428 This method is available to our subclasses, though it is not
429 compulsory to use it; subclasses may prefer to allocate IDs based upon
430 the device configuration instead.
431 """
432 path = self.frontendMiscPath()
433 return complete(path, self._allocateDeviceID)
436 def _allocateDeviceID(self, t):
437 result = t.read("nextDeviceID")
438 if result:
439 result = int(result)
440 else:
441 result = 0
442 t.write("nextDeviceID", str(result + 1))
443 return result
446 def removeBackend(self, devid, *args):
447 frontpath = self.frontendPath(devid)
448 backpath = xstransact.Read(frontpath, "backend")
449 if backpath:
450 return xstransact.Remove(backpath, *args)
451 else:
452 raise VmError("Device %s not connected" % devid)
454 def readVm(self, devid, *args):
455 devpath = self.devicePath(devid)
456 if devpath:
457 return xstransact.Read(devpath, *args)
458 else:
459 raise VmError("Device config %s not found" % devid)
461 def readBackend(self, devid, *args):
462 backpath = self.readVm(devid, "backend")
463 if backpath:
464 return xstransact.Read(backpath, *args)
465 else:
466 raise VmError("Device %s not connected" % devid)
468 def readBackendTxn(self, transaction, devid, *args):
469 backpath = self.readVm(devid, "backend")
470 if backpath:
471 paths = map(lambda x: backpath + "/" + x, args)
472 return transaction.read(*paths)
473 else:
474 raise VmError("Device %s not connected" % devid)
476 def readFrontend(self, devid, *args):
477 return xstransact.Read(self.frontendPath(devid), *args)
479 def readFrontendTxn(self, transaction, devid, *args):
480 paths = map(lambda x: self.frontendPath(devid) + "/" + x, args)
481 return transaction.read(*paths)
483 def deviceIDs(self, transaction = None):
484 """@return The IDs of each of the devices currently configured for
485 this instance's deviceClass.
486 """
487 fe = self.deviceRoot()
489 if transaction:
490 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
491 else:
492 return map(int, xstransact.List(fe))
495 def writeBackend(self, devid, *args):
496 backpath = self.readVm(devid, "backend")
498 if backpath:
499 xstransact.Write(backpath, *args)
500 else:
501 raise VmError("Device %s not connected" % devid)
504 ## private:
506 def addStoreEntries(self, config, devid, backDetails, frontDetails):
507 """Add to backDetails and frontDetails the entries to be written in
508 the store to trigger creation of a device. The backend domain ID is
509 taken from the given config, paths for frontend and backend are
510 computed, and these are added to the backDetails and frontDetails
511 dictionaries for writing to the store, including references from
512 frontend to backend and vice versa.
514 @return A pair of (backpath, frontpath). backDetails and frontDetails
515 will have been updated appropriately, also.
517 @param config The configuration of the device, as given to
518 {@link #createDevice}.
519 @param devid As returned by {@link #getDeviceDetails}.
520 @param backDetails As returned by {@link #getDeviceDetails}.
521 @param frontDetails As returned by {@link #getDeviceDetails}.
522 """
524 import xen.xend.XendDomain
525 xd = xen.xend.XendDomain.instance()
527 backdom_name = config.get('backend')
528 if backdom_name:
529 backdom = xd.domain_lookup_nr(backdom_name)
530 else:
531 backdom = xd.privilegedDomain()
533 if not backdom:
534 raise VmError("Cannot configure device for unknown backend %s" %
535 backdom_name)
537 frontpath = self.frontendPath(devid)
538 backpath = self.backendPath(backdom, devid)
540 frontDetails.update({
541 'backend' : backpath,
542 'backend-id' : "%i" % backdom.getDomid(),
543 'state' : str(xenbusState['Initialising'])
544 })
546 if self.vm.native_protocol:
547 frontDetails.update({'protocol' : self.vm.native_protocol})
549 backDetails.update({
550 'domain' : self.vm.getName(),
551 'frontend' : frontpath,
552 'frontend-id' : "%i" % self.vm.getDomid(),
553 'state' : str(xenbusState['Initialising']),
554 'online' : "1"
555 })
557 return (backpath, frontpath)
560 def waitForBackend(self, devid):
561 frontpath = self.frontendPath(devid)
562 # lookup a phantom
563 phantomPath = xstransact.Read(frontpath, 'phantom_vbd')
564 if phantomPath is not None:
565 log.debug("Waiting for %s's phantom %s.", devid, phantomPath)
566 statusPath = phantomPath + '/' + HOTPLUG_STATUS_NODE
567 ev = Event()
568 result = { 'status': Timeout }
569 xswatch(statusPath, hotplugStatusCallback, ev, result)
570 ev.wait(DEVICE_CREATE_TIMEOUT)
571 err = xstransact.Read(statusPath, HOTPLUG_ERROR_NODE)
572 if result['status'] != 'Connected':
573 return (result['status'], err)
575 backpath = self.readVm(devid, "backend")
578 if backpath:
579 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
580 ev = Event()
581 result = { 'status': Timeout }
583 xswatch(statusPath, hotplugStatusCallback, ev, result)
585 ev.wait(DEVICE_CREATE_TIMEOUT)
587 err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
589 return (result['status'], err)
590 else:
591 return (Missing, None)
594 def waitForBackend_destroy(self, backpath):
596 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
597 ev = Event()
598 result = { 'status': Timeout }
600 xswatch(statusPath, deviceDestroyCallback, ev, result)
602 ev.wait(DEVICE_DESTROY_TIMEOUT)
604 return result['status']
606 def waitForBackend_reconfigure(self, devid):
607 frontpath = self.frontendPath(devid)
608 backpath = xstransact.Read(frontpath, "backend")
609 if backpath:
610 statusPath = backpath + '/' + "state"
611 ev = Event()
612 result = { 'status': Timeout }
614 xswatch(statusPath, xenbusStatusCallback, ev, result)
616 ev.wait(DEVICE_CREATE_TIMEOUT)
618 return (result['status'], None)
619 else:
620 return (Missing, None)
623 def backendPath(self, backdom, devid):
624 """Construct backend path given the backend domain and device id.
626 @param backdom [XendDomainInfo] The backend domain info."""
628 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
629 self.deviceClass,
630 self.vm.getDomid(), devid)
633 def frontendPath(self, devid):
634 return "%s/%d" % (self.frontendRoot(), devid)
637 def frontendRoot(self):
638 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
640 def frontendMiscPath(self):
641 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
642 self.deviceClass)
644 def deviceRoot(self):
645 """Return the /vm/device. Because backendRoot assumes the
646 backend domain is 0"""
647 return "%s/device/%s" % (self.vm.vmpath, self.deviceClass)
649 def devicePath(self, devid):
650 """Return the /device entry of the given VM. We use it to store
651 backend/frontend locations"""
652 return "%s/device/%s/%s" % (self.vm.vmpath,
653 self.deviceClass, devid)
655 def hotplugStatusCallback(statusPath, ev, result):
656 log.debug("hotplugStatusCallback %s.", statusPath)
658 status = xstransact.Read(statusPath)
660 if status is not None:
661 if status == HOTPLUG_STATUS_ERROR:
662 result['status'] = Error
663 elif status == HOTPLUG_STATUS_BUSY:
664 result['status'] = Busy
665 else:
666 result['status'] = Connected
667 else:
668 return 1
670 log.debug("hotplugStatusCallback %d.", result['status'])
672 ev.set()
673 return 0
676 def deviceDestroyCallback(statusPath, ev, result):
677 log.debug("deviceDestroyCallback %s.", statusPath)
679 status = xstransact.Read(statusPath)
681 if status is None:
682 result['status'] = Disconnected
683 else:
684 return 1
686 log.debug("deviceDestroyCallback %d.", result['status'])
688 ev.set()
689 return 0
692 def xenbusStatusCallback(statusPath, ev, result):
693 log.debug("xenbusStatusCallback %s.", statusPath)
695 status = xstransact.Read(statusPath)
697 if status == str(xenbusState['Connected']):
698 result['status'] = Connected
699 else:
700 return 1
702 log.debug("xenbusStatusCallback %d.", result['status'])
704 ev.set()
705 return 0