ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 6689:7d0fb56b4a91

merge?
author cl349@firebug.cl.cam.ac.uk
date Wed Sep 07 19:01:31 2005 +0000 (2005-09-07)
parents 0e2b1e04d4cb 8db9c5873b9b
children 64ca0d3ea97f 7bc32f4c67fb
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.channel import EventChannel
38 from xen.util.blkif import blkdev_name_to_number, expand_dev_name
40 from xen.xend import sxp
41 from xen.xend import Blkctl
42 from xen.xend.PrettyPrint import prettyprintstring
43 from xen.xend.XendBootloader import bootloader
44 from xen.xend.XendLogging import log
45 from xen.xend.XendError import XendError, VmError
46 from xen.xend.XendRoot import get_component
48 from xen.xend.uuid import getUuid
49 from xen.xend.xenstore import DBVar, XenNode, DBMap
51 """Shutdown code for poweroff."""
52 DOMAIN_POWEROFF = 0
54 """Shutdown code for reboot."""
55 DOMAIN_REBOOT = 1
57 """Shutdown code for suspend."""
58 DOMAIN_SUSPEND = 2
60 """Shutdown code for crash."""
61 DOMAIN_CRASH = 3
63 """Map shutdown codes to strings."""
64 shutdown_reasons = {
65 DOMAIN_POWEROFF: "poweroff",
66 DOMAIN_REBOOT : "reboot",
67 DOMAIN_SUSPEND : "suspend",
68 DOMAIN_CRASH : "crash",
69 }
71 RESTART_ALWAYS = 'always'
72 RESTART_ONREBOOT = 'onreboot'
73 RESTART_NEVER = 'never'
75 restart_modes = [
76 RESTART_ALWAYS,
77 RESTART_ONREBOOT,
78 RESTART_NEVER,
79 ]
81 STATE_RESTART_PENDING = 'pending'
82 STATE_RESTART_BOOTING = 'booting'
84 STATE_VM_OK = "ok"
85 STATE_VM_TERMINATED = "terminated"
86 STATE_VM_SUSPENDED = "suspended"
89 def domain_exists(name):
90 # See comment in XendDomain constructor.
91 xd = get_component('xen.xend.XendDomain')
92 return xd.domain_lookup_by_name(name)
94 def shutdown_reason(code):
95 """Get a shutdown reason from a code.
97 @param code: shutdown code
98 @type code: int
99 @return: shutdown reason
100 @rtype: string
101 """
102 return shutdown_reasons.get(code, "?")
104 config_handlers = {}
106 def add_config_handler(name, h):
107 """Add a handler for a config field.
109 @param name: field name
110 @param h: handler: fn(vm, config, field, index)
111 """
112 config_handlers[name] = h
114 def get_config_handler(name):
115 """Get a handler for a config field.
117 returns handler or None
118 """
119 return config_handlers.get(name)
121 """Table of handlers for devices.
122 Indexed by device type.
123 """
124 device_handlers = {}
126 def add_device_handler(name, type):
127 device_handlers[name] = type
129 def get_device_handler(name):
130 return device_handlers[name]
132 def dom_get(dom):
133 """Get info from xen for an existing domain.
135 @param dom: domain id
136 @return: info or None
137 """
138 domlist = xc.domain_getinfo(dom, 1)
139 if domlist and dom == domlist[0]['dom']:
140 return domlist[0]
141 return None
143 class XendDomainInfo:
144 """Virtual machine object."""
146 """Minimum time between domain restarts in seconds.
147 """
148 MINIMUM_RESTART_TIME = 20
150 def create(cls, parentdb, config):
151 """Create a VM from a configuration.
153 @param parentdb: parent db
154 @param config configuration
155 @raise: VmError for invalid configuration
156 """
157 uuid = getUuid()
158 db = parentdb.addChild(uuid)
159 vm = cls(db)
160 vm.construct(config)
161 vm.saveToDB(sync=True)
163 return vm
165 create = classmethod(create)
167 def recreate(cls, db, info):
168 """Create the VM object for an existing domain.
170 @param db: domain db
171 @param info: domain info from xc
172 """
173 dom = info['dom']
174 vm = cls(db)
175 vm.setdom(dom)
176 db.readDB()
177 vm.importFromDB()
178 config = vm.config
179 log.debug('info=' + str(info))
180 log.debug('config=' + prettyprintstring(config))
182 vm.memory = info['mem_kb']/1024
183 vm.target = info['mem_kb'] * 1024
185 if config:
186 try:
187 vm.recreate = True
188 vm.construct(config)
189 finally:
190 vm.recreate = False
191 else:
192 vm.setName("Domain-%d" % dom)
194 vm.exportToDB(save=True)
195 return vm
197 recreate = classmethod(recreate)
199 def restore(cls, parentdb, config, uuid=None):
200 """Create a domain and a VM object to do a restore.
202 @param parentdb: parent db
203 @param config: domain configuration
204 @param uuid: uuid to use
205 """
206 if not uuid:
207 uuid = getUuid()
208 db = parentdb.addChild(uuid)
209 vm = cls(db)
210 ssidref = int(sxp.child_value(config, 'ssidref'))
211 log.debug('restoring with ssidref='+str(ssidref))
212 id = xc.domain_create(ssidref = ssidref)
213 vm.setdom(id)
214 vm.clear_shutdown()
215 try:
216 vm.restore = True
217 vm.construct(config)
218 finally:
219 vm.restore = False
220 vm.exportToDB(save=True, sync=True)
221 return vm
223 restore = classmethod(restore)
225 __exports__ = [
226 DBVar('id', ty='int'),
227 DBVar('name', ty='str'),
228 DBVar('uuid', ty='str'),
229 DBVar('config', ty='sxpr'),
230 DBVar('start_time', ty='float'),
231 DBVar('state', ty='str'),
232 DBVar('store_mfn', ty='long'),
233 DBVar('console_mfn', ty='long', path="console/ring-ref"),
234 DBVar('restart_mode', ty='str'),
235 DBVar('restart_state', ty='str'),
236 DBVar('restart_time', ty='float'),
237 DBVar('restart_count', ty='int'),
238 DBVar('target', ty='long', path="memory/target"),
239 DBVar('device_model_pid', ty='int'),
240 ]
242 def __init__(self, db):
243 self.db = db
244 self.uuid = db.getName()
246 self.recreate = 0
247 self.restore = 0
249 self.config = None
250 self.id = None
251 self.cpu_weight = 1
252 self.start_time = None
253 self.name = None
254 self.memory = None
255 self.ssidref = None
256 self.image = None
258 self.target = None
260 self.store_channel = None
261 self.store_mfn = None
262 self.console_channel = None
263 self.console_mfn = None
264 self.controllers = {}
266 self.info = None
267 self.blkif_backend = False
268 self.netif_backend = False
269 self.netif_idx = 0
270 self.tpmif_backend = False
272 #todo: state: running, suspended
273 self.state = STATE_VM_OK
274 self.state_updated = threading.Condition()
275 self.shutdown_pending = None
277 #todo: set to migrate info if migrating
278 self.migrate = None
280 self.restart_mode = RESTART_ONREBOOT
281 self.restart_state = None
282 self.restart_time = None
283 self.restart_count = 0
285 self.vcpus = 1
286 self.vcpusdb = {}
287 self.bootloader = None
288 self.device_model_pid = 0
290 def setDB(self, db):
291 self.db = db
293 def saveToDB(self, save=False, sync=False):
294 self.db.saveDB(save=save, sync=sync)
296 def exportToDB(self, save=False, sync=False):
297 if self.store_channel:
298 self.store_channel.saveToDB(self.db.addChild("store_channel"),
299 save=save)
300 if self.console_channel:
301 self.console_channel.saveToDB(self.db.addChild("console/console_channel"),
302 save=save)
303 if self.image:
304 self.image.exportToDB(save=save, sync=sync)
305 self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)
307 def importFromDB(self):
308 self.db.importFromDB(self, fields=self.__exports__)
309 self.store_channel = self.eventChannel("store_channel")
311 def setdom(self, dom):
312 """Set the domain id.
314 @param dom: domain id
315 """
316 self.id = int(dom)
317 #self.db.id = self.id
319 def getDomain(self):
320 return self.id
322 def setName(self, name):
323 self.name = name
324 self.db.name = self.name
326 def getName(self):
327 return self.name
329 def getStoreChannel(self):
330 return self.store_channel
332 def getConsoleChannel(self):
333 return self.console_channel
335 def update(self, info=None):
336 """Update with info from xc.domain_getinfo().
337 """
338 self.info = info or dom_get(self.id)
339 self.memory = self.info['mem_kb'] / 1024
340 self.ssidref = self.info['ssidref']
341 self.target = self.info['mem_kb'] * 1024
343 def state_set(self, state):
344 self.state_updated.acquire()
345 if self.state != state:
346 self.state = state
347 self.state_updated.notifyAll()
348 self.state_updated.release()
349 self.saveToDB()
351 def state_wait(self, state):
352 self.state_updated.acquire()
353 while self.state != state:
354 self.state_updated.wait()
355 self.state_updated.release()
357 def __str__(self):
358 s = "<domain"
359 s += " id=" + str(self.id)
360 s += " name=" + self.name
361 s += " memory=" + str(self.memory)
362 s += " ssidref=" + str(self.ssidref)
363 s += ">"
364 return s
366 __repr__ = __str__
368 def getDeviceTypes(self):
369 return self.controllers.keys()
371 def getDeviceControllers(self):
372 return self.controllers.values()
374 def getDeviceController(self, type, error=True):
375 ctrl = self.controllers.get(type)
376 if not ctrl and error:
377 raise XendError("invalid device type:" + type)
378 return ctrl
380 def findDeviceController(self, type):
381 return (self.getDeviceController(type, error=False)
382 or self.createDeviceController(type))
384 def createDeviceController(self, type):
385 ctrl = controller.createDevController(type, self, recreate=self.recreate)
386 self.controllers[type] = ctrl
387 return ctrl
389 def createDevice(self, type, devconfig, change=False):
390 if type == 'vbd':
391 typedev = sxp.child_value(devconfig, 'dev')
392 if re.match('^ioemu:', typedev):
393 return;
394 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
396 devnum = blkdev_name_to_number(sxp.child_value(devconfig, 'dev'))
398 # create backend db
399 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
400 (type, self.uuid, devnum))
402 # create frontend db
403 db = self.db.addChild("/device/%s/%d" % (type, devnum))
405 db['virtual-device'] = "%i" % devnum
406 #db['backend'] = sxp.child_value(devconfig, 'backend', '0')
407 db['backend'] = backdb.getPath()
408 db['backend-id'] = "%i" % backdom.id
410 backdb['frontend'] = db.getPath()
411 (type, params) = string.split(sxp.child_value(devconfig, 'uname'), ':', 1)
412 node = Blkctl.block('bind', type, params)
413 backdb['frontend-id'] = "%i" % self.id
414 backdb['physical-device'] = "%li" % blkdev_name_to_number(node)
415 backdb.saveDB(save=True)
417 # Ok, super gross, this really doesn't belong in the frontend db...
418 db['type'] = type
419 db['node'] = node
420 db['params'] = params
421 db.saveDB(save=True)
423 return
425 if type == 'vif':
426 from xen.xend import XendRoot
427 xroot = XendRoot.instance()
429 def _get_config_ipaddr(config):
430 val = []
431 for ipaddr in sxp.children(config, elt='ip'):
432 val.append(sxp.child0(ipaddr))
433 return val
435 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
437 log.error(devconfig)
439 devnum = self.netif_idx
440 self.netif_idx += 1
442 script = sxp.child_value(devconfig, 'script',
443 xroot.get_vif_script())
444 script = os.path.join(xroot.network_script_dir, script)
445 bridge = sxp.child_value(devconfig, 'bridge',
446 xroot.get_vif_bridge())
447 mac = sxp.child_value(devconfig, 'mac')
448 ipaddr = _get_config_ipaddr(devconfig)
450 # create backend db
451 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
452 (type, self.uuid, devnum))
454 # create frontend db
455 db = self.db.addChild("/device/%s/%d" % (type, devnum))
457 backdb['script'] = script
458 backdb['domain'] = self.name
459 backdb['mac'] = mac
460 backdb['bridge'] = bridge
461 if ipaddr:
462 backdb['ip'] = ' '.join(ipaddr)
463 backdb['frontend'] = db.getPath()
464 backdb['frontend-id'] = "%i" % self.id
465 backdb['handle'] = "%i" % devnum
466 backdb.saveDB(save=True)
468 db['backend'] = backdb.getPath()
469 db['backend-id'] = "%i" % backdom.id
470 db['handle'] = "%i" % devnum
471 db['mac'] = mac
473 db.saveDB(save=True)
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 # create backend db
484 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
485 (type, self.uuid, devnum))
486 # create frontend db
487 db = self.db.addChild("/device/%s/%d" % (type, devnum))
489 backdb['frontend'] = db.getPath()
490 backdb['frontend-id'] = "%i" % self.id
491 backdb['instance'] = sxp.child_value(devconfig, 'instance', '0')
492 backdb.saveDB(save=True)
494 db['handle'] = "%i" % devnum
495 db['backend'] = backdb.getPath()
496 db['backend-id'] = "%i" % int(sxp.child_value(devconfig,
497 'backend', '0'))
498 db.saveDB(save=True)
500 return
502 ctrl = self.findDeviceController(type)
503 return ctrl.createDevice(devconfig, recreate=self.recreate,
504 change=change)
506 def configureDevice(self, type, id, devconfig):
507 ctrl = self.getDeviceController(type)
508 return ctrl.configureDevice(id, devconfig)
510 def destroyDevice(self, type, id, change=False, reboot=False):
511 ctrl = self.getDeviceController(type)
512 return ctrl.destroyDevice(id, change=change, reboot=reboot)
514 def deleteDevice(self, type, id):
515 ctrl = self.getDeviceController(type)
516 return ctrl.deleteDevice(id)
518 def getDevice(self, type, id, error=True):
519 ctrl = self.getDeviceController(type)
520 return ctrl.getDevice(id, error=error)
522 def getDeviceIds(self, type):
523 ctrl = self.getDeviceController(type)
524 return ctrl.getDeviceIds()
526 def getDeviceSxprs(self, type):
527 ctrl = self.getDeviceController(type)
528 return ctrl.getDeviceSxprs()
530 def sxpr(self):
531 sxpr = ['domain',
532 ['id', self.id],
533 ['name', self.name],
534 ['memory', self.memory],
535 ['ssidref', self.ssidref],
536 ['target', self.target] ]
537 if self.uuid:
538 sxpr.append(['uuid', self.uuid])
539 if self.info:
540 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
541 run = (self.info['running'] and 'r') or '-'
542 block = (self.info['blocked'] and 'b') or '-'
543 pause = (self.info['paused'] and 'p') or '-'
544 shut = (self.info['shutdown'] and 's') or '-'
545 crash = (self.info['crashed'] and 'c') or '-'
546 state = run + block + pause + shut + crash
547 sxpr.append(['state', state])
548 if self.info['shutdown']:
549 reason = shutdown_reason(self.info['shutdown_reason'])
550 sxpr.append(['shutdown_reason', reason])
551 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
552 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
553 sxpr.append(['vcpus', self.info['vcpus']])
554 sxpr.append(['cpumap', self.info['cpumap']])
555 # build a string, using '|' to seperate items, show only up
556 # to number of vcpus in domain, and trim the trailing '|'
557 sxpr.append(['vcpu_to_cpu', ''.join(map(lambda x: str(x)+'|',
558 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))[:-1]])
560 if self.start_time:
561 up_time = time.time() - self.start_time
562 sxpr.append(['up_time', str(up_time) ])
563 sxpr.append(['start_time', str(self.start_time) ])
565 if self.store_channel:
566 sxpr.append(self.store_channel.sxpr())
567 if self.store_mfn:
568 sxpr.append(['store_mfn', self.store_mfn])
569 if self.console_channel:
570 sxpr.append(['console_channel', self.console_channel.sxpr()])
571 if self.console_mfn:
572 sxpr.append(['console_mfn', self.console_mfn])
573 # already in (devices)
574 # console = self.getConsole()
575 # if console:
576 # sxpr.append(console.sxpr())
578 if self.restart_count:
579 sxpr.append(['restart_count', self.restart_count])
580 if self.restart_state:
581 sxpr.append(['restart_state', self.restart_state])
582 if self.restart_time:
583 sxpr.append(['restart_time', str(self.restart_time)])
585 devs = self.sxpr_devices()
586 if devs:
587 sxpr.append(devs)
588 if self.config:
589 sxpr.append(['config', self.config])
590 if self.device_model_pid:
591 sxpr.append(['device_model_pid',self.device_model_pid])
592 return sxpr
594 def sxpr_devices(self):
595 sxpr = []
596 for ty in self.getDeviceTypes():
597 devs = self.getDeviceSxprs(ty)
598 sxpr += devs
599 if sxpr:
600 sxpr.insert(0, 'devices')
601 else:
602 sxpr = None
603 return sxpr
605 def check_name(self, name):
606 """Check if a vm name is valid. Valid names contain alphabetic characters,
607 digits, or characters in '_-.:/+'.
608 The same name cannot be used for more than one vm at the same time.
610 @param name: name
611 @raise: VMerror if invalid
612 """
613 if self.recreate: return
614 if name is None or name == '':
615 raise VmError('missing vm name')
616 for c in name:
617 if c in string.digits: continue
618 if c in '_-.:/+': continue
619 if c in string.ascii_letters: continue
620 raise VmError('invalid vm name')
621 dominfo = domain_exists(name)
622 # When creating or rebooting, a domain with my name should not exist.
623 # When restoring, a domain with my name will exist, but it should have
624 # my domain id.
625 if not dominfo:
626 return
627 if dominfo.is_terminated():
628 return
629 if not self.id or (dominfo.id != self.id):
630 raise VmError('vm name clash: ' + name)
632 def construct(self, config):
633 """Construct the vm instance from its configuration.
635 @param config: configuration
636 @raise: VmError on error
637 """
638 # todo - add support for scheduling params?
639 self.config = config
640 try:
641 # Initial domain create.
642 self.setName(sxp.child_value(config, 'name'))
643 self.check_name(self.name)
644 self.init_image()
645 self.configure_cpus(config)
646 self.init_domain()
647 self.register_domain()
648 self.configure_bootloader()
650 # Create domain devices.
651 self.configure_backends()
652 self.configure_restart()
653 self.construct_image()
654 self.configure()
655 self.publish_console()
656 self.exportToDB(save=True)
657 except Exception, ex:
658 # Catch errors, cleanup and re-raise.
659 print 'Domain construction error:', ex
660 import traceback
661 traceback.print_exc()
662 self.destroy()
663 raise
665 def register_domain(self):
666 xd = get_component('xen.xend.XendDomain')
667 xd._add_domain(self)
668 self.exportToDB(save=True)
670 def configure_cpus(self, config):
671 try:
672 self.cpu_weight = float(sxp.child_value(config, 'cpu_weight', '1'))
673 except:
674 raise VmError('invalid cpu weight')
675 self.memory = int(sxp.child_value(config, 'memory'))
676 if self.memory is None:
677 raise VmError('missing memory size')
678 self.target = self.memory * (1 << 20)
679 self.ssidref = int(sxp.child_value(config, 'ssidref'))
680 cpu = sxp.child_value(config, 'cpu')
681 if self.recreate and self.id and cpu is not None and int(cpu) >= 0:
682 xc.domain_pincpu(self.id, 0, 1<<int(cpu))
683 try:
684 image = sxp.child_value(self.config, 'image')
685 vcpus = sxp.child_value(image, 'vcpus')
686 if vcpus:
687 self.vcpus = int(vcpus)
688 except:
689 raise VmError('invalid vcpus value')
691 def exportVCPUSToDB(self, vcpus):
692 for v in range(0,vcpus):
693 path = "/cpu/%d"%(v)
694 if not self.vcpusdb.has_key(path):
695 self.vcpusdb[path] = self.db.addChild(path)
696 db = self.vcpusdb[path]
697 log.debug("writing key availability=online to path %s in store"%(path))
698 db['availability'] = "online"
699 db.saveDB(save=True)
701 def init_image(self):
702 """Create boot image handler for the domain.
703 """
704 image = sxp.child_value(self.config, 'image')
705 if image is None:
706 raise VmError('missing image')
707 self.image = ImageHandler.create(self, image)
709 def construct_image(self):
710 """Construct the boot image for the domain.
711 """
712 self.create_channel()
713 self.image.createImage()
714 self.exportToDB()
715 if self.store_channel and self.store_mfn >= 0:
716 self.db.introduceDomain(self.id,
717 self.store_mfn,
718 self.store_channel)
719 # get the configured value of vcpus and update store
720 self.exportVCPUSToDB(self.vcpus)
722 def delete(self):
723 """Delete the vm's db.
724 """
725 if dom_get(self.id):
726 return
727 self.id = None
728 self.saveToDB(sync=True)
729 try:
730 # Todo: eventually will have to wait for devices to signal
731 # destruction before can delete the db.
732 if self.db:
733 self.db.delete()
734 except Exception, ex:
735 log.warning("error in domain db delete: %s", ex)
736 pass
738 def destroy_domain(self):
739 """Destroy the vm's domain.
740 The domain will not finally go away unless all vm
741 devices have been released.
742 """
743 if self.id is None:
744 return
745 try:
746 xc.domain_destroy(dom=self.id)
747 except Exception, err:
748 log.exception("Domain destroy failed: %s", self.name)
750 def cleanup(self):
751 """Cleanup vm resources: release devices.
752 """
753 self.state = STATE_VM_TERMINATED
754 self.release_devices()
755 if self.store_channel:
756 try:
757 self.store_channel.close()
758 self.store_channel = None
759 except:
760 pass
761 try:
762 self.db.releaseDomain(self.id)
763 except Exception, ex:
764 log.warning("error in domain release on xenstore: %s", ex)
765 pass
766 if self.console_channel:
767 # notify processes using this cosole?
768 try:
769 self.console_channel.close()
770 self.console_channel = None
771 except:
772 pass
773 if self.image:
774 try:
775 self.device_model_pid = 0
776 self.image.destroy()
777 self.image = None
778 except:
779 pass
781 def destroy(self):
782 """Clenup vm and destroy domain.
783 """
784 self.destroy_domain()
785 self.cleanup()
786 self.saveToDB()
787 return 0
789 def is_terminated(self):
790 """Check if a domain has been terminated.
791 """
792 return self.state == STATE_VM_TERMINATED
794 def release_devices(self):
795 """Release all vm devices.
796 """
797 reboot = self.restart_pending()
798 for ctrl in self.getDeviceControllers():
799 if ctrl.isDestroyed(): continue
800 ctrl.destroyController(reboot=reboot)
801 ddb = self.db.addChild("/device")
802 for type in ddb.keys():
803 if type == 'vbd':
804 typedb = ddb.addChild(type)
805 for dev in typedb.keys():
806 devdb = typedb.addChild(str(dev))
807 Blkctl.block('unbind', devdb['type'].getData(),
808 devdb['node'].getData())
809 typedb[dev].delete()
810 typedb.saveDB(save=True)
811 if type == 'vif':
812 typedb = ddb.addChild(type)
813 for dev in typedb.keys():
814 typedb[dev].delete()
815 typedb.saveDB(save=True)
816 if type == 'vtpm':
817 typedb = ddb.addChild(type)
818 for dev in typedb.keys():
819 typedb[dev].delete()
820 typedb.saveDB(save=True)
822 def show(self):
823 """Print virtual machine info.
824 """
825 print "[VM dom=%d name=%s memory=%d ssidref=%d" % (self.id, self.name, self.memory, self.ssidref)
826 print "image:"
827 sxp.show(self.image)
828 print "]"
830 def init_domain(self):
831 """Initialize the domain memory.
832 """
833 if self.recreate:
834 return
835 if self.start_time is None:
836 self.start_time = time.time()
837 try:
838 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
839 except:
840 raise VmError('invalid cpu')
841 id = self.image.initDomain(self.id, self.memory, self.ssidref, cpu, self.cpu_weight)
842 log.debug('init_domain> Created domain=%d name=%s memory=%d',
843 id, self.name, self.memory)
844 self.setdom(id)
846 def eventChannel(self, key):
847 """Create an event channel to the domain.
848 If saved info is available recreate the channel.
850 @param key db key for the saved data (if any)
851 """
852 db = self.db.addChild(key)
853 return EventChannel.restoreFromDB(db, 0, self.id)
855 def create_channel(self):
856 """Create the channels to the domain.
857 """
858 self.store_channel = self.eventChannel("store_channel")
859 self.console_channel = self.eventChannel("console/console_channel")
861 def create_configured_devices(self):
862 devices = sxp.children(self.config, 'device')
863 for d in devices:
864 dev_config = sxp.child0(d)
865 if dev_config is None:
866 raise VmError('invalid device')
867 dev_type = sxp.name(dev_config)
868 ctrl_type = get_device_handler(dev_type)
869 if ctrl_type is None:
870 raise VmError('unknown device type: ' + dev_type)
871 self.createDevice(ctrl_type, dev_config)
873 def create_devices(self):
874 """Create the devices for a vm.
876 @raise: VmError for invalid devices
877 """
878 if self.rebooting():
879 for ctrl in self.getDeviceControllers():
880 ctrl.initController(reboot=True)
881 else:
882 self.create_configured_devices()
883 if not self.device_model_pid:
884 self.device_model_pid = self.image.createDeviceModel()
886 def device_create(self, dev_config):
887 """Create a new device.
889 @param dev_config: device configuration
890 """
891 dev_type = sxp.name(dev_config)
892 dev = self.createDevice(dev_type, dev_config, change=True)
893 self.config.append(['device', dev.getConfig()])
894 return dev.sxpr()
896 def device_configure(self, dev_config, id):
897 """Configure an existing device.
899 @param dev_config: device configuration
900 @param id: device id
901 """
902 type = sxp.name(dev_config)
903 dev = self.getDevice(type, id)
904 old_config = dev.getConfig()
905 new_config = dev.configure(dev_config, change=True)
906 # Patch new config into vm config.
907 new_full_config = ['device', new_config]
908 old_full_config = ['device', old_config]
909 old_index = self.config.index(old_full_config)
910 self.config[old_index] = new_full_config
911 return new_config
913 def device_refresh(self, type, id):
914 """Refresh a device.
916 @param type: device type
917 @param id: device id
918 """
919 dev = self.getDevice(type, id)
920 dev.refresh()
922 def device_delete(self, type, id):
923 """Destroy and remove a device.
925 @param type: device type
926 @param id: device id
927 """
928 dev = self.getDevice(type, id)
929 dev_config = dev.getConfig()
930 if dev_config:
931 self.config.remove(['device', dev_config])
932 self.deleteDevice(type, dev.getId())
934 def configure_bootloader(self):
935 """Configure boot loader.
936 """
937 self.bootloader = sxp.child_value(self.config, "bootloader")
939 def configure_restart(self):
940 """Configure the vm restart mode.
941 """
942 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
943 if r not in restart_modes:
944 raise VmError('invalid restart mode: ' + str(r))
945 self.restart_mode = r;
947 def restart_needed(self, reason):
948 """Determine if the vm needs to be restarted when shutdown
949 for the given reason.
951 @param reason: shutdown reason
952 @return True if needs restart, False otherwise
953 """
954 if self.restart_mode == RESTART_NEVER:
955 return False
956 if self.restart_mode == RESTART_ALWAYS:
957 return True
958 if self.restart_mode == RESTART_ONREBOOT:
959 return reason == 'reboot'
960 return False
962 def restart_cancel(self):
963 """Cancel a vm restart.
964 """
965 self.restart_state = None
967 def restarting(self):
968 """Put the vm into restart mode.
969 """
970 self.restart_state = STATE_RESTART_PENDING
972 def restart_pending(self):
973 """Test if the vm has a pending restart.
974 """
975 return self.restart_state == STATE_RESTART_PENDING
977 def rebooting(self):
978 return self.restart_state == STATE_RESTART_BOOTING
980 def restart_check(self):
981 """Check if domain restart is OK.
982 To prevent restart loops, raise an error if it is
983 less than MINIMUM_RESTART_TIME seconds since the last restart.
984 """
985 tnow = time.time()
986 if self.restart_time is not None:
987 tdelta = tnow - self.restart_time
988 if tdelta < self.MINIMUM_RESTART_TIME:
989 self.restart_cancel()
990 msg = 'VM %s restarting too fast' % self.name
991 log.error(msg)
992 raise VmError(msg)
993 self.restart_time = tnow
994 self.restart_count += 1
996 def restart(self):
997 """Restart the domain after it has exited.
998 Reuses the domain id
1000 """
1001 try:
1002 self.clear_shutdown()
1003 self.state = STATE_VM_OK
1004 self.shutdown_pending = None
1005 self.restart_check()
1006 self.exportToDB()
1007 self.restart_state = STATE_RESTART_BOOTING
1008 if self.bootloader:
1009 self.config = self.bootloader_config()
1010 self.construct(self.config)
1011 self.saveToDB()
1012 finally:
1013 self.restart_state = None
1015 def bootloader_config(self):
1016 # if we're restarting with a bootloader, we need to run it
1017 # FIXME: this assumes the disk is the first device and
1018 # that we're booting from the first disk
1019 blcfg = None
1020 # FIXME: this assumes that we want to use the first disk
1021 dev = sxp.child_value(self.config, "device")
1022 if dev:
1023 disk = sxp.child_value(dev, "uname")
1024 fn = blkdev_uname_to_file(disk)
1025 blcfg = bootloader(self.bootloader, fn, 1, self.vcpus)
1026 if blcfg is None:
1027 msg = "Had a bootloader specified, but can't find disk"
1028 log.error(msg)
1029 raise VmError(msg)
1030 config = sxp.merge(['vm', blconfig ], self.config)
1031 return config
1033 def configure_backends(self):
1034 """Set configuration flags if the vm is a backend for netif or blkif.
1035 Configure the backends to use for vbd and vif if specified.
1036 """
1037 for c in sxp.children(self.config, 'backend'):
1038 v = sxp.child0(c)
1039 name = sxp.name(v)
1040 if name == 'blkif':
1041 self.blkif_backend = True
1042 elif name == 'netif':
1043 self.netif_backend = True
1044 elif name == 'usbif':
1045 self.usbif_backend = True
1046 elif name == 'tpmif':
1047 self.tpmif_backend = True
1048 else:
1049 raise VmError('invalid backend type:' + str(name))
1051 def configure(self):
1052 """Configure a vm.
1054 """
1055 self.configure_fields()
1056 self.create_devices()
1057 self.create_blkif()
1059 def create_blkif(self):
1060 """Create the block device interface (blkif) for the vm.
1061 The vm needs a blkif even if it doesn't have any disks
1062 at creation time, for example when it uses NFS root.
1064 """
1065 return
1066 blkif = self.getDeviceController("vbd", error=False)
1067 if not blkif:
1068 blkif = self.createDeviceController("vbd")
1069 backend = blkif.getBackend(0)
1070 backend.connect(recreate=self.recreate)
1072 def publish_console(self):
1073 db = DBMap(db=XenNode("/console/%d" % self.id))
1074 db['domain'] = self.db.getPath()
1075 db.saveDB(save=True)
1077 def configure_fields(self):
1078 """Process the vm configuration fields using the registered handlers.
1079 """
1080 index = {}
1081 for field in sxp.children(self.config):
1082 field_name = sxp.name(field)
1083 field_index = index.get(field_name, 0)
1084 field_handler = get_config_handler(field_name)
1085 # Ignore unknown fields. Warn?
1086 if field_handler:
1087 v = field_handler(self, self.config, field, field_index)
1088 else:
1089 log.warning("Unknown config field %s", field_name)
1090 index[field_name] = field_index + 1
1092 def mem_target_set(self, target):
1093 """Set domain memory target in bytes.
1094 """
1095 if target:
1096 self.target = target * (1 << 20)
1097 # Commit to XenStore immediately
1098 self.exportToDB()
1100 def vcpu_hotplug(self, vcpu, state):
1101 """Disable or enable VCPU in domain.
1102 """
1103 db = ""
1104 try:
1105 db = self.vcpusdb['/cpu/%d'%(vcpu)]
1106 except:
1107 log.error("Invalid VCPU")
1108 return
1110 if self.store_channel:
1111 if int(state) == 0:
1112 db['availability'] = "offline"
1113 else:
1114 db['availability'] = "online"
1116 db.saveDB(save=True)
1118 def shutdown(self, reason):
1119 if not reason in shutdown_reasons.values():
1120 raise XendError('invalid reason:' + reason)
1121 db = self.db.addChild("/control");
1122 db['shutdown'] = reason;
1123 db.saveDB(save=True);
1124 if not reason in ['suspend']:
1125 self.shutdown_pending = {'start':time.time(), 'reason':reason}
1127 def clear_shutdown(self):
1128 db = self.db.addChild("/control")
1129 db['shutdown'] = ""
1130 db.saveDB(save=True)
1132 def send_sysrq(self, key=0):
1133 db = self.db.addChild("/control");
1134 db['sysrq'] = '%c' % key;
1135 db.saveDB(save=True);
1137 def shutdown_time_left(self, timeout):
1138 if not self.shutdown_pending:
1139 return 0
1140 return timeout - (time.time() - self.shutdown_pending['start'])
1142 def dom0_init_store(self):
1143 if not self.store_channel:
1144 self.store_channel = self.eventChannel("store_channel")
1145 self.store_mfn = xc.init_store(self.store_channel.port2)
1146 if self.store_mfn >= 0:
1147 self.db.introduceDomain(self.id, self.store_mfn,
1148 self.store_channel)
1149 self.exportToDB(save=True, sync=True)
1150 # get run-time value of vcpus and update store
1151 self.exportVCPUSToDB(dom_get(self.id)['vcpus'])
1153 def vm_field_ignore(vm, config, val, index):
1154 """Dummy config field handler used for fields with built-in handling.
1156 @param vm: virtual machine
1157 @param config: vm config
1158 @param val: config field
1159 @param index: field index
1160 """
1161 pass
1163 def vm_field_maxmem(vm, config, val, index):
1164 """Configure vm memory limit.
1166 @param vm: virtual machine
1167 @param config: vm config
1168 @param val: config field
1169 @param index: field index
1170 """
1171 maxmem = sxp.child0(val)
1172 if maxmem is None:
1173 maxmem = vm.memory
1174 try:
1175 maxmem = int(maxmem)
1176 except:
1177 raise VmError("invalid maxmem: " + str(maxmem))
1178 xc.domain_setmaxmem(vm.id, maxmem_kb = maxmem * 1024)
1180 #============================================================================
1181 # Register image handlers.
1182 from image import \
1183 addImageHandlerClass, \
1184 ImageHandler, \
1185 LinuxImageHandler, \
1186 VmxImageHandler
1188 addImageHandlerClass(LinuxImageHandler)
1189 addImageHandlerClass(VmxImageHandler)
1191 # Ignore the fields we already handle.
1192 add_config_handler('name', vm_field_ignore)
1193 add_config_handler('memory', vm_field_ignore)
1194 add_config_handler('ssidref', vm_field_ignore)
1195 add_config_handler('cpu', vm_field_ignore)
1196 add_config_handler('cpu_weight', vm_field_ignore)
1197 add_config_handler('restart', vm_field_ignore)
1198 add_config_handler('image', vm_field_ignore)
1199 add_config_handler('device', vm_field_ignore)
1200 add_config_handler('backend', vm_field_ignore)
1201 add_config_handler('vcpus', vm_field_ignore)
1202 add_config_handler('bootloader', vm_field_ignore)
1204 # Register other config handlers.
1205 add_config_handler('maxmem', vm_field_maxmem)
1207 #============================================================================
1208 # Register device controllers and their device config types.
1210 from server import blkif
1211 controller.addDevControllerClass("vbd", blkif.BlkifController)
1212 add_device_handler("vbd", "vbd")
1214 from server import netif
1215 controller.addDevControllerClass("vif", netif.NetifController)
1216 add_device_handler("vif", "vif")
1218 from server import tpmif
1219 controller.addDevControllerClass("vtpm", tpmif.TPMifController)
1220 add_device_handler("vtpm", "vtpm")
1222 from server import pciif
1223 controller.addDevControllerClass("pci", pciif.PciController)
1224 add_device_handler("pci", "pci")
1226 from xen.xend.server import usbif
1227 controller.addDevControllerClass("usb", usbif.UsbifController)
1228 add_device_handler("usb", "usb")
1230 #============================================================================