ia64/xen-unstable

view tools/python/xen/xend/XendDomainInfo.py @ 6795:3cc679a35d71

Fix imports.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@ewan
date Tue Sep 13 16:44:48 2005 +0100 (2005-09-13)
parents c5045197dcb7
children e7c7196fa329
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
50 from xen.xend.xenstore.xstransact import xstransact
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 def dom_get(dom):
106 """Get info from xen for an existing domain.
108 @param dom: domain id
109 @return: info or None
110 """
111 domlist = xc.domain_getinfo(dom, 1)
112 if domlist and dom == domlist[0]['dom']:
113 return domlist[0]
114 return None
116 class XendDomainInfo:
117 """Virtual machine object."""
119 """Minimum time between domain restarts in seconds.
120 """
121 MINIMUM_RESTART_TIME = 20
123 def create(cls, parentdb, config):
124 """Create a VM from a configuration.
126 @param parentdb: parent db
127 @param config configuration
128 @raise: VmError for invalid configuration
129 """
130 uuid = getUuid()
131 db = parentdb.addChild(uuid)
132 path = parentdb.getPath()
133 vm = cls(uuid, path, db)
134 vm.construct(config)
135 vm.saveToDB(sync=True)
137 return vm
139 create = classmethod(create)
141 def recreate(cls, db, info):
142 """Create the VM object for an existing domain.
144 @param db: domain db
145 @param info: domain info from xc
146 """
147 dom = info['dom']
148 path = "/".join(db.getPath().split("/")[0:-1])
149 vm = cls(db.getName(), path, db)
150 vm.setdom(dom)
151 db.readDB()
152 vm.importFromDB()
153 config = vm.config
154 log.debug('info=' + str(info))
155 log.debug('config=' + prettyprintstring(config))
157 vm.memory = info['mem_kb']/1024
158 vm.target = info['mem_kb'] * 1024
160 if config:
161 try:
162 vm.recreate = True
163 vm.construct(config)
164 finally:
165 vm.recreate = False
166 else:
167 vm.setName("Domain-%d" % dom)
169 vm.exportToDB(save=True)
170 return vm
172 recreate = classmethod(recreate)
174 def restore(cls, parentdb, config, uuid=None):
175 """Create a domain and a VM object to do a restore.
177 @param parentdb: parent db
178 @param config: domain configuration
179 @param uuid: uuid to use
180 """
181 if not uuid:
182 uuid = getUuid()
183 db = parentdb.addChild(uuid)
184 path = parentdb.getPath()
185 vm = cls(uuid, path, db)
186 ssidref = int(sxp.child_value(config, 'ssidref'))
187 log.debug('restoring with ssidref='+str(ssidref))
188 id = xc.domain_create(ssidref = ssidref)
189 vm.setdom(id)
190 vm.clear_shutdown()
191 try:
192 vm.restore = True
193 vm.construct(config)
194 finally:
195 vm.restore = False
196 vm.exportToDB(save=True, sync=True)
197 return vm
199 restore = classmethod(restore)
201 __exports__ = [
202 DBVar('id', ty='int'),
203 DBVar('name', ty='str'),
204 DBVar('uuid', ty='str'),
205 DBVar('config', ty='sxpr'),
206 DBVar('start_time', ty='float'),
207 DBVar('state', ty='str'),
208 DBVar('store_mfn', ty='long'),
209 DBVar('console_mfn', ty='long', path="console/ring-ref"),
210 DBVar('restart_mode', ty='str'),
211 DBVar('restart_state', ty='str'),
212 DBVar('restart_time', ty='float'),
213 DBVar('restart_count', ty='int'),
214 DBVar('target', ty='long', path="memory/target"),
215 DBVar('device_model_pid', ty='int'),
216 ]
218 def __init__(self, uuid, path, db):
219 self.uuid = uuid
220 self.path = path + "/" + uuid
221 self.db = db
223 self.recreate = 0
224 self.restore = 0
226 self.config = None
227 self.id = None
228 self.cpu_weight = 1
229 self.start_time = None
230 self.name = None
231 self.memory = None
232 self.ssidref = None
233 self.image = None
235 self.target = None
237 self.store_channel = None
238 self.store_mfn = None
239 self.console_channel = None
240 self.console_mfn = None
241 self.controllers = {}
243 self.info = None
244 self.blkif_backend = False
245 self.netif_backend = False
246 self.netif_idx = 0
247 self.tpmif_backend = False
249 #todo: state: running, suspended
250 self.state = STATE_VM_OK
251 self.state_updated = threading.Condition()
252 self.shutdown_pending = None
254 #todo: set to migrate info if migrating
255 self.migrate = None
257 self.restart_mode = RESTART_ONREBOOT
258 self.restart_state = None
259 self.restart_time = None
260 self.restart_count = 0
262 self.vcpus = 1
263 self.vcpusdb = {}
264 self.bootloader = None
265 self.device_model_pid = 0
267 def setDB(self, db):
268 self.db = db
270 def saveToDB(self, save=False, sync=False):
271 self.db.saveDB(save=save, sync=sync)
273 def exportToDB(self, save=False, sync=False):
274 if self.store_channel:
275 self.store_channel.saveToDB(self.db.addChild("store_channel"),
276 save=save)
277 if self.console_channel:
278 self.db['console/port'] = "%i" % self.console_channel.port1
279 if self.image:
280 self.image.exportToDB(save=save, sync=sync)
281 self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync)
283 def importFromDB(self):
284 self.db.importFromDB(self, fields=self.__exports__)
285 self.store_channel = self.eventChannelOld("store_channel")
287 def setdom(self, dom):
288 """Set the domain id.
290 @param dom: domain id
291 """
292 self.id = int(dom)
293 #self.db.id = self.id
295 def getDomain(self):
296 return self.id
298 def setName(self, name):
299 self.name = name
300 self.db.name = self.name
302 def getName(self):
303 return self.name
305 def getStoreChannel(self):
306 return self.store_channel
308 def getConsoleChannel(self):
309 return self.console_channel
311 def update(self, info=None):
312 """Update with info from xc.domain_getinfo().
313 """
314 self.info = info or dom_get(self.id)
315 self.memory = self.info['mem_kb'] / 1024
316 self.ssidref = self.info['ssidref']
317 self.target = self.info['mem_kb'] * 1024
319 def state_set(self, state):
320 self.state_updated.acquire()
321 if self.state != state:
322 self.state = state
323 self.state_updated.notifyAll()
324 self.state_updated.release()
325 self.saveToDB()
327 def state_wait(self, state):
328 self.state_updated.acquire()
329 while self.state != state:
330 self.state_updated.wait()
331 self.state_updated.release()
333 def __str__(self):
334 s = "<domain"
335 s += " id=" + str(self.id)
336 s += " name=" + self.name
337 s += " memory=" + str(self.memory)
338 s += " ssidref=" + str(self.ssidref)
339 s += ">"
340 return s
342 __repr__ = __str__
344 def getDeviceController(self, type, error=True):
345 ctrl = self.controllers.get(type)
346 if not ctrl and error:
347 raise XendError("invalid device type:" + type)
348 return ctrl
350 def findDeviceController(self, type):
351 return (self.getDeviceController(type, error=False)
352 or self.createDeviceController(type))
354 def createDeviceController(self, type):
355 ctrl = controller.createDevController(type, self, recreate=self.recreate)
356 self.controllers[type] = ctrl
357 return ctrl
359 def createDevice(self, type, devconfig, change=False):
360 if self.recreate:
361 return
362 if type == 'vbd':
363 typedev = sxp.child_value(devconfig, 'dev')
364 if re.match('^ioemu:', typedev):
365 return;
366 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
368 devnum = blkdev_name_to_number(sxp.child_value(devconfig, 'dev'))
370 # create backend db
371 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
372 (type, self.uuid, devnum))
374 # create frontend db
375 db = self.db.addChild("/device/%s/%d" % (type, devnum))
377 db['virtual-device'] = "%i" % devnum
378 #db['backend'] = sxp.child_value(devconfig, 'backend', '0')
379 db['backend'] = backdb.getPath()
380 db['backend-id'] = "%i" % backdom.id
382 backdb['frontend'] = db.getPath()
383 (type, params) = string.split(sxp.child_value(devconfig, 'uname'), ':', 1)
384 node = Blkctl.block('bind', type, params)
385 backdb['frontend-id'] = "%i" % self.id
386 backdb['physical-device'] = "%li" % blkdev_name_to_number(node)
387 backdb.saveDB(save=True)
389 # Ok, super gross, this really doesn't belong in the frontend db...
390 db['type'] = type
391 db['node'] = node
392 db['params'] = params
393 db.saveDB(save=True)
395 return
397 if type == 'vif':
398 from xen.xend import XendRoot
399 xroot = XendRoot.instance()
401 def _get_config_ipaddr(config):
402 val = []
403 for ipaddr in sxp.children(config, elt='ip'):
404 val.append(sxp.child0(ipaddr))
405 return val
407 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
409 log.error(devconfig)
411 devnum = self.netif_idx
412 self.netif_idx += 1
414 script = sxp.child_value(devconfig, 'script',
415 xroot.get_vif_script())
416 script = os.path.join(xroot.network_script_dir, script)
417 bridge = sxp.child_value(devconfig, 'bridge',
418 xroot.get_vif_bridge())
419 mac = sxp.child_value(devconfig, 'mac')
420 ipaddr = _get_config_ipaddr(devconfig)
422 backpath = "%s/backend/%s/%s/%d" % (backdom.path, type,
423 self.uuid, devnum)
424 frontpath = "%s/device/%s/%d" % (self.path, type, devnum)
426 front = { 'backend' : backpath,
427 'backend-id' : "%i" % backdom.id,
428 'handle' : "%i" % devnum,
429 'mac' : mac }
430 xstransact.Write(frontpath, front)
432 back = { 'script' : script,
433 'domain' : self.name,
434 'mac' : mac,
435 'bridge' : bridge,
436 'frontend' : frontpath,
437 'frontend-id' : "%i" % self.id,
438 'handle' : "%i" % devnum }
439 if ipaddr:
440 back['ip'] = ' '.join(ipaddr)
441 xstransact.Write(backpath, back)
443 return
445 if type == 'vtpm':
446 backdom = domain_exists(sxp.child_value(devconfig, 'backend', '0'))
448 devnum = int(sxp.child_value(devconfig, 'instance', '0'))
449 log.error("The domain has a TPM with instance %d." % devnum)
451 # create backend db
452 backdb = backdom.db.addChild("/backend/%s/%s/%d" %
453 (type, self.uuid, devnum))
454 # create frontend db
455 db = self.db.addChild("/device/%s/%d" % (type, devnum))
457 backdb['frontend'] = db.getPath()
458 backdb['frontend-id'] = "%i" % self.id
459 backdb['instance'] = sxp.child_value(devconfig, 'instance', '0')
460 backdb.saveDB(save=True)
462 db['handle'] = "%i" % devnum
463 db['backend'] = backdb.getPath()
464 db['backend-id'] = "%i" % int(sxp.child_value(devconfig,
465 'backend', '0'))
466 db.saveDB(save=True)
468 return
470 ctrl = self.findDeviceController(type)
471 return ctrl.createDevice(devconfig, recreate=self.recreate,
472 change=change)
474 def configureDevice(self, type, id, devconfig):
475 ctrl = self.getDeviceController(type)
476 return ctrl.configureDevice(id, devconfig)
478 def destroyDevice(self, type, id, change=False, reboot=False):
479 ctrl = self.getDeviceController(type)
480 return ctrl.destroyDevice(id, change=change, reboot=reboot)
482 def deleteDevice(self, type, id):
483 ctrl = self.getDeviceController(type)
484 return ctrl.deleteDevice(id)
486 def getDevice(self, type, id, error=True):
487 ctrl = self.getDeviceController(type)
488 return ctrl.getDevice(id, error=error)
490 def getDeviceIds(self, type):
491 ctrl = self.getDeviceController(type)
492 return ctrl.getDeviceIds()
494 def getDeviceSxprs(self, type):
495 ctrl = self.getDeviceController(type)
496 return ctrl.getDeviceSxprs()
498 def sxpr(self):
499 sxpr = ['domain',
500 ['id', self.id],
501 ['name', self.name],
502 ['memory', self.memory],
503 ['ssidref', self.ssidref],
504 ['target', self.target] ]
505 if self.uuid:
506 sxpr.append(['uuid', self.uuid])
507 if self.info:
508 sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ])
509 run = (self.info['running'] and 'r') or '-'
510 block = (self.info['blocked'] and 'b') or '-'
511 pause = (self.info['paused'] and 'p') or '-'
512 shut = (self.info['shutdown'] and 's') or '-'
513 crash = (self.info['crashed'] and 'c') or '-'
514 state = run + block + pause + shut + crash
515 sxpr.append(['state', state])
516 if self.info['shutdown']:
517 reason = shutdown_reason(self.info['shutdown_reason'])
518 sxpr.append(['shutdown_reason', reason])
519 sxpr.append(['cpu', self.info['vcpu_to_cpu'][0]])
520 sxpr.append(['cpu_time', self.info['cpu_time']/1e9])
521 sxpr.append(['vcpus', self.info['vcpus']])
522 sxpr.append(['cpumap', self.info['cpumap']])
523 # build a string, using '|' to seperate items, show only up
524 # to number of vcpus in domain, and trim the trailing '|'
525 sxpr.append(['vcpu_to_cpu', ''.join(map(lambda x: str(x)+'|',
526 self.info['vcpu_to_cpu'][0:self.info['vcpus']]))[:-1]])
528 if self.start_time:
529 up_time = time.time() - self.start_time
530 sxpr.append(['up_time', str(up_time) ])
531 sxpr.append(['start_time', str(self.start_time) ])
533 if self.store_channel:
534 sxpr.append(self.store_channel.sxpr())
535 if self.store_mfn:
536 sxpr.append(['store_mfn', self.store_mfn])
537 if self.console_channel:
538 sxpr.append(['console_channel', self.console_channel.sxpr()])
539 if self.console_mfn:
540 sxpr.append(['console_mfn', self.console_mfn])
541 # already in (devices)
542 # console = self.getConsole()
543 # if console:
544 # sxpr.append(console.sxpr())
546 if self.restart_count:
547 sxpr.append(['restart_count', self.restart_count])
548 if self.restart_state:
549 sxpr.append(['restart_state', self.restart_state])
550 if self.restart_time:
551 sxpr.append(['restart_time', str(self.restart_time)])
553 devs = self.sxpr_devices()
554 if devs:
555 sxpr.append(devs)
556 if self.config:
557 sxpr.append(['config', self.config])
558 if self.device_model_pid:
559 sxpr.append(['device_model_pid',self.device_model_pid])
560 return sxpr
562 def sxpr_devices(self):
563 sxpr = []
564 for ty in self.controllers.keys():
565 devs = self.getDeviceSxprs(ty)
566 sxpr += devs
567 if sxpr:
568 sxpr.insert(0, 'devices')
569 else:
570 sxpr = None
571 return sxpr
573 def check_name(self, name):
574 """Check if a vm name is valid. Valid names contain alphabetic characters,
575 digits, or characters in '_-.:/+'.
576 The same name cannot be used for more than one vm at the same time.
578 @param name: name
579 @raise: VMerror if invalid
580 """
581 if self.recreate: return
582 if name is None or name == '':
583 raise VmError('missing vm name')
584 for c in name:
585 if c in string.digits: continue
586 if c in '_-.:/+': continue
587 if c in string.ascii_letters: continue
588 raise VmError('invalid vm name')
589 dominfo = domain_exists(name)
590 # When creating or rebooting, a domain with my name should not exist.
591 # When restoring, a domain with my name will exist, but it should have
592 # my domain id.
593 if not dominfo:
594 return
595 if dominfo.is_terminated():
596 return
597 if not self.id or (dominfo.id != self.id):
598 raise VmError('vm name clash: ' + name)
600 def construct(self, config):
601 """Construct the vm instance from its configuration.
603 @param config: configuration
604 @raise: VmError on error
605 """
606 # todo - add support for scheduling params?
607 self.config = config
608 try:
609 # Initial domain create.
610 self.setName(sxp.child_value(config, 'name'))
611 self.check_name(self.name)
612 self.init_image()
613 self.configure_cpus(config)
614 self.init_domain()
615 self.register_domain()
616 self.configure_bootloader()
618 # Create domain devices.
619 self.configure_backends()
620 self.configure_restart()
621 self.construct_image()
622 self.configure()
623 self.exportToDB(save=True)
624 except Exception, ex:
625 # Catch errors, cleanup and re-raise.
626 print 'Domain construction error:', ex
627 import traceback
628 traceback.print_exc()
629 self.destroy()
630 raise
632 def register_domain(self):
633 xd = get_component('xen.xend.XendDomain')
634 xd._add_domain(self)
635 self.exportToDB(save=True)
637 def configure_cpus(self, config):
638 try:
639 self.cpu_weight = float(sxp.child_value(config, 'cpu_weight', '1'))
640 except:
641 raise VmError('invalid cpu weight')
642 self.memory = int(sxp.child_value(config, 'memory'))
643 if self.memory is None:
644 raise VmError('missing memory size')
645 self.target = self.memory * (1 << 20)
646 self.ssidref = int(sxp.child_value(config, 'ssidref'))
647 cpu = sxp.child_value(config, 'cpu')
648 if self.recreate and self.id and cpu is not None and int(cpu) >= 0:
649 xc.domain_pincpu(self.id, 0, 1<<int(cpu))
650 try:
651 image = sxp.child_value(self.config, 'image')
652 vcpus = sxp.child_value(image, 'vcpus')
653 if vcpus:
654 self.vcpus = int(vcpus)
655 except:
656 raise VmError('invalid vcpus value')
658 def exportVCPUSToDB(self, vcpus):
659 for v in range(0,vcpus):
660 path = "/cpu/%d"%(v)
661 if not self.vcpusdb.has_key(path):
662 self.vcpusdb[path] = self.db.addChild(path)
663 db = self.vcpusdb[path]
664 log.debug("writing key availability=online to path %s in store"%(path))
665 db['availability'] = "online"
666 db.saveDB(save=True)
668 def init_image(self):
669 """Create boot image handler for the domain.
670 """
671 image = sxp.child_value(self.config, 'image')
672 if image is None:
673 raise VmError('missing image')
674 self.image = ImageHandler.create(self, image)
676 def construct_image(self):
677 """Construct the boot image for the domain.
678 """
679 self.create_channel()
680 self.image.createImage()
681 self.exportToDB()
682 if self.store_channel and self.store_mfn >= 0:
683 self.db.introduceDomain(self.id,
684 self.store_mfn,
685 self.store_channel)
686 # get the configured value of vcpus and update store
687 self.exportVCPUSToDB(self.vcpus)
689 def delete(self):
690 """Delete the vm's db.
691 """
692 if dom_get(self.id):
693 return
694 self.id = None
695 self.saveToDB(sync=True)
696 try:
697 # Todo: eventually will have to wait for devices to signal
698 # destruction before can delete the db.
699 if self.db:
700 self.db.delete()
701 except Exception, ex:
702 log.warning("error in domain db delete: %s", ex)
703 pass
705 def destroy_domain(self):
706 """Destroy the vm's domain.
707 The domain will not finally go away unless all vm
708 devices have been released.
709 """
710 if self.id is None:
711 return
712 try:
713 xc.domain_destroy(dom=self.id)
714 except Exception, err:
715 log.exception("Domain destroy failed: %s", self.name)
717 def cleanup(self):
718 """Cleanup vm resources: release devices.
719 """
720 self.state = STATE_VM_TERMINATED
721 self.release_devices()
722 if self.store_channel:
723 try:
724 self.store_channel.close()
725 self.store_channel = None
726 except:
727 pass
728 try:
729 self.db.releaseDomain(self.id)
730 except Exception, ex:
731 log.warning("error in domain release on xenstore: %s", ex)
732 pass
733 if self.console_channel:
734 # notify processes using this cosole?
735 try:
736 self.console_channel.close()
737 self.console_channel = None
738 except:
739 pass
740 if self.image:
741 try:
742 self.device_model_pid = 0
743 self.image.destroy()
744 self.image = None
745 except:
746 pass
748 def destroy(self):
749 """Clenup vm and destroy domain.
750 """
751 self.destroy_domain()
752 self.cleanup()
753 self.saveToDB()
754 return 0
756 def is_terminated(self):
757 """Check if a domain has been terminated.
758 """
759 return self.state == STATE_VM_TERMINATED
761 def release_devices(self):
762 """Release all vm devices.
763 """
764 reboot = self.restart_pending()
765 for ctrl in self.controllers.values():
766 if ctrl.isDestroyed(): continue
767 ctrl.destroyController(reboot=reboot)
768 t = xstransact("%s/device" % self.path)
769 for d in t.list("vif"):
770 t.remove(d)
771 t.commit()
772 ddb = self.db.addChild("/device")
773 for type in ddb.keys():
774 if type == 'vbd':
775 typedb = ddb.addChild(type)
776 for dev in typedb.keys():
777 devdb = typedb.addChild(str(dev))
778 Blkctl.block('unbind', devdb['type'].getData(),
779 devdb['node'].getData())
780 typedb[dev].delete()
781 typedb.saveDB(save=True)
782 if type == 'vtpm':
783 typedb = ddb.addChild(type)
784 for dev in typedb.keys():
785 typedb[dev].delete()
786 typedb.saveDB(save=True)
788 def show(self):
789 """Print virtual machine info.
790 """
791 print "[VM dom=%d name=%s memory=%d ssidref=%d" % (self.id, self.name, self.memory, self.ssidref)
792 print "image:"
793 sxp.show(self.image)
794 print "]"
796 def init_domain(self):
797 """Initialize the domain memory.
798 """
799 if self.recreate:
800 return
801 if self.start_time is None:
802 self.start_time = time.time()
803 try:
804 cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
805 except:
806 raise VmError('invalid cpu')
807 id = self.image.initDomain(self.id, self.memory, self.ssidref, cpu, self.cpu_weight)
808 log.debug('init_domain> Created domain=%d name=%s memory=%d',
809 id, self.name, self.memory)
810 self.setdom(id)
812 def eventChannelOld(self, key):
813 """Create an event channel to the domain.
814 If saved info is available recreate the channel.
816 @param key db key for the saved data (if any)
817 """
818 db = self.db.addChild(key)
819 return EventChannel.restoreFromDB(db, 0, self.id)
821 def eventChannel(self, path=None, key=None):
822 """Create an event channel to the domain.
824 @param path under which port is stored in db
825 """
826 port = 0
827 try:
828 if path and key:
829 if path:
830 db = self.db.addChild(path)
831 else:
832 db = self.db
833 port = int(db[key].getData())
834 except: pass
835 return EventChannel.interdomain(0, self.id, port1=port, port2=0)
837 def create_channel(self):
838 """Create the channels to the domain.
839 """
840 self.store_channel = self.eventChannelOld("store_channel")
841 self.console_channel = self.eventChannel("console", "port")
844 def create_configured_devices(self):
845 devices = sxp.children(self.config, 'device')
846 for d in devices:
847 dev_config = sxp.child0(d)
848 if dev_config is None:
849 raise VmError('invalid device')
850 dev_type = sxp.name(dev_config)
852 if not controller.isDevControllerClass(dev_type):
853 raise VmError('unknown device type: ' + dev_type)
855 self.createDevice(dev_type, dev_config)
858 def create_devices(self):
859 """Create the devices for a vm.
861 @raise: VmError for invalid devices
862 """
863 if self.rebooting():
864 for ctrl in self.controllers.values():
865 ctrl.initController(reboot=True)
866 else:
867 self.create_configured_devices()
868 if not self.device_model_pid:
869 self.device_model_pid = self.image.createDeviceModel()
871 def device_create(self, dev_config):
872 """Create a new device.
874 @param dev_config: device configuration
875 """
876 dev_type = sxp.name(dev_config)
877 dev = self.createDevice(dev_type, dev_config, change=True)
878 self.config.append(['device', dev.getConfig()])
879 return dev.sxpr()
881 def device_configure(self, dev_config, id):
882 """Configure an existing device.
884 @param dev_config: device configuration
885 @param id: device id
886 """
887 type = sxp.name(dev_config)
888 dev = self.getDevice(type, id)
889 old_config = dev.getConfig()
890 new_config = dev.configure(dev_config, change=True)
891 # Patch new config into vm config.
892 new_full_config = ['device', new_config]
893 old_full_config = ['device', old_config]
894 old_index = self.config.index(old_full_config)
895 self.config[old_index] = new_full_config
896 return new_config
898 def device_refresh(self, type, id):
899 """Refresh a device.
901 @param type: device type
902 @param id: device id
903 """
904 dev = self.getDevice(type, id)
905 dev.refresh()
907 def device_delete(self, type, id):
908 """Destroy and remove a device.
910 @param type: device type
911 @param id: device id
912 """
913 dev = self.getDevice(type, id)
914 dev_config = dev.getConfig()
915 if dev_config:
916 self.config.remove(['device', dev_config])
917 self.deleteDevice(type, dev.getId())
919 def configure_bootloader(self):
920 """Configure boot loader.
921 """
922 self.bootloader = sxp.child_value(self.config, "bootloader")
924 def configure_restart(self):
925 """Configure the vm restart mode.
926 """
927 r = sxp.child_value(self.config, 'restart', RESTART_ONREBOOT)
928 if r not in restart_modes:
929 raise VmError('invalid restart mode: ' + str(r))
930 self.restart_mode = r;
932 def restart_needed(self, reason):
933 """Determine if the vm needs to be restarted when shutdown
934 for the given reason.
936 @param reason: shutdown reason
937 @return True if needs restart, False otherwise
938 """
939 if self.restart_mode == RESTART_NEVER:
940 return False
941 if self.restart_mode == RESTART_ALWAYS:
942 return True
943 if self.restart_mode == RESTART_ONREBOOT:
944 return reason == 'reboot'
945 return False
947 def restart_cancel(self):
948 """Cancel a vm restart.
949 """
950 self.restart_state = None
952 def restarting(self):
953 """Put the vm into restart mode.
954 """
955 self.restart_state = STATE_RESTART_PENDING
957 def restart_pending(self):
958 """Test if the vm has a pending restart.
959 """
960 return self.restart_state == STATE_RESTART_PENDING
962 def rebooting(self):
963 return self.restart_state == STATE_RESTART_BOOTING
965 def restart_check(self):
966 """Check if domain restart is OK.
967 To prevent restart loops, raise an error if it is
968 less than MINIMUM_RESTART_TIME seconds since the last restart.
969 """
970 tnow = time.time()
971 if self.restart_time is not None:
972 tdelta = tnow - self.restart_time
973 if tdelta < self.MINIMUM_RESTART_TIME:
974 self.restart_cancel()
975 msg = 'VM %s restarting too fast' % self.name
976 log.error(msg)
977 raise VmError(msg)
978 self.restart_time = tnow
979 self.restart_count += 1
981 def restart(self):
982 """Restart the domain after it has exited.
983 Reuses the domain id
985 """
986 try:
987 self.clear_shutdown()
988 self.state = STATE_VM_OK
989 self.shutdown_pending = None
990 self.restart_check()
991 self.exportToDB()
992 self.restart_state = STATE_RESTART_BOOTING
993 if self.bootloader:
994 self.config = self.bootloader_config()
995 self.construct(self.config)
996 self.saveToDB()
997 finally:
998 self.restart_state = None
1000 def bootloader_config(self):
1001 # if we're restarting with a bootloader, we need to run it
1002 # FIXME: this assumes the disk is the first device and
1003 # that we're booting from the first disk
1004 blcfg = None
1005 # FIXME: this assumes that we want to use the first disk
1006 dev = sxp.child_value(self.config, "device")
1007 if dev:
1008 disk = sxp.child_value(dev, "uname")
1009 fn = blkdev_uname_to_file(disk)
1010 blcfg = bootloader(self.bootloader, fn, 1, self.vcpus)
1011 if blcfg is None:
1012 msg = "Had a bootloader specified, but can't find disk"
1013 log.error(msg)
1014 raise VmError(msg)
1015 config = sxp.merge(['vm', blcfg ], self.config)
1016 return config
1018 def configure_backends(self):
1019 """Set configuration flags if the vm is a backend for netif or blkif.
1020 Configure the backends to use for vbd and vif if specified.
1021 """
1022 for c in sxp.children(self.config, 'backend'):
1023 v = sxp.child0(c)
1024 name = sxp.name(v)
1025 if name == 'blkif':
1026 self.blkif_backend = True
1027 elif name == 'netif':
1028 self.netif_backend = True
1029 elif name == 'usbif':
1030 self.usbif_backend = True
1031 elif name == 'tpmif':
1032 self.tpmif_backend = True
1033 else:
1034 raise VmError('invalid backend type:' + str(name))
1036 def configure(self):
1037 """Configure a vm.
1039 """
1040 self.configure_fields()
1041 self.create_devices()
1042 self.create_blkif()
1044 def create_blkif(self):
1045 """Create the block device interface (blkif) for the vm.
1046 The vm needs a blkif even if it doesn't have any disks
1047 at creation time, for example when it uses NFS root.
1049 """
1050 return
1051 blkif = self.getDeviceController("vbd", error=False)
1052 if not blkif:
1053 blkif = self.createDeviceController("vbd")
1054 backend = blkif.getBackend(0)
1055 backend.connect(recreate=self.recreate)
1057 def configure_fields(self):
1058 """Process the vm configuration fields using the registered handlers.
1059 """
1060 index = {}
1061 for field in sxp.children(self.config):
1062 field_name = sxp.name(field)
1063 field_index = index.get(field_name, 0)
1064 field_handler = config_handlers.get(field_name)
1065 # Ignore unknown fields. Warn?
1066 if field_handler:
1067 v = field_handler(self, self.config, field, field_index)
1068 else:
1069 log.warning("Unknown config field %s", field_name)
1070 index[field_name] = field_index + 1
1072 def mem_target_set(self, target):
1073 """Set domain memory target in bytes.
1074 """
1075 if target:
1076 self.target = target * (1 << 20)
1077 # Commit to XenStore immediately
1078 self.exportToDB()
1080 def vcpu_hotplug(self, vcpu, state):
1081 """Disable or enable VCPU in domain.
1082 """
1083 db = ""
1084 try:
1085 db = self.vcpusdb['/cpu/%d'%(vcpu)]
1086 except:
1087 log.error("Invalid VCPU")
1088 return
1090 if self.store_channel:
1091 if int(state) == 0:
1092 db['availability'] = "offline"
1093 else:
1094 db['availability'] = "online"
1096 db.saveDB(save=True)
1098 def shutdown(self, reason):
1099 if not reason in shutdown_reasons.values():
1100 raise XendError('invalid reason:' + reason)
1101 db = self.db.addChild("/control");
1102 db['shutdown'] = reason;
1103 db.saveDB(save=True);
1104 if not reason in ['suspend']:
1105 self.shutdown_pending = {'start':time.time(), 'reason':reason}
1107 def clear_shutdown(self):
1108 db = self.db.addChild("/control")
1109 db['shutdown'] = ""
1110 db.saveDB(save=True)
1112 def send_sysrq(self, key=0):
1113 db = self.db.addChild("/control");
1114 db['sysrq'] = '%c' % key;
1115 db.saveDB(save=True);
1117 def shutdown_time_left(self, timeout):
1118 if not self.shutdown_pending:
1119 return 0
1120 return timeout - (time.time() - self.shutdown_pending['start'])
1122 def dom0_init_store(self):
1123 if not self.store_channel:
1124 self.store_channel = self.eventChannelOld("store_channel")
1125 self.store_mfn = xc.init_store(self.store_channel.port2)
1126 if self.store_mfn >= 0:
1127 self.db.introduceDomain(self.id, self.store_mfn,
1128 self.store_channel)
1129 self.exportToDB(save=True, sync=True)
1130 # get run-time value of vcpus and update store
1131 self.exportVCPUSToDB(dom_get(self.id)['vcpus'])
1134 def vm_field_ignore(_, _1, _2, _3):
1135 """Dummy config field handler used for fields with built-in handling.
1136 Matches the signature required by config_handlers.
1137 """
1138 pass
1141 def vm_field_maxmem(vm, _1, val, _2):
1142 """Config field handler to configure vm memory limit. Matches the
1143 signature required by config_handlers.
1144 """
1145 maxmem = sxp.child0(val)
1146 if maxmem is None:
1147 maxmem = vm.memory
1148 try:
1149 maxmem = int(maxmem)
1150 except:
1151 raise VmError("invalid maxmem: " + str(maxmem))
1152 xc.domain_setmaxmem(vm.id, maxmem_kb = maxmem * 1024)
1155 #============================================================================
1156 # Register image handlers.
1158 from image import \
1159 addImageHandlerClass, \
1160 ImageHandler, \
1161 LinuxImageHandler, \
1162 VmxImageHandler
1164 addImageHandlerClass(LinuxImageHandler)
1165 addImageHandlerClass(VmxImageHandler)
1168 """Table of handlers for field configuration.
1170 field_name[String]: fn(vm, config, field, index) -> value(ignored)
1171 """
1172 config_handlers = {
1174 # Ignore the fields we already handle.
1176 'name': vm_field_ignore,
1177 'memory': vm_field_ignore,
1178 'ssidref': vm_field_ignore,
1179 'cpu': vm_field_ignore,
1180 'cpu_weight': vm_field_ignore,
1181 'restart': vm_field_ignore,
1182 'image': vm_field_ignore,
1183 'device': vm_field_ignore,
1184 'backend': vm_field_ignore,
1185 'vcpus': vm_field_ignore,
1186 'bootloader': vm_field_ignore,
1188 # Register other config handlers.
1189 'maxmem': vm_field_maxmem
1193 #============================================================================
1194 # Register device controllers and their device config types.
1196 from xen.xend.server import blkif, netif, tpmif, pciif, usbif
1197 controller.addDevControllerClass("vbd", blkif.BlkifController)
1198 controller.addDevControllerClass("vif", netif.NetifController)
1199 controller.addDevControllerClass("vtpm", tpmif.TPMifController)
1200 controller.addDevControllerClass("pci", pciif.PciController)
1201 controller.addDevControllerClass("usb", usbif.UsbifController)