ia64/xen-unstable

view tools/python/xen/xend/server/DevController.py @ 10039:36e0159c001b

This patch adds a boolean parameter 'network' to the save method of the
XendCheckpoint class to differentiate between network suspend/resume
(network=True) and local suspend/resume (network=False).
Instead of passing the 'live' parameter to the migration methods, this
'network' parameter is passed now. This ends up being merely a renaming
of the parameter.
A check with the xm-test suite against this change has resulted in no
additional errors.

Signed-off-by: Stefan Berger <stefanb@us.ibm.com>
author kaf24@firebug.cl.cam.ac.uk
date Wed May 10 16:52:55 2006 +0100 (2006-05-10)
parents 1fe63743a147
children f692a0a476c5
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)
345 def deviceIDs(self, transaction = None):
346 """@return The IDs of each of the devices currently configured for
347 this instance's deviceClass.
348 """
349 fe = self.frontendRoot()
350 if transaction:
351 return map(lambda x: int(x.split('/')[-1]), transaction.list(fe))
352 else:
353 return map(int, xstransact.List(fe))
356 ## private:
358 def addStoreEntries(self, config, devid, backDetails, frontDetails):
359 """Add to backDetails and frontDetails the entries to be written in
360 the store to trigger creation of a device. The backend domain ID is
361 taken from the given config, paths for frontend and backend are
362 computed, and these are added to the backDetails and frontDetails
363 dictionaries for writing to the store, including references from
364 frontend to backend and vice versa.
366 @return A pair of (backpath, frontpath). backDetails and frontDetails
367 will have been updated appropriately, also.
369 @param config The configuration of the device, as given to
370 {@link #createDevice}.
371 @param devid As returned by {@link #getDeviceDetails}.
372 @param backDetails As returned by {@link #getDeviceDetails}.
373 @param frontDetails As returned by {@link #getDeviceDetails}.
374 """
376 import xen.xend.XendDomain
377 xd = xen.xend.XendDomain.instance()
379 backdom_name = sxp.child_value(config, 'backend')
380 if backdom_name:
381 backdom = xd.domain_lookup_by_name_or_id_nr(backdom_name)
382 else:
383 backdom = xd.privilegedDomain()
385 if not backdom:
386 raise VmError("Cannot configure device for unknown backend %s" %
387 backdom_name)
389 frontpath = self.frontendPath(devid)
390 backpath = self.backendPath(backdom, devid)
392 frontDetails.update({
393 'backend' : backpath,
394 'backend-id' : "%i" % backdom.getDomid(),
395 'state' : str(xenbusState['Initialising'])
396 })
399 backDetails.update({
400 'domain' : self.vm.getName(),
401 'frontend' : frontpath,
402 'frontend-id' : "%i" % self.vm.getDomid(),
403 'state' : str(xenbusState['Initialising'])
404 })
406 return (backpath, frontpath)
409 def waitForBackend(self, devid):
411 frontpath = self.frontendPath(devid)
412 backpath = xstransact.Read(frontpath, "backend")
414 if backpath:
415 statusPath = backpath + '/' + HOTPLUG_STATUS_NODE
416 ev = Event()
417 result = { 'status': Timeout }
419 xswatch(statusPath, hotplugStatusCallback, ev, result)
421 ev.wait(DEVICE_CREATE_TIMEOUT)
422 return result['status']
423 else:
424 return Missing
427 def backendPath(self, backdom, devid):
428 """@param backdom [XendDomainInfo] The backend domain info."""
430 return "%s/backend/%s/%s/%d" % (backdom.getDomainPath(),
431 self.deviceClass,
432 self.vm.getDomid(), devid)
435 def frontendPath(self, devid):
436 return "%s/%d" % (self.frontendRoot(), devid)
439 def frontendRoot(self):
440 return "%s/device/%s" % (self.vm.getDomainPath(), self.deviceClass)
443 def frontendMiscPath(self):
444 return "%s/device-misc/%s" % (self.vm.getDomainPath(),
445 self.deviceClass)
448 def hotplugStatusCallback(statusPath, ev, result):
449 log.debug("hotplugStatusCallback %s.", statusPath)
451 status = xstransact.Read(statusPath)
453 if status is not None:
454 if status == HOTPLUG_STATUS_ERROR:
455 result['status'] = Error
456 elif status == HOTPLUG_STATUS_BUSY:
457 result['status'] = Busy
458 else:
459 result['status'] = Connected
460 else:
461 return 1
463 log.debug("hotplugStatusCallback %d.", result['status'])
465 ev.set()
466 return 0