ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 6912:aa8943e9b705

Fix setStoreChannel(None) - fixes cleanup on destroy.
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Fri Sep 16 18:05:03 2005 +0000 (2005-09-16)
parents fd19e760932d
children ffbc98d735bd
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 #============================================================================
18 """Representation of a single domain.
19 Includes support for domain construction, using
20 open-ended configurations.
22 Author: Mike Wray <mike.wray@hp.com>
24 """
26 import string, re
27 import os
28 import time
29 import threading
30 import errno
32 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
33 from xen.util.ip import check_subnet, get_current_ipgw
34 from xen.util.blkif import blkdev_uname_to_file
36 from xen.xend.server import controller
37 from xen.xend.server import SrvDaemon; xend = SrvDaemon.instance()
38 from xen.xend.server.channel import EventChannel
39 from xen.util.blkif import blkdev_name_to_number, expand_dev_name
41 from xen.xend import sxp
42 from xen.xend import Blkctl
43 from xen.xend.PrettyPrint import prettyprintstring
44 from xen.xend.XendBootloader import bootloader
45 from xen.xend.XendLogging import log
46 from xen.xend.XendError import XendError, VmError
47 from xen.xend.XendRoot import get_component
49 from xen.xend.uuid import getUuid
50 from xen.xend.xenstore import DBVar, XenNode, DBMap
51 from xen.xend.xenstore.xstransact import xstransact
52 from xen.xend.xenstore.xsutil import IntroduceDomain
54 """Shutdown code for poweroff."""
55 DOMAIN_POWEROFF = 0
57 """Shutdown code for reboot."""
58 DOMAIN_REBOOT = 1
60 """Shutdown code for suspend."""
61 DOMAIN_SUSPEND = 2
63 """Shutdown code for crash."""
64 DOMAIN_CRASH = 3
66 """Map shutdown codes to strings."""
67 shutdown_reasons = {
68 DOMAIN_POWEROFF: "poweroff",
69 DOMAIN_REBOOT : "reboot",
70 DOMAIN_SUSPEND : "suspend",
71 DOMAIN_CRASH : "crash",
72 }
74 RESTART_ALWAYS = 'always'
75 RESTART_ONREBOOT = 'onreboot'
76 RESTART_NEVER = 'never'
78 restart_modes = [
79 RESTART_ALWAYS,
80 RESTART_ONREBOOT,
81 RESTART_NEVER,
82 ]
84 STATE_RESTART_PENDING = 'pending'
85 STATE_RESTART_BOOTING = 'booting'
87 STATE_VM_OK = "ok"
88 STATE_VM_TERMINATED = "terminated"
89 STATE_VM_SUSPENDED = "suspended"
91 """Flag for a block device backend domain."""
92 SIF_BLK_BE_DOMAIN = (1<<4)
94 """Flag for a net device backend domain."""
95 SIF_NET_BE_DOMAIN = (1<<5)
97 """Flag for a TPM device backend domain."""
98 SIF_TPM_BE_DOMAIN = (1<<7)
101 def domain_exists(name):
102 # See comment in XendDomain constructor.
103 xd = get_component('xen.xend.XendDomain')
104 return xd.domain_lookup_by_name(name)
106 def shutdown_reason(code):
107 """Get a shutdown reason from a code.
109 @param code: shutdown code
110 @type code: int
111 @return: shutdown reason
112 @rtype: string
113 """
114 return shutdown_reasons.get(code, "?")
116 def dom_get(dom):
117 """Get info from xen for an existing domain.
119 @param dom: domain id
120 @return: info or None
121 """
122 try:
123 domlist = xc.domain_getinfo(dom, 1)
124 if domlist and dom == domlist[0]['dom']:
125 return domlist[0]
126 except Exception, err:
127 # ignore missing domain
128 log.exception("domain_getinfo(%d) failed, ignoring", dom)
129 return None
131 class XendDomainInfo:
132 """Virtual machine object."""
134 """Minimum time between domain restarts in seconds.
135 """
136 MINIMUM_RESTART_TIME = 20
138 def create(cls, parentdb, config):
139 """Create a VM from a configuration.
141 @param parentdb: parent db
142 @param config configuration
143 @raise: VmError for invalid configuration
144 """
145 uuid = getUuid()
146 db = parentdb.addChild("%s/xend" % uuid)
147 path = parentdb.getPath()
148 vm = cls(uuid, path, db)
149 vm.construct(config)
150 vm.saveToDB(sync=True)
152 return vm
154 create = classmethod(create)
156 def recreate(cls, uuid, domid, db, info):
157 """Create the VM object for an existing domain.
159 @param db: domain db
160 @param info: domain info from xc
161 """
162 path = "/".join(db.getPath().split("/")[0:-2])
163 vm = cls(uuid, path, db)
164 vm.setDomid(domid)
165 vm.name, vm.start_time = vm.gatherVm(("name", str),
166 ("start-time", float))
167 try:
168 db.readDB()
169 except: pass
170 vm.importFromDB()
171 config = vm.config
172 log.debug('info=' + str(info))
173 log.debug('config=' + prettyprintstring(config))
175 vm.memory = info['mem_kb'] / 1024
176 vm.target = info['mem_kb'] * 1024
178 if config:
179 try:
180 vm.recreate = True
181 vm.construct(config)
182 finally:
183 vm.recreate = False
184 else:
185 vm.setName("Domain-%d" % domid)
187 vm.exportToDB(save=True)
188 return vm
190 recreate = classmethod(recreate)
192 def restore(cls, parentdb, config, uuid=None):
193 """Create a domain and a VM object to do a restore.
195 @param parentdb: parent db
196 @param config: domain configuration
197 @param uuid: uuid to use
198 """
199 if not uuid:
200 uuid = getUuid()
201 db = parentdb.addChild("%s/xend" % uuid)
202 path = parentdb.getPath()
203 vm = cls(uuid, path, db)
204 ssidref = int(sxp.child_value(config, 'ssidref'))
205 log.debug('restoring with ssidref='+str(ssidref))
206 id = xc.domain_create(ssidref = ssidref)
207 vm.setDomid(id)
208 vm.clear_shutdown()
209 try:
210 vm.restore = True
211 vm.construct(config)
212 finally:
213 vm.restore = False
214 vm.exportToDB(save=True, sync=True)
215 return vm
217 restore = classmethod(restore)
219 __exports__ = [
220 DBVar('config', ty='sxpr'),
221 DBVar('state', ty='str'),
222 DBVar('restart_mode', ty='str'),
223 DBVar('restart_state', ty='str'),
224 DBVar('restart_time', ty='float'),
225 DBVar('restart_count', ty='int'),
226 ]
228 def __init__(self, uuid, path, db):
229 self.uuid = uuid
230 self.path = path + "/" + uuid
232 self.db = db
234 self.recreate = 0
235 self.restore = 0
237 self.config = None
238 self.domid = None
239 self.cpu_weight = 1
240 self.start_time = None
241 self.name = None
242 self.memory = None
243 self.ssidref = None
244 self.image = None
246 self.target = None
248 self.store_channel = None
249 self.store_mfn = None
250 self.console_channel = None
251 self.console_mfn = None
252 self.controllers = {}
254 self.info = None
255 self.backend_flags = 0
256 self.netif_idx = 0
258 #todo: state: running, suspended
259 self.state = STATE_VM_OK
260 self.state_updated = threading.Condition()
261 self.shutdown_pending = None
263 #todo: set to migrate info if migrating
264 self.migrate = None
266 self.restart_mode = RESTART_ONREBOOT
267 self.restart_state = None
268 self.restart_time = None
269 self.restart_count = 0
271 self.vcpus = 1
272 self.bootloader = None
274 self.writeVm("uuid", self.uuid)
275 self.storeDom("vm", self.path)
277 def readVm(self, *args):
278 return xstransact.Read(self.path, *args)
280 def writeVm(self, *args):
281 return xstransact.Write(self.path, *args)
283 def removeVm(self, *args):
284 return xstransact.Remove(self.path, *args)
286 def gatherVm(self, *args):
287 return xstransact.Gather(self.path, *args)
289 def storeVm(self, *args):
290 return xstransact.Store(self.path, *args)
292 def readDom(self, *args):
293 return xstransact.Read(self.path, *args)
295 def writeDom(self, *args):
296 return xstransact.Write(self.path, *args)
298 def removeDom(self, *args):
299 return xstransact.Remove(self.path, *args)
301 def gatherDom(self, *args):
302 return xstransact.Gather(self.path, *args)
304 def storeDom(self, *args):
305 return xstransact.Store(self.path, *args)
307 def setDB(self, db):
308 self.db = db
310 def saveToDB(self, save=False, sync=False):
311 self.db.saveDB(save=save, sync=sync)
313 def exportToDB(self, save=False, sync=False):
314 self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)
316 def importFromDB(self):
317 self.db.importFromDB(self, fields=self.__exports__)
318 self.store_channel = self.eventChannel("store/port")
320 def setDomid(self, domid):
321 """Set the domain id.
323 @param dom: domain id
324 """
325 self.domid = domid
326 self.storeDom("domid", self.domid)
328 def getDomain(self):
329 return self.domid
331 def setName(self, name):
332 self.name = name
333 self.storeVm("name", name)
335 def getName(self):
336 return self.name
338 def setStoreRef(self, ref):
339 self.store_mfn = ref
340 self.storeDom("store/ring-ref", ref)
342 def setStoreChannel(self, channel):
343 if self.store_channel and self.store_channel != channel:
344 self.store_channel.close()
345 self.store_channel = channel
346 if channel:
347 port = channel.port1
348 else:
349 port = None
350 self.storeDom("store/port", None)
352 def setConsoleRef(self, ref):
353 self.console_mfn = ref
354 self.storeDom("console/ring-ref", ref)
356 def setMemoryTarget(self, target):
357 self.memory_target = target
358 self.storeDom("memory/target", target)
360 def update(self, info=None):
361 """Update with info from xc.domain_getinfo().
362 """
363 if info:
364 self.info = info
365 else:
366 di = dom_get(self.domid)
367 if not di:
368 return
369 self.info = di
370 self.memory = self.info['mem_kb'] / 1024
371 self.ssidref = self.info['ssidref']
373 def state_set(self, state):
374 self.state_updated.acquire()
375 if self.state != state:
376 self.state = state
377 self.state_updated.notifyAll()
378 self.state_updated.release()
379 self.saveToDB()
381 def state_wait(self, state):
382 self.state_updated.acquire()
383 while self.state != state:
384 self.state_updated.wait()
385 self.state_updated.release()
387 def __str__(self):
388 s = "<domain"
389 s += " id=" + str(self.domid)
390 s += " name=" + self.name
391 s += " memory=" + str(self.memory)
392 s += " ssidref=" + str(self.ssidref)
393 s += ">"
394 return s
396 __repr__ = __str__
398 def getDeviceController(self, type, error=True):
399 ctrl = self.controllers.get(type)
400 if not ctrl and error:
401 raise XendError("invalid device type:" + type)
402 return ctrl
404 def findDeviceController(self, type):
405 return (self.getDeviceController(type, error=False)
406 or self.createDeviceController(type))
408 def createDeviceController(self, type):
409 ctrl = controller.createDevController(type, self, recreate=self.recreate)
410 self.controllers[type] = ctrl
411 return ctrl
413 def createDevice(self, type, devconfig, change=False):
414 if self.recreate:
415 return
416 if type == 'vbd':
417 typedev = sxp.child_value(devconfig, 'dev')
418 if re.match('^ioemu:', typedev):
419 return;
421 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
423 devnum = blkdev_name_to_number(sxp.child_value(devconfig, 'dev'))
425 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
426 self.uuid, devnum)
427 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
429 front = { 'backend' : backpath,
430 'backend-id' : "%i" % backdom.domid,
431 'virtual-device' : "%i" % devnum }
432 xstransact.Write(frontpath, front)
434 (type, params) = string.split(sxp.child_value(devconfig,
435 'uname'), ':', 1)
436 readonly = sxp.child_value(devconfig, 'mode', 'r')
437 back = { 'type' : type,
438 'params' : params,
439 'frontend' : frontpath,
440 'frontend-id' : "%i" % self.domid }
441 if readonly == 'r':
442 back['read-only'] = "" # existence indicates read-only
443 xstransact.Write(backpath, back)
445 return
447 if type == 'vif':
448 from xen.xend import XendRoot
449 xroot = XendRoot.instance()
451 def _get_config_ipaddr(config):
452 val = []
453 for ipaddr in sxp.children(config, elt='ip'):
454 val.append(sxp.child0(ipaddr))
455 return val
457 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
459 devnum = self.netif_idx
460 self.netif_idx += 1
462 script = sxp.child_value(devconfig, 'script',
463 xroot.get_vif_script())
464 script = os.path.join(xroot.network_script_dir, script)
465 bridge = sxp.child_value(devconfig, 'bridge',
466 xroot.get_vif_bridge())
467 mac = sxp.child_value(devconfig, 'mac')
468 ipaddr = _get_config_ipaddr(devconfig)
470 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
471 self.uuid, devnum)
472 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
474 front = { 'backend' : backpath,
475 'backend-id' : "%i" % backdom.domid,
476 'handle' : "%i" % devnum,
477 'mac' : mac }
478 xstransact.Write(frontpath, front)
480 back = { 'script' : script,
481 'domain' : self.name,
482 'mac' : mac,
483 'bridge' : bridge,
484 'frontend' : frontpath,
485 'frontend-id' : "%i" % self.domid,
486 'handle' : "%i" % devnum }
487 if ipaddr:
488 back['ip'] = ' '.join(ipaddr)
489 xstransact.Write(backpath, back)
491 return
493 if type == 'vtpm':
494 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
496 devnum = int(sxp.child_value(devconfig, 'instance', '0'))
497 log.error("The domain has a TPM with instance %d." % devnum)
499 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
500 self.uuid, devnum)
501 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
503 front = { 'backend' : backpath,
504 'backend-id' : "%i" % backdom.domid,
505 'handle' : "%i" % devnum }
506 xstransact.Write(frontpath, front)
508 back = { 'instance' : "%i" % devnum,
509 'frontend' : frontpath,
510 'frontend-id' : "%i" % self.domid }
511 xstransact.Write(backpath, back)
513 return
515 ctrl = self.findDeviceController(type)
516 return ctrl.createDevice(devconfig, recreate=self.recreate,
517 change=change)
519 def configureDevice(self, type, id, devconfig):
520 ctrl = self.getDeviceController(type)
521 return ctrl.configureDevice(id, devconfig)
523 def destroyDevice(self, type, id, change=False, reboot=False):
524 ctrl = self.getDeviceController(type)
525 return ctrl.destroyDevice(id, change=change, reboot=reboot)
527 def deleteDevice(self, type, id):
528 ctrl = self.getDeviceController(type)
529 return ctrl.deleteDevice(id)
531 def getDevice(self, type, id, error=True):
532 ctrl = self.getDeviceController(type)
533 return ctrl.getDevice(id, error=error)
535 def getDeviceIds(self, type):
536 ctrl = self.getDeviceController(type)
537 return ctrl.getDeviceIds()
539 def getDeviceSxprs(self, type):
540 ctrl = self.getDeviceController(type)
541 return ctrl.getDeviceSxprs()
543 def sxpr(self):
544 sxpr = ['domain',
545 ['domid', self.domid],
546 ['name', self.name],
547 ['memory', self.memory],
548 ['ssidref', self.ssidref],
549 ['target', self.target] ]
550 if self.uuid:
551 sxpr.append(['uuid', self.uuid])
552 if self.info:
553 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
554 run = (self.info['running'] and 'r') or '-'
555 block = (self.info['blocked'] and 'b') or '-'
556 pause = (self.info['paused'] and 'p') or '-'
557 shut = (self.info['shutdown'] and 's') or '-'
558 crash = (self.info['crashed'] and 'c') or '-'
559 state = run + block + pause + shut + crash
560 sxpr.append(['state', state])
561 if self.info['shutdown']:
562 reason = shutdown_reason(self.info['shutdown_reason'])
563 sxpr.append(['shutdown_reason', reason])
564 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
565 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
566 sxpr.append(['vcpus', self.info['vcpus']])
567 sxpr.append(['cpumap', self.info['cpumap']])
568 # build a string, using '|' to seperate items, show only up
569 # to number of vcpus in domain, and trim the trailing '|'
570 sxpr.append(['vcpu_to_cpu', ''.join(map(lambda x: str(x)+'|',
571 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))[:-1]])
573 if self.start_time:
574 up_time = time.time() - self.start_time
575 sxpr.append(['up_time', str(up_time) ])
576 sxpr.append(['start_time', str(self.start_time) ])
578 if self.store_channel:
579 sxpr.append(self.store_channel.sxpr())
580 if self.store_mfn:
581 sxpr.append(['store_mfn', self.store_mfn])
582 if self.console_channel:
583 sxpr.append(['console_channel', self.console_channel.sxpr()])
584 if self.console_mfn:
585 sxpr.append(['console_mfn', self.console_mfn])
586 # already in (devices)
587 # console = self.getConsole()
588 # if console:
589 # sxpr.append(console.sxpr())
591 if self.restart_count:
592 sxpr.append(['restart_count', self.restart_count])
593 if self.restart_state:
594 sxpr.append(['restart_state', self.restart_state])
595 if self.restart_time:
596 sxpr.append(['restart_time', str(self.restart_time)])
598 devs = self.sxpr_devices()
599 if devs:
600 sxpr.append(devs)
601 if self.config:
602 sxpr.append(['config', self.config])
603 return sxpr
605 def sxpr_devices(self):
606 sxpr = []
607 for ty in self.controllers.keys():
608 devs = self.getDeviceSxprs(ty)
609 sxpr += devs
610 if sxpr:
611 sxpr.insert(0, 'devices')
612 else:
613 sxpr = None
614 return sxpr
616 def check_name(self, name):
617 """Check if a vm name is valid. Valid names contain alphabetic characters,
618 digits, or characters in '_-.:/+'.
619 The same name cannot be used for more than one vm at the same time.
621 @param name: name
622 @raise: VMerror if invalid
623 """
624 if self.recreate: return
625 if name is None or name == '':
626 raise VmError('missing vm name')
627 for c in name:
628 if c in string.digits: continue
629 if c in '_-.:/+': continue
630 if c in string.ascii_letters: continue
631 raise VmError('invalid vm name')
632 dominfo = domain_exists(name)
633 # When creating or rebooting, a domain with my name should not exist.
634 # When restoring, a domain with my name will exist, but it should have
635 # my domain id.
636 if not dominfo:
637 return
638 if dominfo.is_terminated():
639 return
640 if not self.domid or (dominfo.domid != self.domid):
641 raise VmError('vm name clash: ' + name)
643 def construct(self, config):
644 """Construct the vm instance from its configuration.
646 @param config: configuration
647 @raise: VmError on error
648 """
649 # todo - add support for scheduling params?
650 self.config = config
651 try:
652 # Initial domain create.
653 self.setName(sxp.child_value(config, 'name'))
654 self.check_name(self.name)
655 self.init_image()
656 self.configure_cpus(config)
657 self.init_domain()
658 self.register_domain()
660 # Create domain devices.
661 self.configure_backends()
662 self.configure_restart()
663 self.construct_image()
664 self.configure()
665 self.exportToDB(save=True)
666 except Exception, ex:
667 # Catch errors, cleanup and re-raise.
668 print 'Domain construction error:', ex
669 import traceback
670 traceback.print_exc()
671 self.destroy()
672 raise
674 def register_domain(self):
675 xd = get_component('xen.xend.XendDomain')
676 xd._add_domain(self)
677 self.exportToDB(save=True)
679 def configure_cpus(self, config):
680 try:
681 self.cpu_weight = float(sxp.child_value(config, 'cpu_weight', '1'))
682 except:
683 raise VmError('invalid cpu weight')
684 self.memory = int(sxp.child_value(config, 'memory'))
685 if self.memory is None:
686 raise VmError('missing memory size')
687 self.setMemoryTarget(self.memory * (1 << 20))
688 self.ssidref = int(sxp.child_value(config, 'ssidref'))
689 cpu = sxp.child_value(config, 'cpu')
690 if self.recreate and self.domid and cpu is not None and int(cpu) >= 0:
691 xc.domain_pincpu(self.domid, 0, 1<<int(cpu))
692 try:
693 image = sxp.child_value(self.config, 'image')
694 vcpus = sxp.child_value(image, 'vcpus')
695 if vcpus:
696 self.vcpus = int(vcpus)
697 except:
698 raise VmError('invalid vcpus value')
700 def configure_vcpus(self, vcpus):
701 d = {}
702 for v in range(0, vcpus):
703 d["cpu/%d/availability" % v] = "online"
704 self.writeVm(d)
706 def init_image(self):
707 """Create boot image handler for the domain.
708 """
709 image = sxp.child_value(self.config, 'image')
710 if image is None:
711 raise VmError('missing image')
712 self.image = ImageHandler.create(self, image)
714 def construct_image(self):
715 """Construct the boot image for the domain.
716 """
717 self.create_channel()
718 self.image.createImage()
719 self.exportToDB()
720 if self.store_channel and self.store_mfn >= 0:
721 IntroduceDomain(self.domid, self.store_mfn,
722 self.store_channel.port1, self.path)
723 # get the configured value of vcpus and update store
724 self.configure_vcpus(self.vcpus)
726 def delete(self):
727 """Delete the vm's db.
728 """
729 if dom_get(self.domid):
730 return
731 self.domid = None
732 self.saveToDB(sync=True)
733 try:
734 # Todo: eventually will have to wait for devices to signal
735 # destruction before can delete the db.
736 if self.db:
737 self.db.delete()
738 except Exception, ex:
739 log.warning("error in domain db delete: %s", ex)
740 pass
742 def destroy_domain(self):
743 """Destroy the vm's domain.
744 The domain will not finally go away unless all vm
745 devices have been released.
746 """
747 if self.domid is None:
748 return
749 try:
750 xc.domain_destroy(dom=self.domid)
751 except Exception, err:
752 log.exception("Domain destroy failed: %s", self.name)
754 def cleanup(self):
755 """Cleanup vm resources: release devices.
756 """
757 self.state = STATE_VM_TERMINATED
758 self.release_devices()
759 if self.store_channel:
760 self.setStoreChannel(None)
761 if self.console_channel:
762 # notify processes using this cosole?
763 try:
764 self.console_channel.close()
765 self.console_channel = None
766 except:
767 pass
768 if self.image:
769 try:
770 self.image.destroy()
771 self.image = None
772 except:
773 pass
775 def destroy(self):
776 """Clenup vm and destroy domain.
777 """
778 self.destroy_domain()
779 self.cleanup()
780 self.saveToDB()
781 return 0
783 def is_terminated(self):
784 """Check if a domain has been terminated.
785 """
786 return self.state == STATE_VM_TERMINATED
788 def release_devices(self):
789 """Release all vm devices.
790 """
791 reboot = self.restart_pending()
792 for ctrl in self.controllers.values():
793 if ctrl.isDestroyed(): continue
794 ctrl.destroyController(reboot=reboot)
795 t = xstransact("%s/device" % self.path)
796 for d in t.list("vbd"):
797 t.remove(d)
798 for d in t.list("vif"):
799 t.remove(d)
800 for d in t.list("vtpm"):
801 t.remove(d)
802 t.commit()
804 def show(self):
805 """Print virtual machine info.
806 """
807 print "[VM dom=%d name=%s memory=%d ssidref=%d" % (self.domid, self.name, self.memory, self.ssidref)
808 print "image:"
809 sxp.show(self.image)
810 print "]"
812 def init_domain(self):
813 """Initialize the domain memory.
814 """
815 if self.recreate:
816 return
817 if self.start_time is None:
818 self.start_time = time.time()
819 self.storeVm(("start-time", self.start_time))
820 try:
821 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
822 except:
823 raise VmError('invalid cpu')
824 id = self.image.initDomain(self.domid, self.memory, self.ssidref, cpu, self.cpu_weight)
825 log.debug('init_domain> Created domain=%d name=%s memory=%d',
826 id, self.name, self.memory)
827 self.setDomid(id)
829 def eventChannel(self, path=None):
830 """Create an event channel to the domain.
832 @param path under which port is stored in db
833 """
834 port = 0
835 if path:
836 try:
837 port = int(self.readDom(path))
838 except:
839 # if anything goes wrong, assume the port was not yet set
840 pass
841 ret = EventChannel.interdomain(0, self.domid, port1=port, port2=0)
842 self.storeDom(path, ret.port1)
843 return ret
845 def create_channel(self):
846 """Create the channels to the domain.
847 """
848 self.store_channel = self.eventChannel("store/port")
849 self.console_channel = self.eventChannel("console/port")
851 def create_configured_devices(self):
852 devices = sxp.children(self.config, 'device')
853 for d in devices:
854 dev_config = sxp.child0(d)
855 if dev_config is None:
856 raise VmError('invalid device')
857 dev_type = sxp.name(dev_config)
859 if not controller.isDevControllerClass(dev_type):
860 raise VmError('unknown device type: ' + dev_type)
862 self.createDevice(dev_type, dev_config)
865 def create_devices(self):
866 """Create the devices for a vm.
868 @raise: VmError for invalid devices
869 """
870 if self.rebooting():
871 for ctrl in self.controllers.values():
872 ctrl.initController(reboot=True)
873 else:
874 self.create_configured_devices()
875 self.image.createDeviceModel()
877 def device_create(self, dev_config):
878 """Create a new device.
880 @param dev_config: device configuration
881 """
882 dev_type = sxp.name(dev_config)
883 dev = self.createDevice(dev_type, dev_config, change=True)
884 self.config.append(['device', dev.getConfig()])
885 return dev.sxpr()
887 def device_configure(self, dev_config, id):
888 """Configure an existing device.
890 @param dev_config: device configuration
891 @param id: device id
892 """
893 type = sxp.name(dev_config)
894 dev = self.getDevice(type, id)
895 old_config = dev.getConfig()
896 new_config = dev.configure(dev_config, change=True)
897 # Patch new config into vm config.
898 new_full_config = ['device', new_config]
899 old_full_config = ['device', old_config]
900 old_index = self.config.index(old_full_config)
901 self.config[old_index] = new_full_config
902 return new_config
904 def device_refresh(self, type, id):
905 """Refresh a device.
907 @param type: device type
908 @param id: device id
909 """
910 dev = self.getDevice(type, id)
911 dev.refresh()
913 def device_delete(self, type, id):
914 """Destroy and remove a device.
916 @param type: device type
917 @param id: device id
918 """
919 dev = self.getDevice(type, id)
920 dev_config = dev.getConfig()
921 if dev_config:
922 self.config.remove(['device', dev_config])
923 self.deleteDevice(type, dev.getId())
925 def configure_restart(self):
926 """Configure the vm restart mode.
927 """
928 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
929 if r not in restart_modes:
930 raise VmError('invalid restart mode: ' + str(r))
931 self.restart_mode = r;
933 def restart_needed(self, reason):
934 """Determine if the vm needs to be restarted when shutdown
935 for the given reason.
937 @param reason: shutdown reason
938 @return True if needs restart, False otherwise
939 """
940 if self.restart_mode == RESTART_NEVER:
941 return False
942 if self.restart_mode == RESTART_ALWAYS:
943 return True
944 if self.restart_mode == RESTART_ONREBOOT:
945 return reason == 'reboot'
946 return False
948 def restart_cancel(self):
949 """Cancel a vm restart.
950 """
951 self.restart_state = None
953 def restarting(self):
954 """Put the vm into restart mode.
955 """
956 self.restart_state = STATE_RESTART_PENDING
958 def restart_pending(self):
959 """Test if the vm has a pending restart.
960 """
961 return self.restart_state == STATE_RESTART_PENDING
963 def rebooting(self):
964 return self.restart_state == STATE_RESTART_BOOTING
966 def restart_check(self):
967 """Check if domain restart is OK.
968 To prevent restart loops, raise an error if it is
969 less than MINIMUM_RESTART_TIME seconds since the last restart.
970 """
971 tnow = time.time()
972 if self.restart_time is not None:
973 tdelta = tnow - self.restart_time
974 if tdelta < self.MINIMUM_RESTART_TIME:
975 self.restart_cancel()
976 msg = 'VM %s restarting too fast' % self.name
977 log.error(msg)
978 raise VmError(msg)
979 self.restart_time = tnow
980 self.restart_count += 1
982 def restart(self):
983 """Restart the domain after it has exited.
984 Reuses the domain id
986 """
987 try:
988 self.clear_shutdown()
989 self.state = STATE_VM_OK
990 self.shutdown_pending = None
991 self.restart_check()
992 self.exportToDB()
993 self.restart_state = STATE_RESTART_BOOTING
994 self.configure_bootloader()
995 self.construct(self.config)
996 self.saveToDB()
997 finally:
998 self.restart_state = None
1000 def configure_bootloader(self):
1001 self.bootloader = sxp.child_value(self.config, "bootloader")
1002 if not self.bootloader:
1003 return
1004 # if we're restarting with a bootloader, we need to run it
1005 # FIXME: this assumes the disk is the first device and
1006 # that we're booting from the first disk
1007 blcfg = None
1008 # FIXME: this assumes that we want to use the first disk
1009 dev = sxp.child_value(self.config, "device")
1010 if dev:
1011 disk = sxp.child_value(dev, "uname")
1012 fn = blkdev_uname_to_file(disk)
1013 blcfg = bootloader(self.bootloader, fn, 1, self.vcpus)
1014 if blcfg is None:
1015 msg = "Had a bootloader specified, but can't find disk"
1016 log.error(msg)
1017 raise VmError(msg)
1018 self.config = sxp.merge(['vm', ['image', blcfg]], self.config)
1020 def configure_backends(self):
1021 """Set configuration flags if the vm is a backend for netif or blkif.
1022 Configure the backends to use for vbd and vif if specified.
1023 """
1024 for c in sxp.children(self.config, 'backend'):
1025 v = sxp.child0(c)
1026 name = sxp.name(v)
1027 if name == 'blkif':
1028 self.backend_flags |= SIF_BLK_BE_DOMAIN
1029 elif name == 'netif':
1030 self.backend_flags |= SIF_NET_BE_DOMAIN
1031 elif name == 'usbif':
1032 pass
1033 elif name == 'tpmif':
1034 self.backend_flags |= SIF_TPM_BE_DOMAIN
1035 else:
1036 raise VmError('invalid backend type:' + str(name))
1038 def configure(self):
1039 """Configure a vm.
1041 """
1042 self.configure_fields()
1043 self.create_devices()
1044 self.create_blkif()
1046 def create_blkif(self):
1047 """Create the block device interface (blkif) for the vm.
1048 The vm needs a blkif even if it doesn't have any disks
1049 at creation time, for example when it uses NFS root.
1051 """
1052 return
1053 blkif = self.getDeviceController("vbd", error=False)
1054 if not blkif:
1055 blkif = self.createDeviceController("vbd")
1056 backend = blkif.getBackend(0)
1057 backend.connect(recreate=self.recreate)
1059 def configure_fields(self):
1060 """Process the vm configuration fields using the registered handlers.
1061 """
1062 index = {}
1063 for field in sxp.children(self.config):
1064 field_name = sxp.name(field)
1065 field_index = index.get(field_name, 0)
1066 field_handler = config_handlers.get(field_name)
1067 # Ignore unknown fields. Warn?
1068 if field_handler:
1069 v = field_handler(self, self.config, field, field_index)
1070 else:
1071 log.warning("Unknown config field %s", field_name)
1072 index[field_name] = field_index + 1
1074 def vcpu_hotplug(self, vcpu, state):
1075 """Disable or enable VCPU in domain.
1076 """
1077 if vcpu > self.vcpus:
1078 log.error("Invalid VCPU %d" % vcpu)
1079 return
1080 if int(state) == 0:
1081 availability = "offline"
1082 else:
1083 availability = "online"
1084 self.storeVm("cpu/%d/availability" % vcpu, availability)
1086 def shutdown(self, reason):
1087 if not reason in shutdown_reasons.values():
1088 raise XendError('invalid reason:' + reason)
1089 self.storeVm("control/shutdown", reason)
1090 if not reason in ['suspend']:
1091 self.shutdown_pending = {'start':time.time(), 'reason':reason}
1093 def clear_shutdown(self):
1094 self.removeVm("control/shutdown")
1096 def send_sysrq(self, key=0):
1097 self.storeVm("control/sysrq", '%c' % key)
1099 def shutdown_time_left(self, timeout):
1100 if not self.shutdown_pending:
1101 return 0
1102 return timeout - (time.time() - self.shutdown_pending['start'])
1104 def dom0_init_store(self):
1105 if not self.store_channel:
1106 self.store_channel = self.eventChannel("store/port")
1107 if not self.store_channel:
1108 return
1109 ref = xc.init_store(self.store_channel.port2)
1110 if ref and ref >= 0:
1111 self.setStoreRef(ref)
1112 try:
1113 IntroduceDomain(self.domid, ref, self.store_channel.port1,
1114 self.path)
1115 except RuntimeError, ex:
1116 if ex.args[0] == errno.EISCONN:
1117 pass
1118 else:
1119 raise
1120 # get run-time value of vcpus and update store
1121 self.configure_vcpus(dom_get(self.domid)['vcpus'])
1124 def vm_field_ignore(_, _1, _2, _3):
1125 """Dummy config field handler used for fields with built-in handling.
1126 Matches the signature required by config_handlers.
1127 """
1128 pass
1131 def vm_field_maxmem(vm, _1, val, _2):
1132 """Config field handler to configure vm memory limit. Matches the
1133 signature required by config_handlers.
1134 """
1135 maxmem = sxp.child0(val)
1136 if maxmem is None:
1137 maxmem = vm.memory
1138 try:
1139 maxmem = int(maxmem)
1140 except:
1141 raise VmError("invalid maxmem: " + str(maxmem))
1142 xc.domain_setmaxmem(vm.domid, maxmem_kb = maxmem * 1024)
1145 #============================================================================
1146 # Register image handlers.
1148 from image import \
1149 addImageHandlerClass, \
1150 ImageHandler, \
1151 LinuxImageHandler, \
1152 VmxImageHandler
1154 addImageHandlerClass(LinuxImageHandler)
1155 addImageHandlerClass(VmxImageHandler)
1158 """Table of handlers for field configuration.
1160 field_name[String]: fn(vm, config, field, index) -> value(ignored)
1161 """
1162 config_handlers = {
1164 # Ignore the fields we already handle.
1166 'name': vm_field_ignore,
1167 'memory': vm_field_ignore,
1168 'ssidref': vm_field_ignore,
1169 'cpu': vm_field_ignore,
1170 'cpu_weight': vm_field_ignore,
1171 'restart': vm_field_ignore,
1172 'image': vm_field_ignore,
1173 'device': vm_field_ignore,
1174 'backend': vm_field_ignore,
1175 'vcpus': vm_field_ignore,
1176 'bootloader': vm_field_ignore,
1178 # Register other config handlers.
1179 'maxmem': vm_field_maxmem
1183 #============================================================================
1184 # Register device controllers and their device config types.
1186 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1187 controller.addDevControllerClass("vbd", blkif.BlkifController)
1188 controller.addDevControllerClass("vif", netif.NetifController)
1189 controller.addDevControllerClass("vtpm", tpmif.TPMifController)
1190 controller.addDevControllerClass("pci", pciif.PciController)
1191 controller.addDevControllerClass("usb", usbif.UsbifController)