ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 6880:fb72c78604c6

The dom destroy path is doing a dom_get on a non-existent domain to
ensure it is non-existent. This changes throws an explicit exception
when xc_domain_getinfo returns an error, instead of triggering an
internal python error. It then handles the exception in dom_get by
returning None, which callers already expect to mean failure.

Signed-off-by: Robert Read <robert@xensource.com>
author shand@ubuntu.eng.hq.xensource.com
date Wed Sep 14 14:36:29 2005 -0800 (2005-09-14)
parents 1a29e0f5c610
children bd477fcd32ab
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"
92 def domain_exists(name):
93 # See comment in XendDomain constructor.
94 xd = get_component('xen.xend.XendDomain')
95 return xd.domain_lookup_by_name(name)
97 def shutdown_reason(code):
98 """Get a shutdown reason from a code.
100 @param code: shutdown code
101 @type code: int
102 @return: shutdown reason
103 @rtype: string
104 """
105 return shutdown_reasons.get(code, "?")
107 def dom_get(dom):
108 """Get info from xen for an existing domain.
110 @param dom: domain id
111 @return: info or None
112 """
113 try:
114 domlist = xc.domain_getinfo(dom, 1)
115 if domlist and dom == domlist[0]['dom']:
116 return domlist[0]
117 except Exception, err:
118 # ignore missing domain
119 log.exception("domain_getinfo(%d) failed, ignoring", dom)
120 return None
122 class XendDomainInfo:
123 """Virtual machine object."""
125 """Minimum time between domain restarts in seconds.
126 """
127 MINIMUM_RESTART_TIME = 20
129 def create(cls, parentdb, config):
130 """Create a VM from a configuration.
132 @param parentdb: parent db
133 @param config configuration
134 @raise: VmError for invalid configuration
135 """
136 uuid = getUuid()
137 db = parentdb.addChild("%s/xend" % uuid)
138 path = parentdb.getPath()
139 vm = cls(uuid, path, db)
140 vm.construct(config)
141 vm.saveToDB(sync=True)
143 return vm
145 create = classmethod(create)
147 def recreate(cls, uuid, domid, db, info):
148 """Create the VM object for an existing domain.
150 @param db: domain db
151 @param info: domain info from xc
152 """
153 path = "/".join(db.getPath().split("/")[0:-2])
154 vm = cls(uuid, path, db)
155 vm.setDomid(domid)
156 vm.name, vm.start_time = vm.gatherVm(("name", str),
157 ("start-time", float))
158 try:
159 db.readDB()
160 except: pass
161 vm.importFromDB()
162 config = vm.config
163 log.debug('info=' + str(info))
164 log.debug('config=' + prettyprintstring(config))
166 vm.memory = info['mem_kb'] / 1024
167 vm.target = info['mem_kb'] * 1024
169 if config:
170 try:
171 vm.recreate = True
172 vm.construct(config)
173 finally:
174 vm.recreate = False
175 else:
176 vm.setName("Domain-%d" % domid)
178 vm.exportToDB(save=True)
179 return vm
181 recreate = classmethod(recreate)
183 def restore(cls, parentdb, config, uuid=None):
184 """Create a domain and a VM object to do a restore.
186 @param parentdb: parent db
187 @param config: domain configuration
188 @param uuid: uuid to use
189 """
190 if not uuid:
191 uuid = getUuid()
192 db = parentdb.addChild("%s/xend" % uuid)
193 path = parentdb.getPath()
194 vm = cls(uuid, path, db)
195 ssidref = int(sxp.child_value(config, 'ssidref'))
196 log.debug('restoring with ssidref='+str(ssidref))
197 id = xc.domain_create(ssidref = ssidref)
198 vm.setDomid(id)
199 vm.clear_shutdown()
200 try:
201 vm.restore = True
202 vm.construct(config)
203 finally:
204 vm.restore = False
205 vm.exportToDB(save=True, sync=True)
206 return vm
208 restore = classmethod(restore)
210 __exports__ = [
211 DBVar('config', ty='sxpr'),
212 DBVar('state', ty='str'),
213 DBVar('restart_mode', ty='str'),
214 DBVar('restart_state', ty='str'),
215 DBVar('restart_time', ty='float'),
216 DBVar('restart_count', ty='int'),
217 DBVar('device_model_pid', ty='int'),
218 ]
220 def __init__(self, uuid, path, db):
221 self.uuid = uuid
222 self.path = path + "/" + uuid
224 self.db = db
226 self.recreate = 0
227 self.restore = 0
229 self.config = None
230 self.domid = None
231 self.cpu_weight = 1
232 self.start_time = None
233 self.name = None
234 self.memory = None
235 self.ssidref = None
236 self.image = None
238 self.target = None
240 self.store_channel = None
241 self.store_mfn = None
242 self.console_channel = None
243 self.console_mfn = None
244 self.controllers = {}
246 self.info = None
247 self.blkif_backend = False
248 self.netif_backend = False
249 self.netif_idx = 0
250 self.tpmif_backend = False
252 #todo: state: running, suspended
253 self.state = STATE_VM_OK
254 self.state_updated = threading.Condition()
255 self.shutdown_pending = None
257 #todo: set to migrate info if migrating
258 self.migrate = None
260 self.restart_mode = RESTART_ONREBOOT
261 self.restart_state = None
262 self.restart_time = None
263 self.restart_count = 0
265 self.vcpus = 1
266 self.bootloader = None
267 self.device_model_pid = 0
269 self.writeVm("uuid", self.uuid)
270 self.storeDom("vm", self.path)
272 def readVm(self, *args):
273 return xstransact.Read(self.path, *args)
275 def writeVm(self, *args):
276 return xstransact.Write(self.path, *args)
278 def removeVm(self, *args):
279 return xstransact.Remove(self.path, *args)
281 def gatherVm(self, *args):
282 return xstransact.Gather(self.path, *args)
284 def storeVm(self, *args):
285 return xstransact.Store(self.path, *args)
287 def readDom(self, *args):
288 return xstransact.Read(self.path, *args)
290 def writeDom(self, *args):
291 return xstransact.Write(self.path, *args)
293 def removeDom(self, *args):
294 return xstransact.Remove(self.path, *args)
296 def gatherDom(self, *args):
297 return xstransact.Gather(self.path, *args)
299 def storeDom(self, *args):
300 return xstransact.Store(self.path, *args)
302 def setDB(self, db):
303 self.db = db
305 def saveToDB(self, save=False, sync=False):
306 self.db.saveDB(save=save, sync=sync)
308 def exportToDB(self, save=False, sync=False):
309 if self.image:
310 self.image.exportToDB(save=save, sync=sync)
311 self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)
313 def importFromDB(self):
314 self.db.importFromDB(self, fields=self.__exports__)
315 self.store_channel = self.eventChannel("store/port")
317 def setDomid(self, domid):
318 """Set the domain id.
320 @param dom: domain id
321 """
322 self.domid = domid
323 self.storeDom("domid", self.domid)
325 def getDomain(self):
326 return self.domid
328 def setName(self, name):
329 self.name = name
330 self.storeVm("name", name)
332 def getName(self):
333 return self.name
335 def setStoreRef(self, ref):
336 self.store_mfn = ref
337 self.storeDom("store/ring-ref", ref)
339 def setStoreChannel(self, channel):
340 if self.store_channel and self.store_channel != channel:
341 self.store_channel.close()
342 self.store_channel = channel
343 self.storeDom("store/port", channel.port1)
345 def setConsoleRef(self, ref):
346 self.console_mfn = ref
347 self.storeDom("console/ring-ref", ref)
349 def setMemoryTarget(self, target):
350 self.memory_target = target
351 self.storeDom("memory/target", target)
353 def update(self, info=None):
354 """Update with info from xc.domain_getinfo().
355 """
356 self.info = info or dom_get(self.domid)
357 self.memory = self.info['mem_kb'] / 1024
358 self.ssidref = self.info['ssidref']
360 def state_set(self, state):
361 self.state_updated.acquire()
362 if self.state != state:
363 self.state = state
364 self.state_updated.notifyAll()
365 self.state_updated.release()
366 self.saveToDB()
368 def state_wait(self, state):
369 self.state_updated.acquire()
370 while self.state != state:
371 self.state_updated.wait()
372 self.state_updated.release()
374 def __str__(self):
375 s = "<domain"
376 s += " id=" + str(self.domid)
377 s += " name=" + self.name
378 s += " memory=" + str(self.memory)
379 s += " ssidref=" + str(self.ssidref)
380 s += ">"
381 return s
383 __repr__ = __str__
385 def getDeviceController(self, type, error=True):
386 ctrl = self.controllers.get(type)
387 if not ctrl and error:
388 raise XendError("invalid device type:" + type)
389 return ctrl
391 def findDeviceController(self, type):
392 return (self.getDeviceController(type, error=False)
393 or self.createDeviceController(type))
395 def createDeviceController(self, type):
396 ctrl = controller.createDevController(type, self, recreate=self.recreate)
397 self.controllers[type] = ctrl
398 return ctrl
400 def createDevice(self, type, devconfig, change=False):
401 if self.recreate:
402 return
403 if type == 'vbd':
404 typedev = sxp.child_value(devconfig, 'dev')
405 if re.match('^ioemu:', typedev):
406 return;
408 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
410 devnum = blkdev_name_to_number(sxp.child_value(devconfig, 'dev'))
412 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
413 self.uuid, devnum)
414 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
416 front = { 'backend' : backpath,
417 'backend-id' : "%i" % backdom.domid,
418 'virtual-device' : "%i" % devnum }
419 xstransact.Write(frontpath, front)
421 (type, params) = string.split(sxp.child_value(devconfig,
422 'uname'), ':', 1)
423 back = { 'type' : type,
424 'params' : params,
425 'frontend' : frontpath,
426 'frontend-id' : "%i" % self.domid }
427 xstransact.Write(backpath, back)
429 return
431 if type == 'vif':
432 from xen.xend import XendRoot
433 xroot = XendRoot.instance()
435 def _get_config_ipaddr(config):
436 val = []
437 for ipaddr in sxp.children(config, elt='ip'):
438 val.append(sxp.child0(ipaddr))
439 return val
441 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
443 devnum = self.netif_idx
444 self.netif_idx += 1
446 script = sxp.child_value(devconfig, 'script',
447 xroot.get_vif_script())
448 script = os.path.join(xroot.network_script_dir, script)
449 bridge = sxp.child_value(devconfig, 'bridge',
450 xroot.get_vif_bridge())
451 mac = sxp.child_value(devconfig, 'mac')
452 ipaddr = _get_config_ipaddr(devconfig)
454 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
455 self.uuid, devnum)
456 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
458 front = { 'backend' : backpath,
459 'backend-id' : "%i" % backdom.domid,
460 'handle' : "%i" % devnum,
461 'mac' : mac }
462 xstransact.Write(frontpath, front)
464 back = { 'script' : script,
465 'domain' : self.name,
466 'mac' : mac,
467 'bridge' : bridge,
468 'frontend' : frontpath,
469 'frontend-id' : "%i" % self.domid,
470 'handle' : "%i" % devnum }
471 if ipaddr:
472 back['ip'] = ' '.join(ipaddr)
473 xstransact.Write(backpath, back)
475 return
477 if type == 'vtpm':
478 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
480 devnum = int(sxp.child_value(devconfig, 'instance', '0'))
481 log.error("The domain has a TPM with instance %d." % devnum)
483 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
484 self.uuid, devnum)
485 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
487 front = { 'backend' : backpath,
488 'backend-id' : "%i" % backdom.domid,
489 'handle' : "%i" % devnum }
490 xstransact.Write(frontpath, front)
492 back = { 'instance' : "%i" % devnum,
493 'frontend' : frontpath,
494 'frontend-id' : "%i" % self.domid }
495 xstransact.Write(backpath, back)
497 return
499 ctrl = self.findDeviceController(type)
500 return ctrl.createDevice(devconfig, recreate=self.recreate,
501 change=change)
503 def configureDevice(self, type, id, devconfig):
504 ctrl = self.getDeviceController(type)
505 return ctrl.configureDevice(id, devconfig)
507 def destroyDevice(self, type, id, change=False, reboot=False):
508 ctrl = self.getDeviceController(type)
509 return ctrl.destroyDevice(id, change=change, reboot=reboot)
511 def deleteDevice(self, type, id):
512 ctrl = self.getDeviceController(type)
513 return ctrl.deleteDevice(id)
515 def getDevice(self, type, id, error=True):
516 ctrl = self.getDeviceController(type)
517 return ctrl.getDevice(id, error=error)
519 def getDeviceIds(self, type):
520 ctrl = self.getDeviceController(type)
521 return ctrl.getDeviceIds()
523 def getDeviceSxprs(self, type):
524 ctrl = self.getDeviceController(type)
525 return ctrl.getDeviceSxprs()
527 def sxpr(self):
528 sxpr = ['domain',
529 ['domid', self.domid],
530 ['name', self.name],
531 ['memory', self.memory],
532 ['ssidref', self.ssidref],
533 ['target', self.target] ]
534 if self.uuid:
535 sxpr.append(['uuid', self.uuid])
536 if self.info:
537 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
538 run = (self.info['running'] and 'r') or '-'
539 block = (self.info['blocked'] and 'b') or '-'
540 pause = (self.info['paused'] and 'p') or '-'
541 shut = (self.info['shutdown'] and 's') or '-'
542 crash = (self.info['crashed'] and 'c') or '-'
543 state = run + block + pause + shut + crash
544 sxpr.append(['state', state])
545 if self.info['shutdown']:
546 reason = shutdown_reason(self.info['shutdown_reason'])
547 sxpr.append(['shutdown_reason', reason])
548 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
549 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
550 sxpr.append(['vcpus', self.info['vcpus']])
551 sxpr.append(['cpumap', self.info['cpumap']])
552 # build a string, using '|' to seperate items, show only up
553 # to number of vcpus in domain, and trim the trailing '|'
554 sxpr.append(['vcpu_to_cpu', ''.join(map(lambda x: str(x)+'|',
555 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))[:-1]])
557 if self.start_time:
558 up_time = time.time() - self.start_time
559 sxpr.append(['up_time', str(up_time) ])
560 sxpr.append(['start_time', str(self.start_time) ])
562 if self.store_channel:
563 sxpr.append(self.store_channel.sxpr())
564 if self.store_mfn:
565 sxpr.append(['store_mfn', self.store_mfn])
566 if self.console_channel:
567 sxpr.append(['console_channel', self.console_channel.sxpr()])
568 if self.console_mfn:
569 sxpr.append(['console_mfn', self.console_mfn])
570 # already in (devices)
571 # console = self.getConsole()
572 # if console:
573 # sxpr.append(console.sxpr())
575 if self.restart_count:
576 sxpr.append(['restart_count', self.restart_count])
577 if self.restart_state:
578 sxpr.append(['restart_state', self.restart_state])
579 if self.restart_time:
580 sxpr.append(['restart_time', str(self.restart_time)])
582 devs = self.sxpr_devices()
583 if devs:
584 sxpr.append(devs)
585 if self.config:
586 sxpr.append(['config', self.config])
587 if self.device_model_pid:
588 sxpr.append(['device_model_pid',self.device_model_pid])
589 return sxpr
591 def sxpr_devices(self):
592 sxpr = []
593 for ty in self.controllers.keys():
594 devs = self.getDeviceSxprs(ty)
595 sxpr += devs
596 if sxpr:
597 sxpr.insert(0, 'devices')
598 else:
599 sxpr = None
600 return sxpr
602 def check_name(self, name):
603 """Check if a vm name is valid. Valid names contain alphabetic characters,
604 digits, or characters in '_-.:/+'.
605 The same name cannot be used for more than one vm at the same time.
607 @param name: name
608 @raise: VMerror if invalid
609 """
610 if self.recreate: return
611 if name is None or name == '':
612 raise VmError('missing vm name')
613 for c in name:
614 if c in string.digits: continue
615 if c in '_-.:/+': continue
616 if c in string.ascii_letters: continue
617 raise VmError('invalid vm name')
618 dominfo = domain_exists(name)
619 # When creating or rebooting, a domain with my name should not exist.
620 # When restoring, a domain with my name will exist, but it should have
621 # my domain id.
622 if not dominfo:
623 return
624 if dominfo.is_terminated():
625 return
626 if not self.domid or (dominfo.domid != self.domid):
627 raise VmError('vm name clash: ' + name)
629 def construct(self, config):
630 """Construct the vm instance from its configuration.
632 @param config: configuration
633 @raise: VmError on error
634 """
635 # todo - add support for scheduling params?
636 self.config = config
637 try:
638 # Initial domain create.
639 self.setName(sxp.child_value(config, 'name'))
640 self.check_name(self.name)
641 self.init_image()
642 self.configure_cpus(config)
643 self.init_domain()
644 self.register_domain()
645 self.configure_bootloader()
647 # Create domain devices.
648 self.configure_backends()
649 self.configure_restart()
650 self.construct_image()
651 self.configure()
652 self.exportToDB(save=True)
653 except Exception, ex:
654 # Catch errors, cleanup and re-raise.
655 print 'Domain construction error:', ex
656 import traceback
657 traceback.print_exc()
658 self.destroy()
659 raise
661 def register_domain(self):
662 xd = get_component('xen.xend.XendDomain')
663 xd._add_domain(self)
664 self.exportToDB(save=True)
666 def configure_cpus(self, config):
667 try:
668 self.cpu_weight = float(sxp.child_value(config, 'cpu_weight', '1'))
669 except:
670 raise VmError('invalid cpu weight')
671 self.memory = int(sxp.child_value(config, 'memory'))
672 if self.memory is None:
673 raise VmError('missing memory size')
674 self.setMemoryTarget(self.memory * (1 << 20))
675 self.ssidref = int(sxp.child_value(config, 'ssidref'))
676 cpu = sxp.child_value(config, 'cpu')
677 if self.recreate and self.domid and cpu is not None and int(cpu) >= 0:
678 xc.domain_pincpu(self.domid, 0, 1<<int(cpu))
679 try:
680 image = sxp.child_value(self.config, 'image')
681 vcpus = sxp.child_value(image, 'vcpus')
682 if vcpus:
683 self.vcpus = int(vcpus)
684 except:
685 raise VmError('invalid vcpus value')
687 def configure_vcpus(self, vcpus):
688 d = {}
689 for v in range(0, vcpus):
690 d["cpu/%d/availability" % v] = "online"
691 self.writeVm(d)
693 def init_image(self):
694 """Create boot image handler for the domain.
695 """
696 image = sxp.child_value(self.config, 'image')
697 if image is None:
698 raise VmError('missing image')
699 self.image = ImageHandler.create(self, image)
701 def construct_image(self):
702 """Construct the boot image for the domain.
703 """
704 self.create_channel()
705 self.image.createImage()
706 self.exportToDB()
707 if self.store_channel and self.store_mfn >= 0:
708 IntroduceDomain(self.domid, self.store_mfn,
709 self.store_channel.port1, self.path)
710 # get the configured value of vcpus and update store
711 self.configure_vcpus(self.vcpus)
713 def delete(self):
714 """Delete the vm's db.
715 """
716 if dom_get(self.domid):
717 return
718 self.domid = None
719 self.saveToDB(sync=True)
720 try:
721 # Todo: eventually will have to wait for devices to signal
722 # destruction before can delete the db.
723 if self.db:
724 self.db.delete()
725 except Exception, ex:
726 log.warning("error in domain db delete: %s", ex)
727 pass
729 def destroy_domain(self):
730 """Destroy the vm's domain.
731 The domain will not finally go away unless all vm
732 devices have been released.
733 """
734 if self.domid is None:
735 return
736 try:
737 xc.domain_destroy(dom=self.domid)
738 except Exception, err:
739 log.exception("Domain destroy failed: %s", self.name)
741 def cleanup(self):
742 """Cleanup vm resources: release devices.
743 """
744 self.state = STATE_VM_TERMINATED
745 self.release_devices()
746 if self.store_channel:
747 self.setStoreChannel(None)
748 if self.console_channel:
749 # notify processes using this cosole?
750 try:
751 self.console_channel.close()
752 self.console_channel = None
753 except:
754 pass
755 if self.image:
756 try:
757 self.device_model_pid = 0
758 self.image.destroy()
759 self.image = None
760 except:
761 pass
763 def destroy(self):
764 """Clenup vm and destroy domain.
765 """
766 self.destroy_domain()
767 self.cleanup()
768 self.saveToDB()
769 return 0
771 def is_terminated(self):
772 """Check if a domain has been terminated.
773 """
774 return self.state == STATE_VM_TERMINATED
776 def release_devices(self):
777 """Release all vm devices.
778 """
779 reboot = self.restart_pending()
780 for ctrl in self.controllers.values():
781 if ctrl.isDestroyed(): continue
782 ctrl.destroyController(reboot=reboot)
783 t = xstransact("%s/device" % self.path)
784 for d in t.list("vbd"):
785 t.remove(d)
786 for d in t.list("vif"):
787 t.remove(d)
788 for d in t.list("vtpm"):
789 t.remove(d)
790 t.commit()
792 def show(self):
793 """Print virtual machine info.
794 """
795 print "[VM dom=%d name=%s memory=%d ssidref=%d" % (self.domid, self.name, self.memory, self.ssidref)
796 print "image:"
797 sxp.show(self.image)
798 print "]"
800 def init_domain(self):
801 """Initialize the domain memory.
802 """
803 if self.recreate:
804 return
805 if self.start_time is None:
806 self.start_time = time.time()
807 self.storeVm(("start-time", self.start_time))
808 try:
809 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
810 except:
811 raise VmError('invalid cpu')
812 id = self.image.initDomain(self.domid, self.memory, self.ssidref, cpu, self.cpu_weight)
813 log.debug('init_domain> Created domain=%d name=%s memory=%d',
814 id, self.name, self.memory)
815 self.setDomid(id)
817 def eventChannel(self, path=None):
818 """Create an event channel to the domain.
820 @param path under which port is stored in db
821 """
822 port = 0
823 if path:
824 try:
825 port = int(self.readDom(path))
826 except:
827 # if anything goes wrong, assume the port was not yet set
828 pass
829 ret = EventChannel.interdomain(0, self.domid, port1=port, port2=0)
830 self.storeDom(path, ret.port1)
831 return ret
833 def create_channel(self):
834 """Create the channels to the domain.
835 """
836 self.store_channel = self.eventChannel("store/port")
837 self.console_channel = self.eventChannel("console/port")
839 def create_configured_devices(self):
840 devices = sxp.children(self.config, 'device')
841 for d in devices:
842 dev_config = sxp.child0(d)
843 if dev_config is None:
844 raise VmError('invalid device')
845 dev_type = sxp.name(dev_config)
847 if not controller.isDevControllerClass(dev_type):
848 raise VmError('unknown device type: ' + dev_type)
850 self.createDevice(dev_type, dev_config)
853 def create_devices(self):
854 """Create the devices for a vm.
856 @raise: VmError for invalid devices
857 """
858 if self.rebooting():
859 for ctrl in self.controllers.values():
860 ctrl.initController(reboot=True)
861 else:
862 self.create_configured_devices()
863 if not self.device_model_pid:
864 self.device_model_pid = self.image.createDeviceModel()
866 def device_create(self, dev_config):
867 """Create a new device.
869 @param dev_config: device configuration
870 """
871 dev_type = sxp.name(dev_config)
872 dev = self.createDevice(dev_type, dev_config, change=True)
873 self.config.append(['device', dev.getConfig()])
874 return dev.sxpr()
876 def device_configure(self, dev_config, id):
877 """Configure an existing device.
879 @param dev_config: device configuration
880 @param id: device id
881 """
882 type = sxp.name(dev_config)
883 dev = self.getDevice(type, id)
884 old_config = dev.getConfig()
885 new_config = dev.configure(dev_config, change=True)
886 # Patch new config into vm config.
887 new_full_config = ['device', new_config]
888 old_full_config = ['device', old_config]
889 old_index = self.config.index(old_full_config)
890 self.config[old_index] = new_full_config
891 return new_config
893 def device_refresh(self, type, id):
894 """Refresh a device.
896 @param type: device type
897 @param id: device id
898 """
899 dev = self.getDevice(type, id)
900 dev.refresh()
902 def device_delete(self, type, id):
903 """Destroy and remove a device.
905 @param type: device type
906 @param id: device id
907 """
908 dev = self.getDevice(type, id)
909 dev_config = dev.getConfig()
910 if dev_config:
911 self.config.remove(['device', dev_config])
912 self.deleteDevice(type, dev.getId())
914 def configure_bootloader(self):
915 """Configure boot loader.
916 """
917 self.bootloader = sxp.child_value(self.config, "bootloader")
919 def configure_restart(self):
920 """Configure the vm restart mode.
921 """
922 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
923 if r not in restart_modes:
924 raise VmError('invalid restart mode: ' + str(r))
925 self.restart_mode = r;
927 def restart_needed(self, reason):
928 """Determine if the vm needs to be restarted when shutdown
929 for the given reason.
931 @param reason: shutdown reason
932 @return True if needs restart, False otherwise
933 """
934 if self.restart_mode == RESTART_NEVER:
935 return False
936 if self.restart_mode == RESTART_ALWAYS:
937 return True
938 if self.restart_mode == RESTART_ONREBOOT:
939 return reason == 'reboot'
940 return False
942 def restart_cancel(self):
943 """Cancel a vm restart.
944 """
945 self.restart_state = None
947 def restarting(self):
948 """Put the vm into restart mode.
949 """
950 self.restart_state = STATE_RESTART_PENDING
952 def restart_pending(self):
953 """Test if the vm has a pending restart.
954 """
955 return self.restart_state == STATE_RESTART_PENDING
957 def rebooting(self):
958 return self.restart_state == STATE_RESTART_BOOTING
960 def restart_check(self):
961 """Check if domain restart is OK.
962 To prevent restart loops, raise an error if it is
963 less than MINIMUM_RESTART_TIME seconds since the last restart.
964 """
965 tnow = time.time()
966 if self.restart_time is not None:
967 tdelta = tnow - self.restart_time
968 if tdelta < self.MINIMUM_RESTART_TIME:
969 self.restart_cancel()
970 msg = 'VM %s restarting too fast' % self.name
971 log.error(msg)
972 raise VmError(msg)
973 self.restart_time = tnow
974 self.restart_count += 1
976 def restart(self):
977 """Restart the domain after it has exited.
978 Reuses the domain id
980 """
981 try:
982 self.clear_shutdown()
983 self.state = STATE_VM_OK
984 self.shutdown_pending = None
985 self.restart_check()
986 self.exportToDB()
987 self.restart_state = STATE_RESTART_BOOTING
988 if self.bootloader:
989 self.config = self.bootloader_config()
990 self.construct(self.config)
991 self.saveToDB()
992 finally:
993 self.restart_state = None
995 def bootloader_config(self):
996 # if we're restarting with a bootloader, we need to run it
997 # FIXME: this assumes the disk is the first device and
998 # that we're booting from the first disk
999 blcfg = None
1000 # FIXME: this assumes that we want to use the first disk
1001 dev = sxp.child_value(self.config, "device")
1002 if dev:
1003 disk = sxp.child_value(dev, "uname")
1004 fn = blkdev_uname_to_file(disk)
1005 blcfg = bootloader(self.bootloader, fn, 1, self.vcpus)
1006 if blcfg is None:
1007 msg = "Had a bootloader specified, but can't find disk"
1008 log.error(msg)
1009 raise VmError(msg)
1010 config = sxp.merge(['vm', blcfg ], self.config)
1011 return config
1013 def configure_backends(self):
1014 """Set configuration flags if the vm is a backend for netif or blkif.
1015 Configure the backends to use for vbd and vif if specified.
1016 """
1017 for c in sxp.children(self.config, 'backend'):
1018 v = sxp.child0(c)
1019 name = sxp.name(v)
1020 if name == 'blkif':
1021 self.blkif_backend = True
1022 elif name == 'netif':
1023 self.netif_backend = True
1024 elif name == 'usbif':
1025 self.usbif_backend = True
1026 elif name == 'tpmif':
1027 self.tpmif_backend = True
1028 else:
1029 raise VmError('invalid backend type:' + str(name))
1031 def configure(self):
1032 """Configure a vm.
1034 """
1035 self.configure_fields()
1036 self.create_devices()
1037 self.create_blkif()
1039 def create_blkif(self):
1040 """Create the block device interface (blkif) for the vm.
1041 The vm needs a blkif even if it doesn't have any disks
1042 at creation time, for example when it uses NFS root.
1044 """
1045 return
1046 blkif = self.getDeviceController("vbd", error=False)
1047 if not blkif:
1048 blkif = self.createDeviceController("vbd")
1049 backend = blkif.getBackend(0)
1050 backend.connect(recreate=self.recreate)
1052 def configure_fields(self):
1053 """Process the vm configuration fields using the registered handlers.
1054 """
1055 index = {}
1056 for field in sxp.children(self.config):
1057 field_name = sxp.name(field)
1058 field_index = index.get(field_name, 0)
1059 field_handler = config_handlers.get(field_name)
1060 # Ignore unknown fields. Warn?
1061 if field_handler:
1062 v = field_handler(self, self.config, field, field_index)
1063 else:
1064 log.warning("Unknown config field %s", field_name)
1065 index[field_name] = field_index + 1
1067 def vcpu_hotplug(self, vcpu, state):
1068 """Disable or enable VCPU in domain.
1069 """
1070 if vcpu > self.vcpus:
1071 log.error("Invalid VCPU %d" % vcpu)
1072 return
1073 if int(state) == 0:
1074 availability = "offline"
1075 else:
1076 availability = "online"
1077 self.storeVm("cpu/%d/availability" % vcpu, availability)
1079 def shutdown(self, reason):
1080 if not reason in shutdown_reasons.values():
1081 raise XendError('invalid reason:' + reason)
1082 self.storeVm("control/shutdown", reason)
1083 if not reason in ['suspend']:
1084 self.shutdown_pending = {'start':time.time(), 'reason':reason}
1086 def clear_shutdown(self):
1087 self.removeVm("control/shutdown")
1089 def send_sysrq(self, key=0):
1090 self.storeVm("control/sysrq", '%c' % key)
1092 def shutdown_time_left(self, timeout):
1093 if not self.shutdown_pending:
1094 return 0
1095 return timeout - (time.time() - self.shutdown_pending['start'])
1097 def dom0_init_store(self):
1098 if not self.store_channel:
1099 self.store_channel = self.eventChannel("store/port")
1100 if not self.store_channel:
1101 return
1102 ref = xc.init_store(self.store_channel.port2)
1103 if ref and ref >= 0:
1104 self.setStoreRef(ref)
1105 try:
1106 IntroduceDomain(self.domid, ref, self.store_channel.port1,
1107 self.path)
1108 except RuntimeError, ex:
1109 if ex.args[0] == errno.EISCONN:
1110 pass
1111 else:
1112 raise
1113 # get run-time value of vcpus and update store
1114 self.configure_vcpus(dom_get(self.domid)['vcpus'])
1117 def vm_field_ignore(_, _1, _2, _3):
1118 """Dummy config field handler used for fields with built-in handling.
1119 Matches the signature required by config_handlers.
1120 """
1121 pass
1124 def vm_field_maxmem(vm, _1, val, _2):
1125 """Config field handler to configure vm memory limit. Matches the
1126 signature required by config_handlers.
1127 """
1128 maxmem = sxp.child0(val)
1129 if maxmem is None:
1130 maxmem = vm.memory
1131 try:
1132 maxmem = int(maxmem)
1133 except:
1134 raise VmError("invalid maxmem: " + str(maxmem))
1135 xc.domain_setmaxmem(vm.domid, maxmem_kb = maxmem * 1024)
1138 #============================================================================
1139 # Register image handlers.
1141 from image import \
1142 addImageHandlerClass, \
1143 ImageHandler, \
1144 LinuxImageHandler, \
1145 VmxImageHandler
1147 addImageHandlerClass(LinuxImageHandler)
1148 addImageHandlerClass(VmxImageHandler)
1151 """Table of handlers for field configuration.
1153 field_name[String]: fn(vm, config, field, index) -> value(ignored)
1154 """
1155 config_handlers = {
1157 # Ignore the fields we already handle.
1159 'name': vm_field_ignore,
1160 'memory': vm_field_ignore,
1161 'ssidref': vm_field_ignore,
1162 'cpu': vm_field_ignore,
1163 'cpu_weight': vm_field_ignore,
1164 'restart': vm_field_ignore,
1165 'image': vm_field_ignore,
1166 'device': vm_field_ignore,
1167 'backend': vm_field_ignore,
1168 'vcpus': vm_field_ignore,
1169 'bootloader': vm_field_ignore,
1171 # Register other config handlers.
1172 'maxmem': vm_field_maxmem
1176 #============================================================================
1177 # Register device controllers and their device config types.
1179 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1180 controller.addDevControllerClass("vbd", blkif.BlkifController)
1181 controller.addDevControllerClass("vif", netif.NetifController)
1182 controller.addDevControllerClass("vtpm", tpmif.TPMifController)
1183 controller.addDevControllerClass("pci", pciif.PciController)
1184 controller.addDevControllerClass("usb", usbif.UsbifController)