ia64/xen-unstable

view tools/python/xen/xend/server/DevController.py @ 8067:f710753a4026

Added a comment.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Sat Nov 26 00:38:45 2005 +0000 (2005-11-26)
parents 8c3ed250366c
children b1940475284f
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
21 from xen.xend import sxp
22 from xen.xend.XendError import VmError
23 from xen.xend.XendLogging import log
25 from xen.xend.xenstore.xstransact import xstransact
26 from xen.xend.xenstore.xswatch import xswatch
28 DEVICE_CREATE_TIMEOUT = 5
29 HOTPLUG_STATUS_NODE = "hotplug-status"
30 HOTPLUG_ERROR_NODE = "hotplug-error"
31 HOTPLUG_STATUS_ERROR = "error"
32 HOTPLUG_STATUS_BUSY = "busy"
34 Connected = 1
35 Died = 2
36 Error = 3
37 Missing = 4
38 Timeout = 5
39 Busy = 6
41 xenbusState = {
42 'Unknown' : 0,
43 'Initialising' : 1,
44 'InitWait' : 2,
45 'Initialised' : 3,
46 'Connected' : 4,
47 'Closing' : 5,
48 'Closed' : 6,
49 }
51 xenbusState.update(dict(zip(xenbusState.values(), xenbusState.keys())))
54 class DevController:
55 """Abstract base class for a device controller. Device controllers create
56 appropriate entries in the store to trigger the creation, reconfiguration,
57 and destruction of devices in guest domains. Each subclass of
58 DevController is responsible for a particular device-class, and
59 understands the details of configuration specific to that device-class.
61 DevController itself provides the functionality common to all device
62 creation tasks, as well as providing an interface to XendDomainInfo for
63 triggering those events themselves.
64 """
66 # Set when registered.
67 deviceClass = None
70 ## public:
72 def __init__(self, vm):
73 self.vm = vm
76 def createDevice(self, config):
77 """Trigger the creation of a device with the given configuration.
79 @return The ID for the newly created device.
80 """
81 (devid, back, front) = self.getDeviceDetails(config)
82 if devid is None:
83 return 0
85 (backpath, frontpath) = self.addStoreEntries(config, devid, back,
86 front)
88 while True:
89 t = xstransact()
90 try:
91 if devid in self.deviceIDs(t):
92 if 'dev' in back:
93 dev_str = '%s (%d, %s)' % (back['dev'], devid,
94 self.deviceClass)
95 else:
96 dev_str = '%s (%s)' % (devid, self.deviceClass)
98 raise VmError("Device %s is already connected." % dev_str)
100 log.debug('DevController: writing %s to %s.', str(front),
101 frontpath)
102 log.debug('DevController: writing %s to %s.', str(back),
103 backpath)
105 t.remove(frontpath)
106 t.remove(backpath)
108 t.write2(frontpath, front)
109 t.write2(backpath, back)
111 if t.commit():
112 return devid
113 except:
114 t.abort()
115 raise
118 def waitForDevices(self):
119 log.debug("Waiting for devices %s.", self.deviceClass)
121 return map(self.waitForDevice, self.deviceIDs())
124 def waitForDevice(self, devid):
125 log.debug("Waiting for %s.", devid)
127 status = self.waitForBackend(devid)
129 if status == Timeout:
130 self.destroyDevice(devid)
131 raise VmError("Device %s (%s) could not be connected. "
132 "Hotplug scripts not working." %
133 (devid, self.deviceClass))
135 elif status == Error:
136 self.destroyDevice(devid)
137 raise VmError("Device %s (%s) could not be connected. "
138 "Backend device not found." %
139 (devid, self.deviceClass))
141 elif status == Missing:
142 # Don't try to destroy the device; it's already gone away.
143 raise VmError("Device %s (%s) could not be connected. "
144 "Device not found." % (devid, self.deviceClass))
146 elif status == Died:
147 self.destroyDevice(devid)
148 raise VmError("Device %s (%s) could not be connected. "
149 "Device has died." % (devid, self.deviceClass))
151 elif status == Busy:
152 err = None
153 frontpath = self.frontendPath(devid)
154 backpath = xstransact.Read(frontpath, "backend")
155 if backpath:
156 err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
157 if not err:
158 err = "Busy."
160 self.destroyDevice(devid)
161 raise VmError("Device %s (%s) could not be connected.\n%s" %
162 (devid, self.deviceClass, err))
166 def reconfigureDevice(self, devid, config):
167 """Reconfigure the specified device.
169 The implementation here just raises VmError. This may be overridden
170 by those subclasses that can reconfigure their devices.
171 """
172 raise VmError('%s devices may not be reconfigured' % self.deviceClass)
175 def destroyDevice(self, devid):
176 """Destroy the specified device.
178 @param devid The device ID, or something device-specific from which
179 the device ID can be determined (such as a guest-side device name).
181 The implementation here simply deletes the appropriate paths from the
182 store. This may be overridden by subclasses who need to perform other
183 tasks on destruction. Further, the implementation here can only
184 accept integer device IDs, or values that can be converted to
185 integers. Subclasses may accept other values and convert them to
186 integers before passing them here.
187 """
189 devid = int(devid)
191 frontpath = self.frontendPath(devid)
192 backpath = xstransact.Read(frontpath, "backend")
194 if backpath:
195 xstransact.Write(backpath, 'state', str(xenbusState['Closing']))
196 else:
197 raise VmError("Device %s not connected" % devid)
200 def configurations(self):
201 return map(self.configuration, self.deviceIDs())
204 def configuration(self, devid):
205 """@return an s-expression giving the current configuration of the
206 specified device. This would be suitable for giving to {@link
207 #createDevice} in order to recreate that device."""
209 backdomid = xstransact.Read(self.frontendPath(devid), "backend-id")
210 if backdomid is None:
211 raise VmError("Device %s not connected" % devid)
213 return [self.deviceClass, ['backend', int(backdomid)]]
216 def sxprs(self):
217 """@return an s-expression describing all the devices of this
218 controller's device-class.
219 """
220 return xstransact.ListRecursive(self.frontendRoot())
223 def sxpr(self, devid):
224 """@return an s-expression describing the specified device.
225 """
226 return [self.deviceClass, ['dom', self.vm.getDomid(),
227 'id', devid]]
230 ## protected:
232 def getDeviceDetails(self, config):
233 """Compute the details for creation of a device corresponding to the
234 given configuration. These details consist of a tuple of (devID,
235 backDetails, frontDetails), where devID is the ID for the new device,
236 and backDetails and frontDetails are the device configuration
237 specifics for the backend and frontend respectively.
239 backDetails and frontDetails should be dictionaries, the keys and
240 values of which will be used as paths in the store. There is no need
241 for these dictionaries to include the references from frontend to
242 backend, nor vice versa, as these will be handled by DevController.
244 Abstract; must be implemented by every subclass.
246 @return (devID, backDetails, frontDetails), as specified above.
247 """
249 raise NotImplementedError()
252 def getDomid(self):
253 """Stub to {@link XendDomainInfo.getDomid}, for use by our
254 subclasses.
255 """
256 return self.vm.getDomid()
259 def allocateDeviceID(self):
260 """Allocate a device ID, allocating them consecutively on a
261 per-domain, per-device-class basis, and using the store to record the
262 next available ID.
264 This method is available to our subclasses, though it is not
265 compulsory to use it; subclasses may prefer to allocate IDs based upon
266 the device configuration instead.
267 """
268 path = self.frontendMiscPath()
269 while True:
270 t = xstransact(path)
271 try:
272 result = t.read("nextDeviceID")
273 if result:
274 result = int(result)
275 else:
276 result = 0
277 t.write("nextDeviceID", str(result + 1))
278 if t.commit():
279 return result
280 except:
281 t.abort()
282 raise
285 def readBackend(self, devid, *args):
286 frontpath = self.frontendPath(devid)
287 backpath = xstransact.Read(frontpath, "backend")
288 if backpath:
289 return xstransact.Read(backpath, *args)
290 else:
291 raise VmError("Device %s not connected" % devid)
294 def deviceIDs(self, transaction = None):
295 """@return The IDs of each of the devices currently configured for
296 this instance's deviceClass.
297 """
298 fe = self.frontendRoot()
299 if transaction:
300 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
301 else:
302 return map(int, xstransact.List(fe))
305 ## private:
307 def addStoreEntries(self, config, devid, backDetails, frontDetails):
308 """Add to backDetails and frontDetails the entries to be written in
309 the store to trigger creation of a device. The backend domain ID is
310 taken from the given config, paths for frontend and backend are
311 computed, and these are added to the backDetails and frontDetails
312 dictionaries for writing to the store, including references from
313 frontend to backend and vice versa.
315 @return A pair of (backpath, frontpath). backDetails and frontDetails
316 will have been updated appropriately, also.
318 @param config The configuration of the device, as given to
319 {@link #createDevice}.
320 @param devid As returned by {@link #getDeviceDetails}.
321 @param backDetails As returned by {@link #getDeviceDetails}.
322 @param frontDetails As returned by {@link #getDeviceDetails}.
323 """
325 import xen.xend.XendDomain
326 xd = xen.xend.XendDomain.instance()
328 backdom_name = sxp.child_value(config, 'backend')
329 if backdom_name:
330 backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
331 else:
332 backdom = xd.privilegedDomain()
334 if not backdom:
335 raise VmError("Cannot configure device for unknown backend %s" %
336 backdom_name)
338 frontpath = self.frontendPath(devid)
339 backpath = self.backendPath(backdom, devid)
341 frontDetails.update({
342 'backend' : backpath,
343 'backend-id' : "%i" % backdom.getDomid(),
344 'state' : str(xenbusState['Initialising'])
345 })
348 backDetails.update({
349 'domain' : self.vm.getName(),
350 'frontend' : frontpath,
351 'frontend-id' : "%i" % self.vm.getDomid(),
352 'state' : str(xenbusState['Initialising'])
353 })
355 return (backpath, frontpath)
358 def waitForBackend(self, devid):
360 frontpath = self.frontendPath(devid)
361 backpath = xstransact.Read(frontpath, "backend")
363 if backpath:
364 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
365 ev = Event()
366 result = { 'status': Timeout }
368 xswatch(statusPath, hotplugStatusCallback, ev, result)
370 ev.wait(DEVICE_CREATE_TIMEOUT)
371 return result['status']
372 else:
373 return Missing
376 def backendPath(self, backdom, devid):
377 """@param backdom [XendDomainInfo] The backend domain info."""
379 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
380 self.deviceClass,
381 self.vm.getDomid(), devid)
384 def frontendPath(self, devid):
385 return "%s/%d" % (self.frontendRoot(), devid)
388 def frontendRoot(self):
389 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
392 def frontendMiscPath(self):
393 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
394 self.deviceClass)
397 def hotplugStatusCallback(statusPath, ev, result):
398 log.debug("hotplugStatusCallback %s.", statusPath)
400 try:
401 status = xstransact.Read(statusPath)
403 if status is not None:
404 if status == HOTPLUG_STATUS_ERROR:
405 result['status'] = Error
406 elif status == HOTPLUG_STATUS_BUSY:
407 result['status'] = Busy
408 else:
409 result['status'] = Connected
410 else:
411 return 1
412 except VmError:
413 result['status'] = Died
415 log.debug("hotplugStatusCallback %d.", result['status'])
417 ev.set()
418 return 0