debuggers.hg

view tools/python/xen/xend/server/DevController.py @ 10988:353404fe850c

[xend] Fix reboot for hvm domains with cdrom devices.
The device-type was not added to the device details which caused
cdrom devices to appear as harddisks after a reboot.

From: Alex Brett <Alex.Brett@xensource.com>
Signed-off-by: Christian Limpach <Christian.Limpach@xensource.com>
author chris@kneesaa.uk.xensource.com
date Mon Aug 07 11:59:31 2006 +0100 (2006-08-07)
parents f692a0a476c5
children ff124973a28a
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()
270 def migrate(self, deviceConfig, network, dst, step, domName):
271 """ Migration of a device. The 'network' parameter indicates
272 whether the device is network-migrated (True). 'dst' then gives
273 the hostname of the machine to migrate to.
274 This function is called for 4 steps:
275 If step == 0: Check whether the device is ready to be migrated
276 or can at all be migrated; return a '-1' if
277 the device is NOT ready, a '0' otherwise. If it is
278 not ready ( = not possible to migrate this device),
279 migration will not take place.
280 step == 1: Called immediately after step 0; migration
281 of the kernel has started;
282 step == 2: Called after the suspend has been issued
283 to the domain and the domain is not scheduled anymore.
284 Synchronize with what was started in step 1, if necessary.
285 Now the device should initiate its transfer to the
286 given target. Since there might be more than just
287 one device initiating a migration, this step should
288 put the process performing the transfer into the
289 background and return immediately to achieve as much
290 concurrency as possible.
291 step == 3: Synchronize with the migration of the device that
292 was initiated in step 2.
293 Make sure that the migration has finished and only
294 then return from the call.
295 """
296 return 0
299 def recover_migrate(self, deviceConfig, network, dst, step, domName):
300 """ Recover from device migration. The given step was the
301 last one that was successfully executed.
302 """
303 return 0
306 def getDomid(self):
307 """Stub to {@link XendDomainInfo.getDomid}, for use by our
308 subclasses.
309 """
310 return self.vm.getDomid()
313 def allocateDeviceID(self):
314 """Allocate a device ID, allocating them consecutively on a
315 per-domain, per-device-class basis, and using the store to record the
316 next available ID.
318 This method is available to our subclasses, though it is not
319 compulsory to use it; subclasses may prefer to allocate IDs based upon
320 the device configuration instead.
321 """
322 path = self.frontendMiscPath()
323 return complete(path, self._allocateDeviceID)
326 def _allocateDeviceID(self, t):
327 result = t.read("nextDeviceID")
328 if result:
329 result = int(result)
330 else:
331 result = 0
332 t.write("nextDeviceID", str(result + 1))
333 return result
336 def readBackend(self, devid, *args):
337 frontpath = self.frontendPath(devid)
338 backpath = xstransact.Read(frontpath, "backend")
339 if backpath:
340 return xstransact.Read(backpath, *args)
341 else:
342 raise VmError("Device %s not connected" % devid)
344 def readFrontend(self, devid, *args):
345 return xstransact.Read(self.frontendPath(devid), *args)
347 def deviceIDs(self, transaction = None):
348 """@return The IDs of each of the devices currently configured for
349 this instance's deviceClass.
350 """
351 fe = self.backendRoot()
352 if transaction:
353 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
354 else:
355 return map(int, xstransact.List(fe))
358 ## private:
360 def addStoreEntries(self, config, devid, backDetails, frontDetails):
361 """Add to backDetails and frontDetails the entries to be written in
362 the store to trigger creation of a device. The backend domain ID is
363 taken from the given config, paths for frontend and backend are
364 computed, and these are added to the backDetails and frontDetails
365 dictionaries for writing to the store, including references from
366 frontend to backend and vice versa.
368 @return A pair of (backpath, frontpath). backDetails and frontDetails
369 will have been updated appropriately, also.
371 @param config The configuration of the device, as given to
372 {@link #createDevice}.
373 @param devid As returned by {@link #getDeviceDetails}.
374 @param backDetails As returned by {@link #getDeviceDetails}.
375 @param frontDetails As returned by {@link #getDeviceDetails}.
376 """
378 import xen.xend.XendDomain
379 xd = xen.xend.XendDomain.instance()
381 backdom_name = sxp.child_value(config, 'backend')
382 if backdom_name:
383 backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
384 else:
385 backdom = xd.privilegedDomain()
387 if not backdom:
388 raise VmError("Cannot configure device for unknown backend %s" %
389 backdom_name)
391 frontpath = self.frontendPath(devid)
392 backpath = self.backendPath(backdom, devid)
394 frontDetails.update({
395 'backend' : backpath,
396 'backend-id' : "%i" % backdom.getDomid(),
397 'state' : str(xenbusState['Initialising'])
398 })
401 backDetails.update({
402 'domain' : self.vm.getName(),
403 'frontend' : frontpath,
404 'frontend-id' : "%i" % self.vm.getDomid(),
405 'state' : str(xenbusState['Initialising'])
406 })
408 return (backpath, frontpath)
411 def waitForBackend(self, devid):
413 frontpath = self.frontendPath(devid)
414 backpath = xstransact.Read(frontpath, "backend")
416 if backpath:
417 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
418 ev = Event()
419 result = { 'status': Timeout }
421 xswatch(statusPath, hotplugStatusCallback, ev, result)
423 ev.wait(DEVICE_CREATE_TIMEOUT)
424 return result['status']
425 else:
426 return Missing
429 def backendPath(self, backdom, devid):
430 """@param backdom [XendDomainInfo] The backend domain info."""
432 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
433 self.deviceClass,
434 self.vm.getDomid(), devid)
437 def frontendPath(self, devid):
438 return "%s/%d" % (self.frontendRoot(), devid)
441 def frontendRoot(self):
442 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
444 def backendRoot(self):
445 import xen.xend.XendDomain
446 from xen.xend.xenstore.xsutil import GetDomainPath
447 backdom = xen.xend.XendDomain.PRIV_DOMAIN
448 return "%s/backend/%s/%s" % (GetDomainPath(backdom), self.deviceClass, self.vm.getDomid())
450 def frontendMiscPath(self):
451 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
452 self.deviceClass)
455 def hotplugStatusCallback(statusPath, ev, result):
456 log.debug("hotplugStatusCallback %s.", statusPath)
458 status = xstransact.Read(statusPath)
460 if status is not None:
461 if status == HOTPLUG_STATUS_ERROR:
462 result['status'] = Error
463 elif status == HOTPLUG_STATUS_BUSY:
464 result['status'] = Busy
465 else:
466 result['status'] = Connected
467 else:
468 return 1
470 log.debug("hotplugStatusCallback %d.", result['status'])
472 ev.set()
473 return 0