ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 6663:7c269dd2cf1f

Destroy domain before cleaning it up.
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Tue Sep 06 18:14:31 2005 +0000 (2005-09-06)
parents d4d69c509371
children 8db9c5873b9b
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
31 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
32 from xen.util.ip import check_subnet, get_current_ipgw
33 from xen.util.blkif import blkdev_uname_to_file
35 from xen.xend.server import controller
36 from xen.xend.server import SrvDaemon; xend = SrvDaemon.instance()
37 from xen.xend.server import messages
38 from xen.xend.server.channel import EventChannel, channelFactory
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
52 """Shutdown code for poweroff."""
53 DOMAIN_POWEROFF = 0
55 """Shutdown code for reboot."""
56 DOMAIN_REBOOT = 1
58 """Shutdown code for suspend."""
59 DOMAIN_SUSPEND = 2
61 """Shutdown code for crash."""
62 DOMAIN_CRASH = 3
64 """Map shutdown codes to strings."""
65 shutdown_reasons = {
66 DOMAIN_POWEROFF: "poweroff",
67 DOMAIN_REBOOT : "reboot",
68 DOMAIN_SUSPEND : "suspend",
69 DOMAIN_CRASH : "crash",
70 }
72 RESTART_ALWAYS = 'always'
73 RESTART_ONREBOOT = 'onreboot'
74 RESTART_NEVER = 'never'
76 restart_modes = [
77 RESTART_ALWAYS,
78 RESTART_ONREBOOT,
79 RESTART_NEVER,
80 ]
82 STATE_RESTART_PENDING = 'pending'
83 STATE_RESTART_BOOTING = 'booting'
85 STATE_VM_OK = "ok"
86 STATE_VM_TERMINATED = "terminated"
87 STATE_VM_SUSPENDED = "suspended"
90 def domain_exists(name):
91 # See comment in XendDomain constructor.
92 xd = get_component('xen.xend.XendDomain')
93 return xd.domain_lookup_by_name(name)
95 def shutdown_reason(code):
96 """Get a shutdown reason from a code.
98 @param code: shutdown code
99 @type code: int
100 @return: shutdown reason
101 @rtype: string
102 """
103 return shutdown_reasons.get(code, "?")
105 config_handlers = {}
107 def add_config_handler(name, h):
108 """Add a handler for a config field.
110 @param name: field name
111 @param h: handler: fn(vm, config, field, index)
112 """
113 config_handlers[name] = h
115 def get_config_handler(name):
116 """Get a handler for a config field.
118 returns handler or None
119 """
120 return config_handlers.get(name)
122 """Table of handlers for devices.
123 Indexed by device type.
124 """
125 device_handlers = {}
127 def add_device_handler(name, type):
128 device_handlers[name] = type
130 def get_device_handler(name):
131 return device_handlers[name]
133 def dom_get(dom):
134 """Get info from xen for an existing domain.
136 @param dom: domain id
137 @return: info or None
138 """
139 domlist = xc.domain_getinfo(dom, 1)
140 if domlist and dom == domlist[0]['dom']:
141 return domlist[0]
142 return None
144 class XendDomainInfo:
145 """Virtual machine object."""
147 """Minimum time between domain restarts in seconds.
148 """
149 MINIMUM_RESTART_TIME = 20
151 def create(cls, parentdb, config):
152 """Create a VM from a configuration.
154 @param parentdb: parent db
155 @param config configuration
156 @raise: VmError for invalid configuration
157 """
158 uuid = getUuid()
159 db = parentdb.addChild(uuid)
160 vm = cls(db)
161 vm.construct(config)
162 vm.saveToDB(sync=True)
164 return vm
166 create = classmethod(create)
168 def recreate(cls, db, info):
169 """Create the VM object for an existing domain.
171 @param db: domain db
172 @param info: domain info from xc
173 """
174 dom = info['dom']
175 vm = cls(db)
176 vm.setdom(dom)
177 db.readDB()
178 vm.importFromDB()
179 config = vm.config
180 log.debug('info=' + str(info))
181 log.debug('config=' + prettyprintstring(config))
183 vm.memory = info['mem_kb']/1024
184 vm.target = info['mem_kb'] * 1024
186 if config:
187 try:
188 vm.recreate = True
189 vm.construct(config)
190 finally:
191 vm.recreate = False
192 else:
193 vm.setName("Domain-%d" % dom)
195 vm.exportToDB(save=True)
196 return vm
198 recreate = classmethod(recreate)
200 def restore(cls, parentdb, config, uuid=None):
201 """Create a domain and a VM object to do a restore.
203 @param parentdb: parent db
204 @param config: domain configuration
205 @param uuid: uuid to use
206 """
207 if not uuid:
208 uuid = getUuid()
209 db = parentdb.addChild(uuid)
210 vm = cls(db)
211 ssidref = int(sxp.child_value(config, 'ssidref'))
212 log.debug('restoring with ssidref='+str(ssidref))
213 id = xc.domain_create(ssidref = ssidref)
214 vm.setdom(id)
215 vm.clear_shutdown()
216 try:
217 vm.restore = True
218 vm.construct(config)
219 finally:
220 vm.restore = False
221 vm.exportToDB(save=True, sync=True)
222 return vm
224 restore = classmethod(restore)
226 __exports__ = [
227 DBVar('id', ty='int'),
228 DBVar('name', ty='str'),
229 DBVar('uuid', ty='str'),
230 DBVar('config', ty='sxpr'),
231 DBVar('start_time', ty='float'),
232 DBVar('state', ty='str'),
233 DBVar('store_mfn', ty='long'),
234 DBVar('console_mfn', ty='long', path="console/ring-ref"),
235 DBVar('restart_mode', ty='str'),
236 DBVar('restart_state', ty='str'),
237 DBVar('restart_time', ty='float'),
238 DBVar('restart_count', ty='int'),
239 DBVar('target', ty='long', path="memory/target"),
240 DBVar('device_model_pid', ty='int'),
241 ]
243 def __init__(self, db):
244 self.db = db
245 self.uuid = db.getName()
247 self.recreate = 0
248 self.restore = 0
250 self.config = None
251 self.id = None
252 self.cpu_weight = 1
253 self.start_time = None
254 self.name = None
255 self.memory = None
256 self.ssidref = None
257 self.image = None
259 self.target = None
261 self.channel = None
262 self.store_channel = None
263 self.store_mfn = None
264 self.console_channel = None
265 self.console_mfn = None
266 self.controllers = {}
268 self.info = None
269 self.blkif_backend = False
270 self.netif_backend = False
271 self.netif_idx = 0
272 self.tpmif_backend = False
274 #todo: state: running, suspended
275 self.state = STATE_VM_OK
276 self.state_updated = threading.Condition()
277 self.shutdown_pending = None
279 #todo: set to migrate info if migrating
280 self.migrate = None
282 self.restart_mode = RESTART_ONREBOOT
283 self.restart_state = None
284 self.restart_time = None
285 self.restart_count = 0
287 self.vcpus = 1
288 self.vcpusdb = {}
289 self.bootloader = None
290 self.device_model_pid = 0
292 def setDB(self, db):
293 self.db = db
295 def saveToDB(self, save=False, sync=False):
296 self.db.saveDB(save=save, sync=sync)
298 def exportToDB(self, save=False, sync=False):
299 if self.channel:
300 self.channel.saveToDB(self.db.addChild("channel"), save=save)
301 if self.store_channel:
302 self.store_channel.saveToDB(self.db.addChild("store_channel"),
303 save=save)
304 if self.console_channel:
305 self.console_channel.saveToDB(self.db.addChild("console/console_channel"),
306 save=save)
307 if self.image:
308 self.image.exportToDB(save=save, sync=sync)
309 self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)
311 def importFromDB(self):
312 self.db.importFromDB(self, fields=self.__exports__)
313 self.store_channel = self.eventChannel("store_channel")
315 def setdom(self, dom):
316 """Set the domain id.
318 @param dom: domain id
319 """
320 self.id = int(dom)
321 #self.db.id = self.id
323 def getDomain(self):
324 return self.id
326 def setName(self, name):
327 self.name = name
328 self.db.name = self.name
330 def getName(self):
331 return self.name
333 def getChannel(self):
334 return self.channel
336 def getStoreChannel(self):
337 return self.store_channel
339 def getConsoleChannel(self):
340 return self.console_channel
342 def update(self, info=None):
343 """Update with info from xc.domain_getinfo().
344 """
345 self.info = info or dom_get(self.id)
346 self.memory = self.info['mem_kb'] / 1024
347 self.ssidref = self.info['ssidref']
348 self.target = self.info['mem_kb'] * 1024
350 def state_set(self, state):
351 self.state_updated.acquire()
352 if self.state != state:
353 self.state = state
354 self.state_updated.notifyAll()
355 self.state_updated.release()
356 self.saveToDB()
358 def state_wait(self, state):
359 self.state_updated.acquire()
360 while self.state != state:
361 self.state_updated.wait()
362 self.state_updated.release()
364 def __str__(self):
365 s = "<domain"
366 s += " id=" + str(self.id)
367 s += " name=" + self.name
368 s += " memory=" + str(self.memory)
369 s += " ssidref=" + str(self.ssidref)
370 s += ">"
371 return s
373 __repr__ = __str__
375 def getDeviceTypes(self):
376 return self.controllers.keys()
378 def getDeviceControllers(self):
379 return self.controllers.values()
381 def getDeviceController(self, type, error=True):
382 ctrl = self.controllers.get(type)
383 if not ctrl and error:
384 raise XendError("invalid device type:" + type)
385 return ctrl
387 def findDeviceController(self, type):
388 return (self.getDeviceController(type, error=False)
389 or self.createDeviceController(type))
391 def createDeviceController(self, type):
392 ctrl = controller.createDevController(type, self, recreate=self.recreate)
393 self.controllers[type] = ctrl
394 return ctrl
396 def createDevice(self, type, devconfig, change=False):
397 if type == 'vbd':
398 typedev = sxp.child_value(devconfig, 'dev')
399 if re.match('^ioemu:', typedev):
400 return;
401 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
403 devnum = blkdev_name_to_number(sxp.child_value(devconfig, 'dev'))
405 # create backend db
406 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
407 (type, self.uuid, devnum))
409 # create frontend db
410 db = self.db.addChild("/device/%s/%d" % (type, devnum))
412 db['virtual-device'] = "%i" % devnum
413 #db['backend'] = sxp.child_value(devconfig, 'backend', '0')
414 db['backend'] = backdb.getPath()
415 db['backend-id'] = "%i" % backdom.id
417 backdb['frontend'] = db.getPath()
418 (type, params) = string.split(sxp.child_value(devconfig, 'uname'), ':', 1)
419 node = Blkctl.block('bind', type, params)
420 backdb['frontend-id'] = "%i" % self.id
421 backdb['physical-device'] = "%li" % blkdev_name_to_number(node)
422 backdb.saveDB(save=True)
424 # Ok, super gross, this really doesn't belong in the frontend db...
425 db['type'] = type
426 db['node'] = node
427 db['params'] = params
428 db.saveDB(save=True)
430 return
432 if type == 'vif':
433 from xen.xend import XendRoot
434 xroot = XendRoot.instance()
436 def _get_config_ipaddr(config):
437 val = []
438 for ipaddr in sxp.children(config, elt='ip'):
439 val.append(sxp.child0(ipaddr))
440 return val
442 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
444 log.error(devconfig)
446 devnum = self.netif_idx
447 self.netif_idx += 1
449 script = sxp.child_value(devconfig, 'script',
450 xroot.get_vif_script())
451 script = os.path.join(xroot.network_script_dir, script)
452 bridge = sxp.child_value(devconfig, 'bridge',
453 xroot.get_vif_bridge())
454 mac = sxp.child_value(devconfig, 'mac')
455 ipaddr = _get_config_ipaddr(devconfig)
457 # create backend db
458 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
459 (type, self.uuid, devnum))
461 # create frontend db
462 db = self.db.addChild("/device/%s/%d" % (type, devnum))
464 backdb['script'] = script
465 backdb['domain'] = self.name
466 backdb['mac'] = mac
467 backdb['bridge'] = bridge
468 if ipaddr:
469 backdb['ip'] = ' '.join(ipaddr)
470 backdb['frontend'] = db.getPath()
471 backdb['frontend-id'] = "%i" % self.id
472 backdb['handle'] = "%i" % devnum
473 backdb.saveDB(save=True)
475 db['backend'] = backdb.getPath()
476 db['backend-id'] = "%i" % backdom.id
477 db['handle'] = "%i" % devnum
478 db['mac'] = mac
480 db.saveDB(save=True)
482 return
484 if type == 'vtpm':
485 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
487 devnum = int(sxp.child_value(devconfig, 'instance', '0'))
488 log.error("The domain has a TPM with instance %d." % devnum)
490 # create backend db
491 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
492 (type, self.uuid, devnum))
493 # create frontend db
494 db = self.db.addChild("/device/%s/%d" % (type, devnum))
496 backdb['frontend'] = db.getPath()
497 backdb['frontend-id'] = "%i" % self.id
498 backdb['instance'] = sxp.child_value(devconfig, 'instance', '0')
499 backdb.saveDB(save=True)
501 db['handle'] = "%i" % devnum
502 db['backend'] = backdb.getPath()
503 db['backend-id'] = "%i" % int(sxp.child_value(devconfig,
504 'backend', '0'))
505 db.saveDB(save=True)
507 return
509 ctrl = self.findDeviceController(type)
510 return ctrl.createDevice(devconfig, recreate=self.recreate,
511 change=change)
513 def configureDevice(self, type, id, devconfig):
514 ctrl = self.getDeviceController(type)
515 return ctrl.configureDevice(id, devconfig)
517 def destroyDevice(self, type, id, change=False, reboot=False):
518 ctrl = self.getDeviceController(type)
519 return ctrl.destroyDevice(id, change=change, reboot=reboot)
521 def deleteDevice(self, type, id):
522 ctrl = self.getDeviceController(type)
523 return ctrl.deleteDevice(id)
525 def getDevice(self, type, id, error=True):
526 ctrl = self.getDeviceController(type)
527 return ctrl.getDevice(id, error=error)
529 def getDeviceIds(self, type):
530 ctrl = self.getDeviceController(type)
531 return ctrl.getDeviceIds()
533 def getDeviceSxprs(self, type):
534 ctrl = self.getDeviceController(type)
535 return ctrl.getDeviceSxprs()
537 def sxpr(self):
538 sxpr = ['domain',
539 ['id', self.id],
540 ['name', self.name],
541 ['memory', self.memory],
542 ['ssidref', self.ssidref],
543 ['target', self.target] ]
544 if self.uuid:
545 sxpr.append(['uuid', self.uuid])
546 if self.info:
547 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
548 run = (self.info['running'] and 'r') or '-'
549 block = (self.info['blocked'] and 'b') or '-'
550 pause = (self.info['paused'] and 'p') or '-'
551 shut = (self.info['shutdown'] and 's') or '-'
552 crash = (self.info['crashed'] and 'c') or '-'
553 state = run + block + pause + shut + crash
554 sxpr.append(['state', state])
555 if self.info['shutdown']:
556 reason = shutdown_reason(self.info['shutdown_reason'])
557 sxpr.append(['shutdown_reason', reason])
558 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
559 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
560 sxpr.append(['vcpus', self.info['vcpus']])
561 sxpr.append(['cpumap', self.info['cpumap']])
562 # build a string, using '|' to seperate items, show only up
563 # to number of vcpus in domain, and trim the trailing '|'
564 sxpr.append(['vcpu_to_cpu', ''.join(map(lambda x: str(x)+'|',
565 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))[:-1]])
567 if self.start_time:
568 up_time = time.time() - self.start_time
569 sxpr.append(['up_time', str(up_time) ])
570 sxpr.append(['start_time', str(self.start_time) ])
572 if self.channel:
573 sxpr.append(self.channel.sxpr())
574 if self.store_channel:
575 sxpr.append(self.store_channel.sxpr())
576 if self.store_mfn:
577 sxpr.append(['store_mfn', self.store_mfn])
578 if self.console_channel:
579 sxpr.append(['console_channel', self.console_channel.sxpr()])
580 if self.console_mfn:
581 sxpr.append(['console_mfn', self.console_mfn])
582 # already in (devices)
583 # console = self.getConsole()
584 # if console:
585 # sxpr.append(console.sxpr())
587 if self.restart_count:
588 sxpr.append(['restart_count', self.restart_count])
589 if self.restart_state:
590 sxpr.append(['restart_state', self.restart_state])
591 if self.restart_time:
592 sxpr.append(['restart_time', str(self.restart_time)])
594 devs = self.sxpr_devices()
595 if devs:
596 sxpr.append(devs)
597 if self.config:
598 sxpr.append(['config', self.config])
599 if self.device_model_pid:
600 sxpr.append(['device_model_pid',self.device_model_pid])
601 return sxpr
603 def sxpr_devices(self):
604 sxpr = []
605 for ty in self.getDeviceTypes():
606 devs = self.getDeviceSxprs(ty)
607 sxpr += devs
608 if sxpr:
609 sxpr.insert(0, 'devices')
610 else:
611 sxpr = None
612 return sxpr
614 def check_name(self, name):
615 """Check if a vm name is valid. Valid names contain alphabetic characters,
616 digits, or characters in '_-.:/+'.
617 The same name cannot be used for more than one vm at the same time.
619 @param name: name
620 @raise: VMerror if invalid
621 """
622 if self.recreate: return
623 if name is None or name == '':
624 raise VmError('missing vm name')
625 for c in name:
626 if c in string.digits: continue
627 if c in '_-.:/+': continue
628 if c in string.ascii_letters: continue
629 raise VmError('invalid vm name')
630 dominfo = domain_exists(name)
631 # When creating or rebooting, a domain with my name should not exist.
632 # When restoring, a domain with my name will exist, but it should have
633 # my domain id.
634 if not dominfo:
635 return
636 if dominfo.is_terminated():
637 return
638 if not self.id or (dominfo.id != self.id):
639 raise VmError('vm name clash: ' + name)
641 def construct(self, config):
642 """Construct the vm instance from its configuration.
644 @param config: configuration
645 @raise: VmError on error
646 """
647 # todo - add support for scheduling params?
648 self.config = config
649 try:
650 # Initial domain create.
651 self.setName(sxp.child_value(config, 'name'))
652 self.check_name(self.name)
653 self.init_image()
654 self.configure_cpus(config)
655 self.init_domain()
656 self.register_domain()
657 self.configure_bootloader()
659 # Create domain devices.
660 self.configure_backends()
661 self.configure_restart()
662 self.construct_image()
663 self.configure()
664 self.publish_console()
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.target = 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.id and cpu is not None and int(cpu) >= 0:
691 xc.domain_pincpu(self.id, 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 exportVCPUSToDB(self, vcpus):
701 for v in range(0,vcpus):
702 path = "/cpu/%d"%(v)
703 if not self.vcpusdb.has_key(path):
704 self.vcpusdb[path] = self.db.addChild(path)
705 db = self.vcpusdb[path]
706 log.debug("writing key availability=online to path %s in store"%(path))
707 db['availability'] = "online"
708 db.saveDB(save=True)
710 def init_image(self):
711 """Create boot image handler for the domain.
712 """
713 image = sxp.child_value(self.config, 'image')
714 if image is None:
715 raise VmError('missing image')
716 self.image = ImageHandler.create(self, image)
718 def construct_image(self):
719 """Construct the boot image for the domain.
720 """
721 self.create_channel()
722 self.image.createImage()
723 self.exportToDB()
724 if self.store_channel and self.store_mfn >= 0:
725 self.db.introduceDomain(self.id,
726 self.store_mfn,
727 self.store_channel)
728 # get the configured value of vcpus and update store
729 self.exportVCPUSToDB(self.vcpus)
731 def delete(self):
732 """Delete the vm's db.
733 """
734 if dom_get(self.id):
735 return
736 self.id = None
737 self.saveToDB(sync=True)
738 try:
739 # Todo: eventually will have to wait for devices to signal
740 # destruction before can delete the db.
741 if self.db:
742 self.db.delete()
743 except Exception, ex:
744 log.warning("error in domain db delete: %s", ex)
745 pass
747 def destroy_domain(self):
748 """Destroy the vm's domain.
749 The domain will not finally go away unless all vm
750 devices have been released.
751 """
752 if self.id is None:
753 return
754 try:
755 xc.domain_destroy(dom=self.id)
756 except Exception, err:
757 log.exception("Domain destroy failed: %s", self.name)
759 def cleanup(self):
760 """Cleanup vm resources: release devices.
761 """
762 self.state = STATE_VM_TERMINATED
763 self.release_devices()
764 if self.channel:
765 try:
766 self.channel.close()
767 self.channel = None
768 except:
769 pass
770 if self.store_channel:
771 try:
772 self.store_channel.close()
773 self.store_channel = None
774 except:
775 pass
776 try:
777 self.db.releaseDomain(self.id)
778 except Exception, ex:
779 log.warning("error in domain release on xenstore: %s", ex)
780 pass
781 if self.console_channel:
782 # notify processes using this cosole?
783 try:
784 self.console_channel.close()
785 self.console_channel = None
786 except:
787 pass
788 if self.image:
789 try:
790 self.device_model_pid = 0
791 self.image.destroy()
792 self.image = None
793 except:
794 pass
796 def destroy(self):
797 """Clenup vm and destroy domain.
798 """
799 self.destroy_domain()
800 self.cleanup()
801 self.saveToDB()
802 return 0
804 def is_terminated(self):
805 """Check if a domain has been terminated.
806 """
807 return self.state == STATE_VM_TERMINATED
809 def release_devices(self):
810 """Release all vm devices.
811 """
812 reboot = self.restart_pending()
813 for ctrl in self.getDeviceControllers():
814 if ctrl.isDestroyed(): continue
815 ctrl.destroyController(reboot=reboot)
816 ddb = self.db.addChild("/device")
817 for type in ddb.keys():
818 if type == 'vbd':
819 typedb = ddb.addChild(type)
820 for dev in typedb.keys():
821 devdb = typedb.addChild(str(dev))
822 Blkctl.block('unbind', devdb['type'].getData(),
823 devdb['node'].getData())
824 typedb[dev].delete()
825 typedb.saveDB(save=True)
826 if type == 'vif':
827 typedb = ddb.addChild(type)
828 for dev in typedb.keys():
829 typedb[dev].delete()
830 typedb.saveDB(save=True)
831 if type == 'vtpm':
832 typedb = ddb.addChild(type)
833 for dev in typedb.keys():
834 typedb[dev].delete()
835 typedb.saveDB(save=True)
837 def show(self):
838 """Print virtual machine info.
839 """
840 print "[VM dom=%d name=%s memory=%d ssidref=%d" % (self.id, self.name, self.memory, self.ssidref)
841 print "image:"
842 sxp.show(self.image)
843 print "]"
845 def init_domain(self):
846 """Initialize the domain memory.
847 """
848 if self.recreate:
849 return
850 if self.start_time is None:
851 self.start_time = time.time()
852 try:
853 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
854 except:
855 raise VmError('invalid cpu')
856 id = self.image.initDomain(self.id, self.memory, self.ssidref, cpu, self.cpu_weight)
857 log.debug('init_domain> Created domain=%d name=%s memory=%d',
858 id, self.name, self.memory)
859 self.setdom(id)
861 def openChannel(self, key, local, remote):
862 """Create a control channel to the domain.
863 If saved info is available recreate the channel.
865 @param key db key for the saved data (if any)
866 @param local default local port
867 @param remote default remote port
868 """
869 db = self.db.addChild(key)
870 chan = channelFactory().restoreFromDB(db, self.id, local, remote)
871 #todo: save here?
872 #chan.saveToDB(db)
873 return chan
875 def eventChannel(self, key):
876 """Create an event channel to the domain.
877 If saved info is available recreate the channel.
879 @param key db key for the saved data (if any)
880 """
881 db = self.db.addChild(key)
882 return EventChannel.restoreFromDB(db, 0, self.id)
884 def create_channel(self):
885 """Create the channels to the domain.
886 """
887 self.channel = self.openChannel("channel", 0, 1)
888 self.store_channel = self.eventChannel("store_channel")
889 self.console_channel = self.eventChannel("console/console_channel")
891 def create_configured_devices(self):
892 devices = sxp.children(self.config, 'device')
893 for d in devices:
894 dev_config = sxp.child0(d)
895 if dev_config is None:
896 raise VmError('invalid device')
897 dev_type = sxp.name(dev_config)
898 ctrl_type = get_device_handler(dev_type)
899 if ctrl_type is None:
900 raise VmError('unknown device type: ' + dev_type)
901 self.createDevice(ctrl_type, dev_config)
903 def create_devices(self):
904 """Create the devices for a vm.
906 @raise: VmError for invalid devices
907 """
908 if self.rebooting():
909 for ctrl in self.getDeviceControllers():
910 ctrl.initController(reboot=True)
911 else:
912 self.create_configured_devices()
913 if not self.device_model_pid:
914 self.device_model_pid = self.image.createDeviceModel()
916 def device_create(self, dev_config):
917 """Create a new device.
919 @param dev_config: device configuration
920 """
921 dev_type = sxp.name(dev_config)
922 dev = self.createDevice(dev_type, dev_config, change=True)
923 self.config.append(['device', dev.getConfig()])
924 return dev.sxpr()
926 def device_configure(self, dev_config, id):
927 """Configure an existing device.
929 @param dev_config: device configuration
930 @param id: device id
931 """
932 type = sxp.name(dev_config)
933 dev = self.getDevice(type, id)
934 old_config = dev.getConfig()
935 new_config = dev.configure(dev_config, change=True)
936 # Patch new config into vm config.
937 new_full_config = ['device', new_config]
938 old_full_config = ['device', old_config]
939 old_index = self.config.index(old_full_config)
940 self.config[old_index] = new_full_config
941 return new_config
943 def device_refresh(self, type, id):
944 """Refresh a device.
946 @param type: device type
947 @param id: device id
948 """
949 dev = self.getDevice(type, id)
950 dev.refresh()
952 def device_delete(self, type, id):
953 """Destroy and remove a device.
955 @param type: device type
956 @param id: device id
957 """
958 dev = self.getDevice(type, id)
959 dev_config = dev.getConfig()
960 if dev_config:
961 self.config.remove(['device', dev_config])
962 self.deleteDevice(type, dev.getId())
964 def configure_bootloader(self):
965 """Configure boot loader.
966 """
967 self.bootloader = sxp.child_value(self.config, "bootloader")
969 def configure_restart(self):
970 """Configure the vm restart mode.
971 """
972 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
973 if r not in restart_modes:
974 raise VmError('invalid restart mode: ' + str(r))
975 self.restart_mode = r;
977 def restart_needed(self, reason):
978 """Determine if the vm needs to be restarted when shutdown
979 for the given reason.
981 @param reason: shutdown reason
982 @return True if needs restart, False otherwise
983 """
984 if self.restart_mode == RESTART_NEVER:
985 return False
986 if self.restart_mode == RESTART_ALWAYS:
987 return True
988 if self.restart_mode == RESTART_ONREBOOT:
989 return reason == 'reboot'
990 return False
992 def restart_cancel(self):
993 """Cancel a vm restart.
994 """
995 self.restart_state = None
997 def restarting(self):
998 """Put the vm into restart mode.
999 """
1000 self.restart_state = STATE_RESTART_PENDING
1002 def restart_pending(self):
1003 """Test if the vm has a pending restart.
1004 """
1005 return self.restart_state == STATE_RESTART_PENDING
1007 def rebooting(self):
1008 return self.restart_state == STATE_RESTART_BOOTING
1010 def restart_check(self):
1011 """Check if domain restart is OK.
1012 To prevent restart loops, raise an error if it is
1013 less than MINIMUM_RESTART_TIME seconds since the last restart.
1014 """
1015 tnow = time.time()
1016 if self.restart_time is not None:
1017 tdelta = tnow - self.restart_time
1018 if tdelta < self.MINIMUM_RESTART_TIME:
1019 self.restart_cancel()
1020 msg = 'VM %s restarting too fast' % self.name
1021 log.error(msg)
1022 raise VmError(msg)
1023 self.restart_time = tnow
1024 self.restart_count += 1
1026 def restart(self):
1027 """Restart the domain after it has exited.
1028 Reuses the domain id
1030 """
1031 try:
1032 self.clear_shutdown()
1033 self.state = STATE_VM_OK
1034 self.shutdown_pending = None
1035 self.restart_check()
1036 self.exportToDB()
1037 self.restart_state = STATE_RESTART_BOOTING
1038 if self.bootloader:
1039 self.config = self.bootloader_config()
1040 self.construct(self.config)
1041 self.saveToDB()
1042 finally:
1043 self.restart_state = None
1045 def bootloader_config(self):
1046 # if we're restarting with a bootloader, we need to run it
1047 # FIXME: this assumes the disk is the first device and
1048 # that we're booting from the first disk
1049 blcfg = None
1050 # FIXME: this assumes that we want to use the first disk
1051 dev = sxp.child_value(self.config, "device")
1052 if dev:
1053 disk = sxp.child_value(dev, "uname")
1054 fn = blkdev_uname_to_file(disk)
1055 blcfg = bootloader(self.bootloader, fn, 1, self.vcpus)
1056 if blcfg is None:
1057 msg = "Had a bootloader specified, but can't find disk"
1058 log.error(msg)
1059 raise VmError(msg)
1060 config = sxp.merge(['vm', blconfig ], self.config)
1061 return config
1063 def configure_backends(self):
1064 """Set configuration flags if the vm is a backend for netif or blkif.
1065 Configure the backends to use for vbd and vif if specified.
1066 """
1067 for c in sxp.children(self.config, 'backend'):
1068 v = sxp.child0(c)
1069 name = sxp.name(v)
1070 if name == 'blkif':
1071 self.blkif_backend = True
1072 elif name == 'netif':
1073 self.netif_backend = True
1074 elif name == 'usbif':
1075 self.usbif_backend = True
1076 elif name == 'tpmif':
1077 self.tpmif_backend = True
1078 else:
1079 raise VmError('invalid backend type:' + str(name))
1081 def configure(self):
1082 """Configure a vm.
1084 """
1085 self.configure_fields()
1086 self.create_devices()
1087 self.create_blkif()
1089 def create_blkif(self):
1090 """Create the block device interface (blkif) for the vm.
1091 The vm needs a blkif even if it doesn't have any disks
1092 at creation time, for example when it uses NFS root.
1094 """
1095 return
1096 blkif = self.getDeviceController("vbd", error=False)
1097 if not blkif:
1098 blkif = self.createDeviceController("vbd")
1099 backend = blkif.getBackend(0)
1100 backend.connect(recreate=self.recreate)
1102 def publish_console(self):
1103 db = DBMap(db=XenNode("/console/%d" % self.id))
1104 db['domain'] = self.db.getPath()
1105 db.saveDB(save=True)
1107 def configure_fields(self):
1108 """Process the vm configuration fields using the registered handlers.
1109 """
1110 index = {}
1111 for field in sxp.children(self.config):
1112 field_name = sxp.name(field)
1113 field_index = index.get(field_name, 0)
1114 field_handler = get_config_handler(field_name)
1115 # Ignore unknown fields. Warn?
1116 if field_handler:
1117 v = field_handler(self, self.config, field, field_index)
1118 else:
1119 log.warning("Unknown config field %s", field_name)
1120 index[field_name] = field_index + 1
1122 def mem_target_set(self, target):
1123 """Set domain memory target in bytes.
1124 """
1125 if target:
1126 self.target = target * (1 << 20)
1127 # Commit to XenStore immediately
1128 self.exportToDB()
1130 def vcpu_hotplug(self, vcpu, state):
1131 """Disable or enable VCPU in domain.
1132 """
1133 db = ""
1134 try:
1135 db = self.vcpusdb['/cpu/%d'%(vcpu)]
1136 except:
1137 log.error("Invalid VCPU")
1138 return
1140 if self.store_channel:
1141 if int(state) == 0:
1142 db['availability'] = "offline"
1143 else:
1144 db['availability'] = "online"
1146 db.saveDB(save=True)
1148 def shutdown(self, reason):
1149 if not reason in shutdown_reasons.values():
1150 raise XendError('invalid reason:' + reason)
1151 db = self.db.addChild("/control");
1152 db['shutdown'] = reason;
1153 db.saveDB(save=True);
1154 if not reason in ['suspend']:
1155 self.shutdown_pending = {'start':time.time(), 'reason':reason}
1157 def clear_shutdown(self):
1158 db = self.db.addChild("/control")
1159 db['shutdown'] = ""
1160 db.saveDB(save=True)
1162 def send_sysrq(self, key=0):
1163 db = self.db.addChild("/control");
1164 db['sysrq'] = '%c' % key;
1165 db.saveDB(save=True);
1167 def shutdown_time_left(self, timeout):
1168 if not self.shutdown_pending:
1169 return 0
1170 return timeout - (time.time() - self.shutdown_pending['start'])
1172 def dom0_init_store(self):
1173 if not self.store_channel:
1174 self.store_channel = self.eventChannel("store_channel")
1175 self.store_mfn = xc.init_store(self.store_channel.port2)
1176 if self.store_mfn >= 0:
1177 self.db.introduceDomain(self.id, self.store_mfn,
1178 self.store_channel)
1179 self.exportToDB(save=True, sync=True)
1180 # get run-time value of vcpus and update store
1181 self.exportVCPUSToDB(dom_get(self.id)['vcpus'])
1183 def vm_field_ignore(vm, config, val, index):
1184 """Dummy config field handler used for fields with built-in handling.
1186 @param vm: virtual machine
1187 @param config: vm config
1188 @param val: config field
1189 @param index: field index
1190 """
1191 pass
1193 def vm_field_maxmem(vm, config, val, index):
1194 """Configure vm memory limit.
1196 @param vm: virtual machine
1197 @param config: vm config
1198 @param val: config field
1199 @param index: field index
1200 """
1201 maxmem = sxp.child0(val)
1202 if maxmem is None:
1203 maxmem = vm.memory
1204 try:
1205 maxmem = int(maxmem)
1206 except:
1207 raise VmError("invalid maxmem: " + str(maxmem))
1208 xc.domain_setmaxmem(vm.id, maxmem_kb = maxmem * 1024)
1210 #============================================================================
1211 # Register image handlers.
1212 from image import \
1213 addImageHandlerClass, \
1214 ImageHandler, \
1215 LinuxImageHandler, \
1216 VmxImageHandler
1218 addImageHandlerClass(LinuxImageHandler)
1219 addImageHandlerClass(VmxImageHandler)
1221 # Ignore the fields we already handle.
1222 add_config_handler('name', vm_field_ignore)
1223 add_config_handler('memory', vm_field_ignore)
1224 add_config_handler('ssidref', vm_field_ignore)
1225 add_config_handler('cpu', vm_field_ignore)
1226 add_config_handler('cpu_weight', vm_field_ignore)
1227 add_config_handler('restart', vm_field_ignore)
1228 add_config_handler('image', vm_field_ignore)
1229 add_config_handler('device', vm_field_ignore)
1230 add_config_handler('backend', vm_field_ignore)
1231 add_config_handler('vcpus', vm_field_ignore)
1232 add_config_handler('bootloader', vm_field_ignore)
1234 # Register other config handlers.
1235 add_config_handler('maxmem', vm_field_maxmem)
1237 #============================================================================
1238 # Register device controllers and their device config types.
1240 from server import blkif
1241 controller.addDevControllerClass("vbd", blkif.BlkifController)
1242 add_device_handler("vbd", "vbd")
1244 from server import netif
1245 controller.addDevControllerClass("vif", netif.NetifController)
1246 add_device_handler("vif", "vif")
1248 from server import tpmif
1249 controller.addDevControllerClass("vtpm", tpmif.TPMifController)
1250 add_device_handler("vtpm", "vtpm")
1252 from server import pciif
1253 controller.addDevControllerClass("pci", pciif.PciController)
1254 add_device_handler("pci", "pci")
1256 from xen.xend.server import usbif
1257 controller.addDevControllerClass("usb", usbif.UsbifController)
1258 add_device_handler("usb", "usb")
1260 #============================================================================