kaf24@6064: #============================================================================ kaf24@6064: # This library is free software; you can redistribute it and/or kaf24@6064: # modify it under the terms of version 2.1 of the GNU Lesser General Public kaf24@6064: # License as published by the Free Software Foundation. kaf24@6064: # kaf24@6064: # This library is distributed in the hope that it will be useful, kaf24@6064: # but WITHOUT ANY WARRANTY; without even the implied warranty of kaf24@6064: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU kaf24@6064: # Lesser General Public License for more details. kaf24@6064: # kaf24@6064: # You should have received a copy of the GNU Lesser General Public kaf24@6064: # License along with this library; if not, write to the Free Software kaf24@6064: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA kaf24@6064: #============================================================================ kaf24@6064: # Copyright (C) 2004, 2005 Mike Wray kaf24@6064: #============================================================================ kaf24@6064: mjw@1719: """General support for controllers, which handle devices mjw@1719: for a domain. mjw@1719: """ mjw@1654: mjw@4580: from xen.xend.XendError import XendError cl349@5388: from xen.xend.xenstore import DBVar mjw@1623: mjw@2397: DEBUG = 0 mjw@1677: mjw@4580: class DevControllerTable: mjw@4668: """Table of device controller classes, indexed by type name. mjw@2332: """ mjw@2332: mjw@2332: def __init__(self): mjw@4668: self.controllerClasses = {} mjw@4580: mjw@4668: def getDevControllerClass(self, type): mjw@4668: return self.controllerClasses.get(type) mjw@4580: cl349@5346: def addDevControllerClass(self, cls): cl349@5346: self.controllerClasses[cls.getType()] = cls mjw@2332: mjw@4668: def delDevControllerClass(self, type): mjw@4668: if type in self.controllerClasses: mjw@4668: del self.controllerClasses[type] mjw@2332: mjw@4580: def createDevController(self, type, vm, recreate=False): cl349@5346: cls = self.getDevControllerClass(type) cl349@5346: if not cls: kaf24@6131: raise XendError("unknown device type: " + str(type)) cl349@5346: return cls.createDevController(vm, recreate=recreate) mjw@2332: mjw@4580: def getDevControllerTable(): mjw@4668: """Singleton constructor for the controller table. mjw@4668: """ mjw@4580: global devControllerTable mjw@4580: try: mjw@4580: devControllerTable mjw@4580: except: mjw@4580: devControllerTable = DevControllerTable() mjw@4580: return devControllerTable mjw@1623: cl349@5346: def addDevControllerClass(name, cls): mjw@4668: """Add a device controller class to the controller table. mjw@4668: """ cl349@5346: cls.type = name cl349@5346: getDevControllerTable().addDevControllerClass(cls) mjw@4580: ewan@6771: ewan@6771: def isDevControllerClass(name): ewan@6771: """@return True if a device controller class has been registered with ewan@6771: the controller table under the given name.""" ewan@6771: return name in getDevControllerTable().controllerClasses ewan@6771: ewan@6771: mjw@4580: def createDevController(name, vm, recreate=False): mjw@4580: return getDevControllerTable().createDevController(name, vm, recreate=recreate) mjw@4580: mjw@4580: class DevController: mjw@4580: """Abstract class for a device controller attached to a domain. mjw@4580: A device controller manages all the devices of a given type for a domain. mjw@4580: There is exactly one device controller for each device type for mjw@4580: a domain. mjw@2332: mjw@4580: """ mjw@4580: cl349@5388: # State: cl349@5388: # controller/ : for controller cl349@5388: # device// : for each device cl349@5388: cl349@5346: def createDevController(cls, vm, recreate=False): mjw@4668: """Class method to create a dev controller. mjw@4668: """ cl349@5346: ctrl = cls(vm, recreate=recreate) mjw@4668: ctrl.initController(recreate=recreate) cl349@5388: ctrl.exportToDB() mjw@4668: return ctrl mjw@4668: mjw@4668: createDevController = classmethod(createDevController) mjw@4668: cl349@5346: def getType(cls): cl349@5346: return cls.type mjw@4668: mjw@4668: getType = classmethod(getType) mjw@4668: cl349@5388: __exports__ = [ cl349@5388: DBVar('type', 'str'), cl349@5388: DBVar('destroyed', 'bool'), cl349@5388: ] cl349@5388: cl349@5346: # Set when registered. cl349@5346: type = None cl349@5346: mjw@4668: def __init__(self, vm, recreate=False): mjw@4580: self.destroyed = False mjw@4580: self.vm = vm cl349@5388: self.db = self.getDB() mjw@4580: self.deviceId = 0 mjw@4580: self.devices = {} mjw@4580: self.device_order = [] mjw@2332: cl349@5388: def getDB(self): cl349@5388: """Get the db node to use for a controller. cl349@5388: """ cl349@5388: return self.vm.db.addChild("/controller/%s" % self.getType()) cl349@5388: cl349@5388: def getDevDB(self, id): cl349@5388: """Get the db node to use for a device. cl349@5388: """ cl349@5388: return self.vm.db.addChild("/device/%s/%s" % (self.getType(), id)) cl349@5388: cl349@5388: def exportToDB(self, save=False): cl349@5388: self.db.exportToDB(self, fields=self.__exports__, save=save) cl349@5388: cl349@5388: def importFromDB(self): cl349@5388: self.db.importFromDB(self, fields=self.__exports__) cl349@5388: mjw@4580: def getDevControllerType(self): mjw@4580: return self.dctype mjw@2332: mjw@4580: def getDomain(self): mjw@4580: return self.vm.getDomain() mjw@4580: mjw@4580: def getDomainName(self): mjw@4580: return self.vm.getName() mjw@2332: mjw@4580: def getDomainInfo(self): mjw@4580: return self.vm mjw@2332: mjw@4580: #---------------------------------------------------------------------------- mjw@4580: # Subclass interface. mjw@4580: # Subclasses should define the unimplemented methods.. mjw@4580: # Redefinitions must have the same arguments. mjw@2332: mjw@4580: def initController(self, recreate=False, reboot=False): mjw@4668: """Initialise the controller. Called when the controller is mjw@4668: first created, and again after the domain is rebooted (with reboot True). mjw@4668: If called with recreate True (and reboot False) the controller is being mjw@4668: recreated after a xend restart. mjw@4668: mjw@4668: As this can be a re-init (after reboot) any controller state should mjw@4668: be reset. For example the destroyed flag. mjw@4668: """ mjw@4580: self.destroyed = False mjw@4580: if reboot: mjw@4580: self.rebootDevices() mjw@4580: mjw@4580: def newDevice(self, id, config, recreate=False): mjw@4580: """Create a device with the given config. mjw@4580: Must be defined in subclass. mjw@4668: Called with recreate True when the device is being recreated after a mjw@4668: xend restart. mjw@4580: mjw@4580: @return device mjw@2332: """ mjw@2332: raise NotImplementedError() mjw@2332: mjw@4580: def createDevice(self, config, recreate=False, change=False): mjw@4668: """Create a device and attach to its front- and back-ends. mjw@4668: If recreate is true the device is being recreated after a xend restart. mjw@4668: If change is true the device is a change to an existing domain, mjw@4668: i.e. it is being added at runtime rather than when the domain is created. mjw@4668: """ cl349@5388: dev = self.newDevice(self.nextDeviceId(), config, recreate=recreate) cl349@5388: if self.vm.recreate: cl349@5388: dev.importFromDB() mjw@4580: dev.init(recreate=recreate) mjw@4580: self.addDevice(dev) cl349@5388: if not recreate: cl349@5388: dev.exportToDB() mjw@4580: dev.attach(recreate=recreate, change=change) cl349@5388: dev.exportToDB() mjw@4580: kaf24@6131: return dev kaf24@6131: mjw@4580: def configureDevice(self, id, config, change=False): mjw@4580: """Reconfigure an existing device. mjw@4580: May be defined in subclass.""" cl349@5338: dev = self.getDevice(id, error=True) mjw@4580: dev.configure(config, change=change) mjw@2267: mjw@4580: def destroyDevice(self, id, change=False, reboot=False): mjw@4580: """Destroy a device. mjw@4668: May be defined in subclass. mjw@4668: mjw@4668: If reboot is true the device is being destroyed for a domain reboot. mjw@4668: mjw@4668: The device is not deleted, since it may be recreated later. mjw@4668: """ cl349@5338: dev = self.getDevice(id, error=True) mjw@4580: dev.destroy(change=change, reboot=reboot) mjw@4580: return dev mjw@4580: mjw@4580: def deleteDevice(self, id, change=True): mjw@4668: """Destroy a device and delete it. mjw@4668: Normally called to remove a device from a domain at runtime. mjw@4668: """ mjw@4580: dev = self.destroyDevice(id, change=change) mjw@4580: self.removeDevice(dev) mjw@4580: mjw@4580: def destroyController(self, reboot=False): mjw@4580: """Destroy all devices and clean up. mjw@4668: May be defined in subclass. mjw@4668: If reboot is true the controller is being destroyed for a domain reboot. mjw@4668: Called at domain shutdown. mjw@4668: """ mjw@4580: self.destroyed = True mjw@4580: self.destroyDevices(reboot=reboot) mjw@4580: mjw@4580: #---------------------------------------------------------------------------- mjw@4580: mjw@4580: def isDestroyed(self): mjw@4580: return self.destroyed mjw@4580: cl349@5338: def getDevice(self, id, error=False): kaf24@6132: dev = self.devices.get(int(id)) cl349@5338: if error and not dev: kaf24@6131: raise XendError("invalid device id: " + str(id)) cl349@5338: return dev mjw@4580: mjw@4580: def getDeviceIds(self): mjw@4580: return [ dev.getId() for dev in self.device_order ] mjw@4580: mjw@4580: def getDevices(self): mjw@4580: return self.device_order mjw@4580: mjw@4580: def getDeviceConfig(self, id): mjw@4580: return self.getDevice(id).getConfig() mjw@4580: mjw@4580: def getDeviceConfigs(self): mjw@4580: return [ dev.getConfig() for dev in self.device_order ] mjw@4580: mjw@4580: def getDeviceSxprs(self): mjw@4580: return [ dev.sxpr() for dev in self.device_order ] mjw@4580: mjw@4580: def addDevice(self, dev): mjw@4580: self.devices[dev.getId()] = dev mjw@4580: self.device_order.append(dev) mjw@4580: return dev mjw@4580: mjw@4580: def removeDevice(self, dev): mjw@4580: if dev.getId() in self.devices: mjw@4580: del self.devices[dev.getId()] mjw@4580: if dev in self.device_order: mjw@4580: self.device_order.remove(dev) mjw@4580: mjw@4580: def rebootDevices(self): mjw@4580: for dev in self.getDevices(): mjw@4580: dev.reboot() mjw@4580: mjw@4580: def destroyDevices(self, reboot=False): mjw@4580: """Destroy all devices. mjw@4580: """ mjw@4580: for dev in self.getDevices(): mjw@4580: dev.destroy(reboot=reboot) mjw@2225: mjw@4580: def getMaxDeviceId(self): mjw@4580: maxid = 0 mjw@4580: for id in self.devices: mjw@4580: if id > maxid: mjw@4580: maxid = id mjw@4580: return maxid mjw@4580: mjw@4580: def nextDeviceId(self): mjw@4580: id = self.deviceId mjw@4580: self.deviceId += 1 mjw@4580: return id mjw@4580: mjw@4580: def getDeviceCount(self): mjw@4580: return len(self.devices) mjw@4580: mjw@4580: class Dev: mjw@4580: """Abstract class for a device attached to a device controller. mjw@2332: mjw@4580: @ivar id: identifier mjw@4580: @type id: int mjw@4580: @ivar controller: device controller mjw@4580: @type controller: DevController mjw@4580: """ mjw@4580: cl349@5388: # ./status : need 2: actual and requested? cl349@5388: # down-down: initial. cl349@5388: # up-up: fully up. cl349@5388: # down-up: down requested, still up. Watch front and back, when both cl349@5388: # down go to down-down. But what if one (or both) is not connected? cl349@5388: # Still have front/back trees with status? Watch front/status, back/status? cl349@5388: # up-down: up requested, still down. cl349@5388: # Back-end watches ./status, front/status cl349@5388: # Front-end watches ./status, back/status cl349@5388: # i.e. each watches the other 2. cl349@5388: # Each is status/request status/actual? cl349@5388: # cl349@5388: # backend? cl349@5388: # frontend? cl349@5388: cl349@5388: __exports__ = [ cl349@5388: DBVar('id', ty='int'), cl349@5388: DBVar('type', ty='str'), cl349@5388: DBVar('config', ty='sxpr'), cl349@5388: DBVar('destroyed', ty='bool'), cl349@5388: ] cl349@5388: mjw@4580: def __init__(self, controller, id, config, recreate=False): mjw@4580: self.controller = controller mjw@4580: self.id = id mjw@4580: self.config = config mjw@4580: self.destroyed = False cl349@5388: self.type = self.getType() cl349@5388: cl349@5388: self.db = controller.getDevDB(id) cl349@5388: cl349@5388: def exportToDB(self, save=False): cl349@5388: self.db.exportToDB(self, fields=self.__exports__, save=save) cl349@5388: cl349@5388: def importFromDB(self): cl349@5388: self.db.importFromDB(self, fields=self.__exports__) mjw@4580: mjw@4580: def getDomain(self): mjw@4580: return self.controller.getDomain() mjw@4580: mjw@4580: def getDomainName(self): mjw@4580: return self.controller.getDomainName() mjw@4580: mjw@4580: def getDomainInfo(self): mjw@4580: return self.controller.getDomainInfo() mjw@4580: mjw@4580: def getController(self): mjw@4580: return self.controller mjw@4580: mjw@4580: def getType(self): mjw@4580: return self.controller.getType() mjw@2332: mjw@4580: def getId(self): mjw@4580: return self.id mjw@4580: mjw@4580: def getConfig(self): mjw@4580: return self.config mjw@4580: mjw@4580: def isDestroyed(self): mjw@4580: return self.destroyed mjw@4580: mjw@4580: #---------------------------------------------------------------------------- mjw@4580: # Subclass interface. mjw@4580: # Define methods in subclass as needed. mjw@4580: # Redefinitions must have the same arguments. mjw@4580: mjw@4580: def init(self, recreate=False, reboot=False): mjw@4580: """Initialization. Called on initial create (when reboot is False) mjw@4580: and on reboot (when reboot is True). When xend is restarting is mjw@4580: called with recreate True. Define in subclass if needed. mjw@4668: mjw@4668: Device instance variables must be defined in the class constructor, mjw@4668: but given null or default values. The real values should be initialised mjw@4668: in this method. This allows devices to be re-initialised. mjw@4668: mjw@4668: Since this can be called to re-initialise a device any state flags mjw@4668: should be reset. mjw@4580: """ mjw@4580: self.destroyed = False mjw@2332: mjw@4580: def attach(self, recreate=False, change=False): mjw@4580: """Attach the device to its front and back ends. mjw@4580: Define in subclass if needed. mjw@4580: """ mjw@4580: pass mjw@4580: mjw@4580: def reboot(self): mjw@4668: """Reconnect the device when the domain is rebooted. mjw@4580: """ mjw@4580: self.init(reboot=True) mjw@4580: self.attach() mjw@2332: mjw@4580: def sxpr(self): mjw@4580: """Get the s-expression for the deivice. mjw@4580: Implement in a subclass if needed. mjw@2332: mjw@4580: @return: sxpr mjw@4580: """ mjw@4580: return self.getConfig() mjw@4580: mjw@4580: def configure(self, config, change=False): mjw@4580: """Reconfigure the device. mjw@4580: mjw@4580: Implement in subclass. mjw@2332: """ mjw@2332: raise NotImplementedError() mjw@2332: mjw@4580: def refresh(self): mjw@4580: """Refresh the device.. mjw@4580: Default no-op. Define in subclass if needed. mjw@2520: """ mjw@4580: pass mjw@2185: mjw@4580: def destroy(self, change=False, reboot=False): mjw@4580: """Destroy the device. mjw@4580: If change is True notify destruction (runtime change). mjw@4580: If reboot is True the device is being destroyed for a reboot. mjw@4580: Redefine in subclass if needed. mjw@4668: mjw@4668: Called at domain shutdown and when a device is deleted from mjw@4668: a running domain (with change True). mjw@4580: """ mjw@4580: self.destroyed = True mjw@4580: pass mjw@1654: mjw@4580: #----------------------------------------------------------------------------