ia64/xen-unstable

view tools/python/xen/xend/server/DevController.py @ 18738:2c20d026bb55

xend: Fix typo in waitForBackend() for phantom VBDs

Signed-off-by: Masaki Kanno <kanno.masaki@jp.fujitsu.com>
author Keir Fraser <keir.fraser@citrix.com>
date Tue Oct 28 10:37:30 2008 +0000 (2008-10-28)
parents 5c48ab6b1977
children b3774712e654
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
26 from xen.xend.server.DevConstants import *
28 from xen.xend.xenstore.xstransact import xstransact, complete
29 from xen.xend.xenstore.xswatch import xswatch
31 import os
33 xoptions = XendOptions.instance()
36 class DevController:
37 """Abstract base class for a device controller. Device controllers create
38 appropriate entries in the store to trigger the creation, reconfiguration,
39 and destruction of devices in guest domains. Each subclass of
40 DevController is responsible for a particular device-class, and
41 understands the details of configuration specific to that device-class.
43 DevController itself provides the functionality common to all device
44 creation tasks, as well as providing an interface to XendDomainInfo for
45 triggering those events themselves.
46 """
48 # Set when registered.
49 deviceClass = None
52 ## public:
54 def __init__(self, vm):
55 self.vm = vm
56 self.hotplug = True
58 def createDevice(self, config):
59 """Trigger the creation of a device with the given configuration.
61 @return The ID for the newly created device.
62 """
63 (devid, back, front) = self.getDeviceDetails(config)
64 if devid is None:
65 return 0
67 self.setupDevice(config)
69 (backpath, frontpath) = self.addStoreEntries(config, devid, back,
70 front)
72 import xen.xend.XendDomain
73 xd = xen.xend.XendDomain.instance()
74 backdom_name = config.get('backend')
75 if backdom_name is None:
76 backdom = xen.xend.XendDomain.DOM0_ID
77 else:
78 bd = xd.domain_lookup_nr(backdom_name)
79 backdom = bd.getDomid()
80 count = 0
81 while True:
82 t = xstransact()
83 try:
84 if devid in self.deviceIDs(t):
85 if 'dev' in back:
86 dev_str = '%s (%d, %s)' % (back['dev'], devid,
87 self.deviceClass)
88 else:
89 dev_str = '%s (%s)' % (devid, self.deviceClass)
91 raise VmError("Device %s is already connected." % dev_str)
93 if count == 0:
94 log.debug('DevController: writing %s to %s.',
95 str(front), frontpath)
96 log.debug('DevController: writing %s to %s.',
97 str(xen.xend.XendConfig.scrub_password(back)), backpath)
98 elif count % 50 == 0:
99 log.debug(
100 'DevController: still waiting to write device entries.')
102 devpath = self.devicePath(devid)
104 t.remove(frontpath)
105 t.remove(backpath)
106 t.remove(devpath)
108 t.mkdir(backpath)
109 t.set_permissions(backpath,
110 {'dom': backdom },
111 {'dom' : self.vm.getDomid(),
112 'read' : True })
113 t.mkdir(frontpath)
114 t.set_permissions(frontpath,
115 {'dom': self.vm.getDomid()},
116 {'dom': backdom, 'read': True})
118 t.write2(frontpath, front)
119 t.write2(backpath, back)
121 t.mkdir(devpath)
122 t.write2(devpath, {
123 'backend' : backpath,
124 'backend-id' : "%i" % backdom,
125 'frontend' : frontpath,
126 'frontend-id' : "%i" % self.vm.getDomid()
127 })
129 if t.commit():
130 return devid
132 count += 1
133 except:
134 t.abort()
135 raise
138 def waitForDevices(self):
139 log.debug("Waiting for devices %s.", self.deviceClass)
140 return map(self.waitForDevice, self.deviceIDs())
143 def waitForDevice(self, devid):
144 log.debug("Waiting for %s.", devid)
146 if not self.hotplug:
147 return
149 (status, err) = self.waitForBackend(devid)
151 if status == Timeout:
152 self.destroyDevice(devid, False)
153 raise VmError("Device %s (%s) could not be connected. "
154 "Hotplug scripts not working." %
155 (devid, self.deviceClass))
157 elif status == Error:
158 self.destroyDevice(devid, False)
159 if err is None:
160 raise VmError("Device %s (%s) could not be connected. "
161 "Backend device not found." %
162 (devid, self.deviceClass))
163 else:
164 raise VmError("Device %s (%s) could not be connected. "
165 "%s" % (devid, self.deviceClass, err))
166 elif status == Missing:
167 # Don't try to destroy the device; it's already gone away.
168 raise VmError("Device %s (%s) could not be connected. "
169 "Device not found." % (devid, self.deviceClass))
171 elif status == Busy:
172 self.destroyDevice(devid, False)
173 if err is None:
174 err = "Busy."
175 raise VmError("Device %s (%s) could not be connected.\n%s" %
176 (devid, self.deviceClass, err))
179 def waitForDevice_destroy(self, devid, backpath):
180 log.debug("Waiting for %s - destroyDevice.", devid)
182 if not self.hotplug:
183 return
185 status = self.waitForBackend_destroy(backpath)
187 if status == Timeout:
188 raise VmError("Device %s (%s) could not be disconnected. " %
189 (devid, self.deviceClass))
191 def waitForDevice_reconfigure(self, devid):
192 log.debug("Waiting for %s - reconfigureDevice.", devid)
194 (status, err) = self.waitForBackend_reconfigure(devid)
196 if status == Timeout:
197 raise VmError("Device %s (%s) could not be reconfigured. " %
198 (devid, self.deviceClass))
201 def reconfigureDevice(self, devid, config):
202 """Reconfigure the specified device.
204 The implementation here just raises VmError. This may be overridden
205 by those subclasses that can reconfigure their devices.
206 """
207 raise VmError('%s devices may not be reconfigured' % self.deviceClass)
210 def destroyDevice(self, devid, force):
211 """Destroy the specified device.
213 @param devid The device ID, or something device-specific from which
214 the device ID can be determined (such as a guest-side device name).
216 The implementation here simply deletes the appropriate paths from the
217 store. This may be overridden by subclasses who need to perform other
218 tasks on destruction. The implementation here accepts integer device
219 IDs or paths containg integer deviceIDs, e.g. vfb/0. Subclasses may
220 accept other values and convert them to integers before passing them
221 here.
222 """
224 dev = self.convertToDeviceNumber(devid)
226 # Modify online status /before/ updating state (latter is watched by
227 # drivers, so this ordering avoids a race).
228 self.writeBackend(dev, 'online', "0")
229 self.writeBackend(dev, 'state', str(xenbusState['Closing']))
231 if force:
232 frontpath = self.frontendPath(dev)
233 backpath = self.readVm(dev, "backend")
234 if backpath:
235 xstransact.Remove(backpath)
236 xstransact.Remove(frontpath)
238 # xstransact.Remove(self.devicePath()) ?? Below is the same ?
239 self.vm._removeVm("device/%s/%d" % (self.deviceClass, dev))
241 def configurations(self, transaction = None):
242 return map(lambda x: self.configuration(x, transaction), self.deviceIDs(transaction))
245 def configuration(self, devid, transaction = None):
246 """@return an s-expression giving the current configuration of the
247 specified device. This would be suitable for giving to {@link
248 #createDevice} in order to recreate that device."""
249 configDict = self.getDeviceConfiguration(devid, transaction)
250 sxpr = [self.deviceClass]
251 for key, val in configDict.items():
252 if isinstance(val, (types.ListType, types.TupleType)):
253 for v in val:
254 if v != None:
255 sxpr.append([key, v])
256 else:
257 if val != None:
258 sxpr.append([key, val])
259 return sxpr
261 def sxprs(self):
262 """@return an s-expression describing all the devices of this
263 controller's device-class.
264 """
265 return xstransact.ListRecursive(self.frontendRoot())
268 def sxpr(self, devid):
269 """@return an s-expression describing the specified device.
270 """
271 return [self.deviceClass, ['dom', self.vm.getDomid(),
272 'id', devid]]
275 def getDeviceConfiguration(self, devid, transaction = None):
276 """Returns the configuration of a device.
278 @note: Similar to L{configuration} except it returns a dict.
279 @return: dict
280 """
281 if transaction is None:
282 backdomid = xstransact.Read(self.devicePath(devid), "backend-id")
283 else:
284 backdomid = transaction.read(self.devicePath(devid) + "/backend-id")
286 if backdomid is None:
287 raise VmError("Device %s not connected" % devid)
289 return {'backend': int(backdomid)}
291 def getAllDeviceConfigurations(self):
292 all_configs = {}
293 for devid in self.deviceIDs():
294 config_dict = self.getDeviceConfiguration(devid)
295 all_configs[devid] = config_dict
296 return all_configs
299 def convertToDeviceNumber(self, devid):
300 try:
301 return int(devid)
302 except ValueError:
303 # Does devid contain devicetype/deviceid?
304 # Propogate exception if unable to find an integer devid
305 return int(type(devid) is str and devid.split('/')[-1] or None)
307 ## protected:
309 def getDeviceDetails(self, config):
310 """Compute the details for creation of a device corresponding to the
311 given configuration. These details consist of a tuple of (devID,
312 backDetails, frontDetails), where devID is the ID for the new device,
313 and backDetails and frontDetails are the device configuration
314 specifics for the backend and frontend respectively.
316 backDetails and frontDetails should be dictionaries, the keys and
317 values of which will be used as paths in the store. There is no need
318 for these dictionaries to include the references from frontend to
319 backend, nor vice versa, as these will be handled by DevController.
321 Abstract; must be implemented by every subclass.
323 @return (devID, backDetails, frontDetails), as specified above.
324 """
326 raise NotImplementedError()
328 def setupDevice(self, config):
329 """ Setup device from config.
330 """
331 return
333 def migrate(self, deviceConfig, network, dst, step, domName):
334 """ Migration of a device. The 'network' parameter indicates
335 whether the device is network-migrated (True). 'dst' then gives
336 the hostname of the machine to migrate to.
337 This function is called for 4 steps:
338 If step == 0: Check whether the device is ready to be migrated
339 or can at all be migrated; return a '-1' if
340 the device is NOT ready, a '0' otherwise. If it is
341 not ready ( = not possible to migrate this device),
342 migration will not take place.
343 step == 1: Called immediately after step 0; migration
344 of the kernel has started;
345 step == 2: Called after the suspend has been issued
346 to the domain and the domain is not scheduled anymore.
347 Synchronize with what was started in step 1, if necessary.
348 Now the device should initiate its transfer to the
349 given target. Since there might be more than just
350 one device initiating a migration, this step should
351 put the process performing the transfer into the
352 background and return immediately to achieve as much
353 concurrency as possible.
354 step == 3: Synchronize with the migration of the device that
355 was initiated in step 2.
356 Make sure that the migration has finished and only
357 then return from the call.
358 """
359 tool = xoptions.get_external_migration_tool()
360 if tool:
361 log.info("Calling external migration tool for step %d" % step)
362 fd = os.popen("%s -type %s -step %d -host %s -domname %s" %
363 (tool, self.deviceClass, step, dst, domName))
364 for line in fd:
365 log.info(line.rstrip())
366 rc = fd.close()
367 if rc:
368 raise VmError('Migration tool returned %d' % (rc >> 8))
369 return 0
372 def recover_migrate(self, deviceConfig, network, dst, step, domName):
373 """ Recover from device migration. The given step was the
374 last one that was successfully executed.
375 """
376 tool = xoptions.get_external_migration_tool()
377 if tool:
378 log.info("Calling external migration tool")
379 fd = os.popen("%s -type %s -step %d -host %s -domname %s -recover" %
380 (tool, self.deviceClass, step, dst, domName))
381 for line in fd:
382 log.info(line.rstrip())
383 rc = fd.close()
384 if rc:
385 raise VmError('Migration tool returned %d' % (rc >> 8))
386 return 0
389 def getDomid(self):
390 """Stub to {@link XendDomainInfo.getDomid}, for use by our
391 subclasses.
392 """
393 return self.vm.getDomid()
396 def allocateDeviceID(self):
397 """Allocate a device ID, allocating them consecutively on a
398 per-domain, per-device-class basis, and using the store to record the
399 next available ID.
401 This method is available to our subclasses, though it is not
402 compulsory to use it; subclasses may prefer to allocate IDs based upon
403 the device configuration instead.
404 """
405 path = self.frontendMiscPath()
406 return complete(path, self._allocateDeviceID)
409 def _allocateDeviceID(self, t):
410 result = t.read("nextDeviceID")
411 if result:
412 result = int(result)
413 else:
414 result = 0
415 t.write("nextDeviceID", str(result + 1))
416 return result
419 def removeBackend(self, devid, *args):
420 frontpath = self.frontendPath(devid)
421 backpath = xstransact.Read(frontpath, "backend")
422 if backpath:
423 return xstransact.Remove(backpath, *args)
424 else:
425 raise VmError("Device %s not connected" % devid)
427 def readVm(self, devid, *args):
428 devpath = self.devicePath(devid)
429 if devpath:
430 return xstransact.Read(devpath, *args)
431 else:
432 raise VmError("Device config %s not found" % devid)
434 def readBackend(self, devid, *args):
435 backpath = self.readVm(devid, "backend")
436 if backpath:
437 return xstransact.Read(backpath, *args)
438 else:
439 raise VmError("Device %s not connected" % devid)
441 def readBackendTxn(self, transaction, devid, *args):
442 backpath = self.readVm(devid, "backend")
443 if backpath:
444 paths = map(lambda x: backpath + "/" + x, args)
445 return transaction.read(*paths)
446 else:
447 raise VmError("Device %s not connected" % devid)
449 def readFrontend(self, devid, *args):
450 return xstransact.Read(self.frontendPath(devid), *args)
452 def readFrontendTxn(self, transaction, devid, *args):
453 paths = map(lambda x: self.frontendPath(devid) + "/" + x, args)
454 return transaction.read(*paths)
456 def deviceIDs(self, transaction = None):
457 """@return The IDs of each of the devices currently configured for
458 this instance's deviceClass.
459 """
460 fe = self.deviceRoot()
462 if transaction:
463 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
464 else:
465 return map(int, xstransact.List(fe))
468 def writeBackend(self, devid, *args):
469 backpath = self.readVm(devid, "backend")
471 if backpath:
472 xstransact.Write(backpath, *args)
473 else:
474 raise VmError("Device %s not connected" % devid)
477 ## private:
479 def addStoreEntries(self, config, devid, backDetails, frontDetails):
480 """Add to backDetails and frontDetails the entries to be written in
481 the store to trigger creation of a device. The backend domain ID is
482 taken from the given config, paths for frontend and backend are
483 computed, and these are added to the backDetails and frontDetails
484 dictionaries for writing to the store, including references from
485 frontend to backend and vice versa.
487 @return A pair of (backpath, frontpath). backDetails and frontDetails
488 will have been updated appropriately, also.
490 @param config The configuration of the device, as given to
491 {@link #createDevice}.
492 @param devid As returned by {@link #getDeviceDetails}.
493 @param backDetails As returned by {@link #getDeviceDetails}.
494 @param frontDetails As returned by {@link #getDeviceDetails}.
495 """
497 import xen.xend.XendDomain
498 xd = xen.xend.XendDomain.instance()
500 backdom_name = config.get('backend')
501 if backdom_name:
502 backdom = xd.domain_lookup_nr(backdom_name)
503 else:
504 backdom = xd.privilegedDomain()
506 if not backdom:
507 raise VmError("Cannot configure device for unknown backend %s" %
508 backdom_name)
510 frontpath = self.frontendPath(devid)
511 backpath = self.backendPath(backdom, devid)
513 frontDetails.update({
514 'backend' : backpath,
515 'backend-id' : "%i" % backdom.getDomid(),
516 'state' : str(xenbusState['Initialising'])
517 })
519 if self.vm.native_protocol:
520 frontDetails.update({'protocol' : self.vm.native_protocol})
522 backDetails.update({
523 'domain' : self.vm.getName(),
524 'frontend' : frontpath,
525 'frontend-id' : "%i" % self.vm.getDomid(),
526 'state' : str(xenbusState['Initialising']),
527 'online' : "1"
528 })
530 return (backpath, frontpath)
533 def waitForBackend(self, devid):
534 frontpath = self.frontendPath(devid)
535 # lookup a phantom
536 phantomPath = xstransact.Read(frontpath, 'phantom_vbd')
537 if phantomPath is not None:
538 log.debug("Waiting for %s's phantom %s.", devid, phantomPath)
539 statusPath = phantomPath + '/' + HOTPLUG_STATUS_NODE
540 ev = Event()
541 result = { 'status': Timeout }
542 xswatch(statusPath, hotplugStatusCallback, ev, result)
543 ev.wait(DEVICE_CREATE_TIMEOUT)
544 err = xstransact.Read(statusPath, HOTPLUG_ERROR_NODE)
545 if result['status'] != Connected:
546 return (result['status'], err)
548 backpath = self.readVm(devid, "backend")
551 if backpath:
552 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
553 ev = Event()
554 result = { 'status': Timeout }
556 xswatch(statusPath, hotplugStatusCallback, ev, result)
558 ev.wait(DEVICE_CREATE_TIMEOUT)
560 err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
562 return (result['status'], err)
563 else:
564 return (Missing, None)
567 def waitForBackend_destroy(self, backpath):
569 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
570 ev = Event()
571 result = { 'status': Timeout }
573 xswatch(statusPath, deviceDestroyCallback, ev, result)
575 ev.wait(DEVICE_DESTROY_TIMEOUT)
577 return result['status']
579 def waitForBackend_reconfigure(self, devid):
580 frontpath = self.frontendPath(devid)
581 backpath = xstransact.Read(frontpath, "backend")
582 if backpath:
583 statusPath = backpath + '/' + "state"
584 ev = Event()
585 result = { 'status': Timeout }
587 xswatch(statusPath, xenbusStatusCallback, ev, result)
589 ev.wait(DEVICE_CREATE_TIMEOUT)
591 return (result['status'], None)
592 else:
593 return (Missing, None)
596 def backendPath(self, backdom, devid):
597 """Construct backend path given the backend domain and device id.
599 @param backdom [XendDomainInfo] The backend domain info."""
601 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
602 self.deviceClass,
603 self.vm.getDomid(), devid)
606 def frontendPath(self, devid):
607 return "%s/%d" % (self.frontendRoot(), devid)
610 def frontendRoot(self):
611 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
613 def frontendMiscPath(self):
614 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
615 self.deviceClass)
617 def deviceRoot(self):
618 """Return the /vm/device. Because backendRoot assumes the
619 backend domain is 0"""
620 return "%s/device/%s" % (self.vm.vmpath, self.deviceClass)
622 def devicePath(self, devid):
623 """Return the /device entry of the given VM. We use it to store
624 backend/frontend locations"""
625 return "%s/device/%s/%s" % (self.vm.vmpath,
626 self.deviceClass, devid)
628 def hotplugStatusCallback(statusPath, ev, result):
629 log.debug("hotplugStatusCallback %s.", statusPath)
631 status = xstransact.Read(statusPath)
633 if status is not None:
634 if status == HOTPLUG_STATUS_ERROR:
635 result['status'] = Error
636 elif status == HOTPLUG_STATUS_BUSY:
637 result['status'] = Busy
638 else:
639 result['status'] = Connected
640 else:
641 return 1
643 log.debug("hotplugStatusCallback %d.", result['status'])
645 ev.set()
646 return 0
649 def deviceDestroyCallback(statusPath, ev, result):
650 log.debug("deviceDestroyCallback %s.", statusPath)
652 status = xstransact.Read(statusPath)
654 if status is None:
655 result['status'] = Disconnected
656 else:
657 return 1
659 log.debug("deviceDestroyCallback %d.", result['status'])
661 ev.set()
662 return 0
665 def xenbusStatusCallback(statusPath, ev, result):
666 log.debug("xenbusStatusCallback %s.", statusPath)
668 status = xstransact.Read(statusPath)
670 if status == str(xenbusState['Connected']):
671 result['status'] = Connected
672 else:
673 return 1
675 log.debug("xenbusStatusCallback %d.", result['status'])
677 ev.set()
678 return 0