direct-io.hg

view tools/python/xen/xend/server/DevController.py @ 9524:a97fe1ca116e

Lookup backend by name _or_id_. This is important for domain 0, for example!

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@leeni.uk.xensource.com
date Mon Apr 03 23:57:42 2006 +0100 (2006-04-03)
parents 7df5921af7e4
children 1fe63743a147
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, complete
26 from xen.xend.xenstore.xswatch import xswatch
28 DEVICE_CREATE_TIMEOUT = 10
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 Error = 2
36 Missing = 3
37 Timeout = 4
38 Busy = 5
40 xenbusState = {
41 'Unknown' : 0,
42 'Initialising' : 1,
43 'InitWait' : 2,
44 'Initialised' : 3,
45 'Connected' : 4,
46 'Closing' : 5,
47 'Closed' : 6,
48 }
50 xenbusState.update(dict(zip(xenbusState.values(), xenbusState.keys())))
53 class DevController:
54 """Abstract base class for a device controller. Device controllers create
55 appropriate entries in the store to trigger the creation, reconfiguration,
56 and destruction of devices in guest domains. Each subclass of
57 DevController is responsible for a particular device-class, and
58 understands the details of configuration specific to that device-class.
60 DevController itself provides the functionality common to all device
61 creation tasks, as well as providing an interface to XendDomainInfo for
62 triggering those events themselves.
63 """
65 # Set when registered.
66 deviceClass = None
69 ## public:
71 def __init__(self, vm):
72 self.vm = vm
75 def createDevice(self, config):
76 """Trigger the creation of a device with the given configuration.
78 @return The ID for the newly created device.
79 """
80 (devid, back, front) = self.getDeviceDetails(config)
81 if devid is None:
82 return 0
84 (backpath, frontpath) = self.addStoreEntries(config, devid, back,
85 front)
87 import xen.xend.XendDomain
88 xd = xen.xend.XendDomain.instance()
89 backdom_name = sxp.child_value(config, 'backend')
90 if backdom_name is None:
91 backdom = xen.xend.XendDomain.PRIV_DOMAIN
92 else:
93 bd = xd.domain_lookup_by_name_or_id_nr(backdom_name)
94 backdom = bd.getDomid()
95 count = 0
96 while True:
97 t = xstransact()
98 try:
99 if devid in self.deviceIDs(t):
100 if 'dev' in back:
101 dev_str = '%s (%d, %s)' % (back['dev'], devid,
102 self.deviceClass)
103 else:
104 dev_str = '%s (%s)' % (devid, self.deviceClass)
106 raise VmError("Device %s is already connected." % dev_str)
108 if count == 0:
109 log.debug('DevController: writing %s to %s.', str(front),
110 frontpath)
111 log.debug('DevController: writing %s to %s.', str(back),
112 backpath)
113 elif count % 50 == 0:
114 log.debug(
115 'DevController: still waiting to write device entries.')
117 t.remove(frontpath)
118 t.remove(backpath)
120 t.mkdir(backpath)
121 t.set_permissions(backpath,
122 {'dom': backdom },
123 {'dom' : self.vm.getDomid(),
124 'read' : True })
125 t.mkdir(frontpath)
126 t.set_permissions(frontpath,
127 {'dom': self.vm.getDomid()},
128 {'dom': backdom, 'read': True})
130 t.write2(frontpath, front)
131 t.write2(backpath, back)
133 if t.commit():
134 return devid
136 count += 1
137 except:
138 t.abort()
139 raise
142 def waitForDevices(self):
143 log.debug("Waiting for devices %s.", self.deviceClass)
145 return map(self.waitForDevice, self.deviceIDs())
148 def waitForDevice(self, devid):
149 log.debug("Waiting for %s.", devid)
151 status = self.waitForBackend(devid)
153 if status == Timeout:
154 self.destroyDevice(devid)
155 raise VmError("Device %s (%s) could not be connected. "
156 "Hotplug scripts not working." %
157 (devid, self.deviceClass))
159 elif status == Error:
160 self.destroyDevice(devid)
161 raise VmError("Device %s (%s) could not be connected. "
162 "Backend device not found." %
163 (devid, self.deviceClass))
165 elif status == Missing:
166 # Don't try to destroy the device; it's already gone away.
167 raise VmError("Device %s (%s) could not be connected. "
168 "Device not found." % (devid, self.deviceClass))
170 elif status == Busy:
171 err = None
172 frontpath = self.frontendPath(devid)
173 backpath = xstransact.Read(frontpath, "backend")
174 if backpath:
175 err = xstransact.Read(backpath, HOTPLUG_ERROR_NODE)
176 if not err:
177 err = "Busy."
179 self.destroyDevice(devid)
180 raise VmError("Device %s (%s) could not be connected.\n%s" %
181 (devid, self.deviceClass, err))
185 def reconfigureDevice(self, devid, config):
186 """Reconfigure the specified device.
188 The implementation here just raises VmError. This may be overridden
189 by those subclasses that can reconfigure their devices.
190 """
191 raise VmError('%s devices may not be reconfigured' % self.deviceClass)
194 def destroyDevice(self, devid):
195 """Destroy the specified device.
197 @param devid The device ID, or something device-specific from which
198 the device ID can be determined (such as a guest-side device name).
200 The implementation here simply deletes the appropriate paths from the
201 store. This may be overridden by subclasses who need to perform other
202 tasks on destruction. Further, the implementation here can only
203 accept integer device IDs, or values that can be converted to
204 integers. Subclasses may accept other values and convert them to
205 integers before passing them here.
206 """
208 devid = int(devid)
210 frontpath = self.frontendPath(devid)
211 backpath = xstransact.Read(frontpath, "backend")
213 if backpath:
214 xstransact.Write(backpath, 'state', str(xenbusState['Closing']))
215 else:
216 raise VmError("Device %s not connected" % devid)
219 def configurations(self):
220 return map(self.configuration, self.deviceIDs())
223 def configuration(self, devid):
224 """@return an s-expression giving the current configuration of the
225 specified device. This would be suitable for giving to {@link
226 #createDevice} in order to recreate that device."""
228 backdomid = xstransact.Read(self.frontendPath(devid), "backend-id")
229 if backdomid is None:
230 raise VmError("Device %s not connected" % devid)
232 return [self.deviceClass, ['backend', int(backdomid)]]
235 def sxprs(self):
236 """@return an s-expression describing all the devices of this
237 controller's device-class.
238 """
239 return xstransact.ListRecursive(self.frontendRoot())
242 def sxpr(self, devid):
243 """@return an s-expression describing the specified device.
244 """
245 return [self.deviceClass, ['dom', self.vm.getDomid(),
246 'id', devid]]
249 ## protected:
251 def getDeviceDetails(self, config):
252 """Compute the details for creation of a device corresponding to the
253 given configuration. These details consist of a tuple of (devID,
254 backDetails, frontDetails), where devID is the ID for the new device,
255 and backDetails and frontDetails are the device configuration
256 specifics for the backend and frontend respectively.
258 backDetails and frontDetails should be dictionaries, the keys and
259 values of which will be used as paths in the store. There is no need
260 for these dictionaries to include the references from frontend to
261 backend, nor vice versa, as these will be handled by DevController.
263 Abstract; must be implemented by every subclass.
265 @return (devID, backDetails, frontDetails), as specified above.
266 """
268 raise NotImplementedError()
271 def getDomid(self):
272 """Stub to {@link XendDomainInfo.getDomid}, for use by our
273 subclasses.
274 """
275 return self.vm.getDomid()
278 def allocateDeviceID(self):
279 """Allocate a device ID, allocating them consecutively on a
280 per-domain, per-device-class basis, and using the store to record the
281 next available ID.
283 This method is available to our subclasses, though it is not
284 compulsory to use it; subclasses may prefer to allocate IDs based upon
285 the device configuration instead.
286 """
287 path = self.frontendMiscPath()
288 return complete(path, self._allocateDeviceID)
291 def _allocateDeviceID(self, t):
292 result = t.read("nextDeviceID")
293 if result:
294 result = int(result)
295 else:
296 result = 0
297 t.write("nextDeviceID", str(result + 1))
298 return result
301 def readBackend(self, devid, *args):
302 frontpath = self.frontendPath(devid)
303 backpath = xstransact.Read(frontpath, "backend")
304 if backpath:
305 return xstransact.Read(backpath, *args)
306 else:
307 raise VmError("Device %s not connected" % devid)
310 def deviceIDs(self, transaction = None):
311 """@return The IDs of each of the devices currently configured for
312 this instance's deviceClass.
313 """
314 fe = self.frontendRoot()
315 if transaction:
316 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
317 else:
318 return map(int, xstransact.List(fe))
321 ## private:
323 def addStoreEntries(self, config, devid, backDetails, frontDetails):
324 """Add to backDetails and frontDetails the entries to be written in
325 the store to trigger creation of a device. The backend domain ID is
326 taken from the given config, paths for frontend and backend are
327 computed, and these are added to the backDetails and frontDetails
328 dictionaries for writing to the store, including references from
329 frontend to backend and vice versa.
331 @return A pair of (backpath, frontpath). backDetails and frontDetails
332 will have been updated appropriately, also.
334 @param config The configuration of the device, as given to
335 {@link #createDevice}.
336 @param devid As returned by {@link #getDeviceDetails}.
337 @param backDetails As returned by {@link #getDeviceDetails}.
338 @param frontDetails As returned by {@link #getDeviceDetails}.
339 """
341 import xen.xend.XendDomain
342 xd = xen.xend.XendDomain.instance()
344 backdom_name = sxp.child_value(config, 'backend')
345 if backdom_name:
346 backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
347 else:
348 backdom = xd.privilegedDomain()
350 if not backdom:
351 raise VmError("Cannot configure device for unknown backend %s" %
352 backdom_name)
354 frontpath = self.frontendPath(devid)
355 backpath = self.backendPath(backdom, devid)
357 frontDetails.update({
358 'backend' : backpath,
359 'backend-id' : "%i" % backdom.getDomid(),
360 'state' : str(xenbusState['Initialising'])
361 })
364 backDetails.update({
365 'domain' : self.vm.getName(),
366 'frontend' : frontpath,
367 'frontend-id' : "%i" % self.vm.getDomid(),
368 'state' : str(xenbusState['Initialising'])
369 })
371 return (backpath, frontpath)
374 def waitForBackend(self, devid):
376 frontpath = self.frontendPath(devid)
377 backpath = xstransact.Read(frontpath, "backend")
379 if backpath:
380 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
381 ev = Event()
382 result = { 'status': Timeout }
384 xswatch(statusPath, hotplugStatusCallback, ev, result)
386 ev.wait(DEVICE_CREATE_TIMEOUT)
387 return result['status']
388 else:
389 return Missing
392 def backendPath(self, backdom, devid):
393 """@param backdom [XendDomainInfo] The backend domain info."""
395 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
396 self.deviceClass,
397 self.vm.getDomid(), devid)
400 def frontendPath(self, devid):
401 return "%s/%d" % (self.frontendRoot(), devid)
404 def frontendRoot(self):
405 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
408 def frontendMiscPath(self):
409 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
410 self.deviceClass)
413 def hotplugStatusCallback(statusPath, ev, result):
414 log.debug("hotplugStatusCallback %s.", statusPath)
416 status = xstransact.Read(statusPath)
418 if status is not None:
419 if status == HOTPLUG_STATUS_ERROR:
420 result['status'] = Error
421 elif status == HOTPLUG_STATUS_BUSY:
422 result['status'] = Busy
423 else:
424 result['status'] = Connected
425 else:
426 return 1
428 log.debug("hotplugStatusCallback %d.", result['status'])
430 ev.set()
431 return 0