ia64/xen-unstable

view tools/python/xen/xend/server/DevController.py @ 13642:ba3ec84c9423

[XEND] Add missing ConsoleController.py

Signed-off-by: Alastair Tse <atse@xensource.com>
author Alastair Tse <atse@xensource.com>
date Fri Jan 26 02:44:35 2007 +0000 (2007-01-26)
parents b111908dd70b
children ed9dba8e2c67
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
26 from xen.xend.xenstore.xstransact import xstransact, complete
27 from xen.xend.xenstore.xswatch import xswatch
29 import os
31 DEVICE_CREATE_TIMEOUT = 100
32 HOTPLUG_STATUS_NODE = "hotplug-status"
33 HOTPLUG_ERROR_NODE = "hotplug-error"
34 HOTPLUG_STATUS_ERROR = "error"
35 HOTPLUG_STATUS_BUSY = "busy"
37 Connected = 1
38 Error = 2
39 Missing = 3
40 Timeout = 4
41 Busy = 5
43 xenbusState = {
44 'Unknown' : 0,
45 'Initialising' : 1,
46 'InitWait' : 2,
47 'Initialised' : 3,
48 'Connected' : 4,
49 'Closing' : 5,
50 'Closed' : 6,
51 }
53 xoptions = XendOptions.instance()
55 xenbusState.update(dict(zip(xenbusState.values(), xenbusState.keys())))
58 class DevController:
59 """Abstract base class for a device controller. Device controllers create
60 appropriate entries in the store to trigger the creation, reconfiguration,
61 and destruction of devices in guest domains. Each subclass of
62 DevController is responsible for a particular device-class, and
63 understands the details of configuration specific to that device-class.
65 DevController itself provides the functionality common to all device
66 creation tasks, as well as providing an interface to XendDomainInfo for
67 triggering those events themselves.
68 """
70 # Set when registered.
71 deviceClass = None
74 ## public:
76 def __init__(self, vm):
77 self.vm = vm
78 self.hotplug = True
80 def createDevice(self, config):
81 """Trigger the creation of a device with the given configuration.
83 @return The ID for the newly created device.
84 """
85 (devid, back, front) = self.getDeviceDetails(config)
86 if devid is None:
87 return 0
89 (backpath, frontpath) = self.addStoreEntries(config, devid, back,
90 front)
92 import xen.xend.XendDomain
93 xd = xen.xend.XendDomain.instance()
94 backdom_name = config.get('backend')
95 if backdom_name is None:
96 backdom = xen.xend.XendDomain.DOM0_ID
97 else:
98 bd = xd.domain_lookup_nr(backdom_name)
99 backdom = bd.getDomid()
100 count = 0
101 while True:
102 t = xstransact()
103 try:
104 if devid in self.deviceIDs(t):
105 if 'dev' in back:
106 dev_str = '%s (%d, %s)' % (back['dev'], devid,
107 self.deviceClass)
108 else:
109 dev_str = '%s (%s)' % (devid, self.deviceClass)
111 raise VmError("Device %s is already connected." % dev_str)
113 if count == 0:
114 log.debug('DevController: writing %s to %s.', str(front),
115 frontpath)
116 log.debug('DevController: writing %s to %s.', str(back),
117 backpath)
118 elif count % 50 == 0:
119 log.debug(
120 'DevController: still waiting to write device entries.')
122 t.remove(frontpath)
123 t.remove(backpath)
125 t.mkdir(backpath)
126 t.set_permissions(backpath,
127 {'dom': backdom },
128 {'dom' : self.vm.getDomid(),
129 'read' : True })
130 t.mkdir(frontpath)
131 t.set_permissions(frontpath,
132 {'dom': self.vm.getDomid()},
133 {'dom': backdom, 'read': True})
135 t.write2(frontpath, front)
136 t.write2(backpath, back)
138 if t.commit():
139 return devid
141 count += 1
142 except:
143 t.abort()
144 raise
147 def waitForDevices(self):
148 log.debug("Waiting for devices %s.", self.deviceClass)
149 return map(self.waitForDevice, self.deviceIDs())
152 def waitForDevice(self, devid):
153 log.debug("Waiting for %s.", devid)
155 if not self.hotplug:
156 return
158 status = self.waitForBackend(devid)
160 if status == Timeout:
161 self.destroyDevice(devid, False)
162 raise VmError("Device %s (%s) could not be connected. "
163 "Hotplug scripts not working." %
164 (devid, self.deviceClass))
166 elif status == Error:
167 self.destroyDevice(devid, False)
168 raise VmError("Device %s (%s) could not be connected. "
169 "Backend device not found." %
170 (devid, self.deviceClass))
172 elif status == Missing:
173 # Don't try to destroy the device; it's already gone away.
174 raise VmError("Device %s (%s) could not be connected. "
175 "Device not found." % (devid, self.deviceClass))
177 elif status == Busy:
178 err = None
179 frontpath = self.frontendPath(devid)
180 backpath = xstransact.Read(frontpath, "backend")
181 if backpath:
182 err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
183 if not err:
184 err = "Busy."
186 self.destroyDevice(devid, False)
187 raise VmError("Device %s (%s) could not be connected.\n%s" %
188 (devid, self.deviceClass, err))
192 def reconfigureDevice(self, devid, config):
193 """Reconfigure the specified device.
195 The implementation here just raises VmError. This may be overridden
196 by those subclasses that can reconfigure their devices.
197 """
198 raise VmError('%s devices may not be reconfigured' % self.deviceClass)
201 def destroyDevice(self, devid, force):
202 """Destroy the specified device.
204 @param devid The device ID, or something device-specific from which
205 the device ID can be determined (such as a guest-side device name).
207 The implementation here simply deletes the appropriate paths from the
208 store. This may be overridden by subclasses who need to perform other
209 tasks on destruction. Further, the implementation here can only
210 accept integer device IDs, or values that can be converted to
211 integers. Subclasses may accept other values and convert them to
212 integers before passing them here.
213 """
215 devid = int(devid)
217 # Modify online status /before/ updating state (latter is watched by
218 # drivers, so this ordering avoids a race).
219 self.writeBackend(devid, 'online', "0")
220 self.writeBackend(devid, 'state', str(xenbusState['Closing']))
222 if force:
223 frontpath = self.frontendPath(devid)
224 backpath = xstransact.Read(frontpath, "backend")
225 if backpath:
226 xstransact.Remove(backpath)
227 xstransact.Remove(frontpath)
230 def configurations(self):
231 return map(self.configuration, self.deviceIDs())
234 def configuration(self, devid):
235 """@return an s-expression giving the current configuration of the
236 specified device. This would be suitable for giving to {@link
237 #createDevice} in order to recreate that device."""
238 configDict = self.getDeviceConfiguration(devid)
239 sxpr = [self.deviceClass]
240 for key, val in configDict.items():
241 if isinstance(val, (types.ListType, types.TupleType)):
242 for v in val:
243 if v != None:
244 sxpr.append([key, v])
245 else:
246 if val != None:
247 sxpr.append([key, val])
248 return sxpr
250 def sxprs(self):
251 """@return an s-expression describing all the devices of this
252 controller's device-class.
253 """
254 return xstransact.ListRecursive(self.frontendRoot())
257 def sxpr(self, devid):
258 """@return an s-expression describing the specified device.
259 """
260 return [self.deviceClass, ['dom', self.vm.getDomid(),
261 'id', devid]]
264 def getDeviceConfiguration(self, devid):
265 """Returns the configuration of a device.
267 @note: Similar to L{configuration} except it returns a dict.
268 @return: dict
269 """
270 backdomid = xstransact.Read(self.frontendPath(devid), "backend-id")
271 if backdomid is None:
272 raise VmError("Device %s not connected" % devid)
274 return {'backend': int(backdomid)}
276 def getAllDeviceConfigurations(self):
277 all_configs = {}
278 for devid in self.deviceIDs():
279 config_dict = self.getDeviceConfiguration(devid)
280 all_configs[devid] = config_dict
281 return all_configs
283 ## protected:
285 def getDeviceDetails(self, config):
286 """Compute the details for creation of a device corresponding to the
287 given configuration. These details consist of a tuple of (devID,
288 backDetails, frontDetails), where devID is the ID for the new device,
289 and backDetails and frontDetails are the device configuration
290 specifics for the backend and frontend respectively.
292 backDetails and frontDetails should be dictionaries, the keys and
293 values of which will be used as paths in the store. There is no need
294 for these dictionaries to include the references from frontend to
295 backend, nor vice versa, as these will be handled by DevController.
297 Abstract; must be implemented by every subclass.
299 @return (devID, backDetails, frontDetails), as specified above.
300 """
302 raise NotImplementedError()
304 def migrate(self, deviceConfig, network, dst, step, domName):
305 """ Migration of a device. The 'network' parameter indicates
306 whether the device is network-migrated (True). 'dst' then gives
307 the hostname of the machine to migrate to.
308 This function is called for 4 steps:
309 If step == 0: Check whether the device is ready to be migrated
310 or can at all be migrated; return a '-1' if
311 the device is NOT ready, a '0' otherwise. If it is
312 not ready ( = not possible to migrate this device),
313 migration will not take place.
314 step == 1: Called immediately after step 0; migration
315 of the kernel has started;
316 step == 2: Called after the suspend has been issued
317 to the domain and the domain is not scheduled anymore.
318 Synchronize with what was started in step 1, if necessary.
319 Now the device should initiate its transfer to the
320 given target. Since there might be more than just
321 one device initiating a migration, this step should
322 put the process performing the transfer into the
323 background and return immediately to achieve as much
324 concurrency as possible.
325 step == 3: Synchronize with the migration of the device that
326 was initiated in step 2.
327 Make sure that the migration has finished and only
328 then return from the call.
329 """
330 tool = xoptions.get_external_migration_tool()
331 if tool:
332 log.info("Calling external migration tool for step %d" % step)
333 fd = os.popen("%s -type %s -step %d -host %s -domname %s" %
334 (tool, self.deviceClass, step, dst, domName))
335 for line in fd:
336 log.info(line.rstrip())
337 rc = fd.close()
338 if rc:
339 raise VmError('Migration tool returned %d' % (rc >> 8))
340 return 0
343 def recover_migrate(self, deviceConfig, network, dst, step, domName):
344 """ Recover from device migration. The given step was the
345 last one that was successfully executed.
346 """
347 tool = xoptions.get_external_migration_tool()
348 if tool:
349 log.info("Calling external migration tool")
350 fd = os.popen("%s -type %s -step %d -host %s -domname %s -recover" %
351 (tool, self.deviceClass, step, dst, domName))
352 for line in fd:
353 log.info(line.rstrip())
354 rc = fd.close()
355 if rc:
356 raise VmError('Migration tool returned %d' % (rc >> 8))
357 return 0
360 def getDomid(self):
361 """Stub to {@link XendDomainInfo.getDomid}, for use by our
362 subclasses.
363 """
364 return self.vm.getDomid()
367 def allocateDeviceID(self):
368 """Allocate a device ID, allocating them consecutively on a
369 per-domain, per-device-class basis, and using the store to record the
370 next available ID.
372 This method is available to our subclasses, though it is not
373 compulsory to use it; subclasses may prefer to allocate IDs based upon
374 the device configuration instead.
375 """
376 path = self.frontendMiscPath()
377 return complete(path, self._allocateDeviceID)
380 def _allocateDeviceID(self, t):
381 result = t.read("nextDeviceID")
382 if result:
383 result = int(result)
384 else:
385 result = 0
386 t.write("nextDeviceID", str(result + 1))
387 return result
390 def readBackend(self, devid, *args):
391 frontpath = self.frontendPath(devid)
392 backpath = xstransact.Read(frontpath, "backend")
393 if backpath:
394 return xstransact.Read(backpath, *args)
395 else:
396 raise VmError("Device %s not connected" % devid)
398 def readFrontend(self, devid, *args):
399 return xstransact.Read(self.frontendPath(devid), *args)
401 def deviceIDs(self, transaction = None):
402 """@return The IDs of each of the devices currently configured for
403 this instance's deviceClass.
404 """
405 fe = self.backendRoot()
406 if transaction:
407 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
408 else:
409 return map(int, xstransact.List(fe))
412 def writeBackend(self, devid, *args):
413 frontpath = self.frontendPath(devid)
414 backpath = xstransact.Read(frontpath, "backend")
416 if backpath:
417 xstransact.Write(backpath, *args)
418 else:
419 raise VmError("Device %s not connected" % devid)
422 ## private:
424 def addStoreEntries(self, config, devid, backDetails, frontDetails):
425 """Add to backDetails and frontDetails the entries to be written in
426 the store to trigger creation of a device. The backend domain ID is
427 taken from the given config, paths for frontend and backend are
428 computed, and these are added to the backDetails and frontDetails
429 dictionaries for writing to the store, including references from
430 frontend to backend and vice versa.
432 @return A pair of (backpath, frontpath). backDetails and frontDetails
433 will have been updated appropriately, also.
435 @param config The configuration of the device, as given to
436 {@link #createDevice}.
437 @param devid As returned by {@link #getDeviceDetails}.
438 @param backDetails As returned by {@link #getDeviceDetails}.
439 @param frontDetails As returned by {@link #getDeviceDetails}.
440 """
442 import xen.xend.XendDomain
443 xd = xen.xend.XendDomain.instance()
445 backdom_name = config.get('backend')
446 if backdom_name:
447 backdom = xd.domain_lookup_nr(backdom_name)
448 else:
449 backdom = xd.privilegedDomain()
451 if not backdom:
452 raise VmError("Cannot configure device for unknown backend %s" %
453 backdom_name)
455 frontpath = self.frontendPath(devid)
456 backpath = self.backendPath(backdom, devid)
458 frontDetails.update({
459 'backend' : backpath,
460 'backend-id' : "%i" % backdom.getDomid(),
461 'state' : str(xenbusState['Initialising'])
462 })
465 backDetails.update({
466 'domain' : self.vm.getName(),
467 'frontend' : frontpath,
468 'frontend-id' : "%i" % self.vm.getDomid(),
469 'state' : str(xenbusState['Initialising']),
470 'online' : "1"
471 })
473 return (backpath, frontpath)
476 def waitForBackend(self, devid):
478 frontpath = self.frontendPath(devid)
479 backpath = xstransact.Read(frontpath, "backend")
481 if backpath:
482 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
483 ev = Event()
484 result = { 'status': Timeout }
486 xswatch(statusPath, hotplugStatusCallback, ev, result)
488 ev.wait(DEVICE_CREATE_TIMEOUT)
489 return result['status']
490 else:
491 return Missing
494 def backendPath(self, backdom, devid):
495 """Construct backend path given the backend domain and device id.
497 @param backdom [XendDomainInfo] The backend domain info."""
499 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
500 self.deviceClass,
501 self.vm.getDomid(), devid)
504 def frontendPath(self, devid):
505 return "%s/%d" % (self.frontendRoot(), devid)
508 def frontendRoot(self):
509 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
511 def backendRoot(self):
512 """Construct backend root path assuming backend is domain 0."""
513 from xen.xend.XendDomain import DOM0_ID
514 from xen.xend.xenstore.xsutil import GetDomainPath
515 return "%s/backend/%s/%s" % (GetDomainPath(DOM0_ID),
516 self.deviceClass, self.vm.getDomid())
518 def frontendMiscPath(self):
519 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
520 self.deviceClass)
523 def hotplugStatusCallback(statusPath, ev, result):
524 log.debug("hotplugStatusCallback %s.", statusPath)
526 status = xstransact.Read(statusPath)
528 if status is not None:
529 if status == HOTPLUG_STATUS_ERROR:
530 result['status'] = Error
531 elif status == HOTPLUG_STATUS_BUSY:
532 result['status'] = Busy
533 else:
534 result['status'] = Connected
535 else:
536 return 1
538 log.debug("hotplugStatusCallback %d.", result['status'])
540 ev.set()
541 return 0