ia64/xen-unstable

view tools/python/xen/xend/XendDomain.py @ 5118:242591aabc7a

bitkeeper revision 1.1528 (4292fdfc5zZkxl3RcxrdBZCU6Rd8EA)

XendDomain.py:
Handle error case where exec fails.
xpopen.py:
Exit with 127 if exec fails.
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Tue May 24 10:12:12 2005 +0000 (2005-05-24)
parents 18001959ba29
children dd3849d6cdea
line source
1 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
3 """Handler for domain operations.
4 Nothing here is persistent (across reboots).
5 Needs to be persistent for one uptime.
6 """
7 import sys
8 import traceback
9 import time
11 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
13 import sxp
14 import XendRoot; xroot = XendRoot.instance()
15 import XendDB
16 import XendDomainInfo
17 import XendMigrate
18 import EventServer; eserver = EventServer.instance()
19 from XendError import XendError
20 from XendLogging import log
22 import scheduler
24 from xen.xend.server import channel
27 import errno
28 import os
29 import select
30 from string import join
31 from struct import pack, unpack, calcsize
32 from xen.util.xpopen import xPopen3
34 __all__ = [ "XendDomain" ]
36 SHUTDOWN_TIMEOUT = 30
38 class XendDomain:
39 """Index of all domains. Singleton.
40 """
42 """Path to domain database."""
43 dbpath = "domain"
45 class XendDomainDict(dict):
46 def get_by_name(self, name):
47 try:
48 return filter(lambda d: d.name == name, self.values())[0]
49 except IndexError, err:
50 return None
52 """Dict of domain info indexed by domain id."""
53 domains = XendDomainDict()
55 def __init__(self):
56 # Hack alert. Python does not support mutual imports, but XendDomainInfo
57 # needs access to the XendDomain instance to look up domains. Attempting
58 # to import XendDomain from XendDomainInfo causes unbounded recursion.
59 # So we stuff the XendDomain instance (self) into xroot's components.
60 xroot.add_component("xen.xend.XendDomain", self)
61 # Table of domain info indexed by domain id.
62 self.db = XendDB.XendDB(self.dbpath)
63 eserver.subscribe('xend.virq', self.onVirq)
64 self.initial_refresh()
66 def list(self):
67 """Get list of domain objects.
69 @return: domain objects
70 """
71 return self.domains.values()
73 def onVirq(self, event, val):
74 """Event handler for virq.
75 """
76 print 'onVirq>', val
77 self.refresh(cleanup=True)
79 def xen_domains(self):
80 """Get table of domains indexed by id from xc.
81 """
82 domlist = xc.domain_getinfo()
83 doms = {}
84 for d in domlist:
85 domid = str(d['dom'])
86 doms[domid] = d
87 return doms
89 def xen_domain(self, dom):
90 """Get info about a single domain from xc.
91 Returns None if not found.
92 """
93 dom = int(dom)
94 dominfo = xc.domain_getinfo(dom, 1)
95 if dominfo == [] or dominfo[0]['dom'] != dom:
96 dominfo = None
97 else:
98 dominfo = dominfo[0]
99 return dominfo
101 def initial_refresh(self):
102 """Refresh initial domain info from db.
103 """
104 doms = self.xen_domains()
105 for config in self.db.fetchall("").values():
106 domid = str(sxp.child_value(config, 'id'))
107 if domid in doms:
108 try:
109 self._new_domain(config, doms[domid])
110 self.update_domain(domid)
111 except Exception, ex:
112 log.exception("Error recreating domain info: id=%s", domid)
113 self._delete_domain(domid)
114 else:
115 self._delete_domain(domid)
116 self.refresh(cleanup=True)
118 def sync_domain(self, info):
119 """Sync info for a domain to disk.
121 info domain info
122 """
123 self.db.save(info.id, info.sxpr())
125 def close(self):
126 pass
128 def _new_domain(self, savedinfo, info):
129 """Create a domain entry from saved info.
131 @param savedinfo: saved info from the db
132 @param info: domain info from xen
133 @return: domain
134 """
135 dominfo = XendDomainInfo.vm_recreate(savedinfo, info)
136 self.domains[dominfo.id] = dominfo
137 return dominfo
139 def _add_domain(self, info, notify=True):
140 """Add a domain entry to the tables.
142 @param info: domain info object
143 @param notify: send a domain created event if true
144 """
145 # Remove entries under the wrong id.
146 for i, d in self.domains.items():
147 if i != d.id:
148 del self.domains[i]
149 self.db.delete(i)
150 if info.id in self.domains:
151 notify = False
152 self.domains[info.id] = info
153 self.sync_domain(info)
154 if notify:
155 eserver.inject('xend.domain.create', [info.name, info.id])
157 def _delete_domain(self, id, notify=True):
158 """Remove a domain from the tables.
160 @param id: domain id
161 @param notify: send a domain died event if true
162 """
163 info = self.domains.get(id)
164 if info:
165 del self.domains[id]
166 if notify:
167 eserver.inject('xend.domain.died', [info.name, info.id])
168 self.db.delete(id)
170 def reap(self):
171 """Look for domains that have crashed or stopped.
172 Tidy them up.
173 """
174 casualties = []
175 doms = self.xen_domains()
176 for d in doms.values():
177 dead = 0
178 dead = dead or (d['crashed'] or d['shutdown'])
179 dead = dead or (d['dying'] and
180 not(d['running'] or d['paused'] or d['blocked']))
181 if dead:
182 casualties.append(d)
183 destroyed = 0
184 for d in casualties:
185 id = str(d['dom'])
186 #print 'reap>', id
187 dominfo = self.domains.get(id)
188 name = (dominfo and dominfo.name) or '??'
189 if dominfo and dominfo.is_terminated():
190 #print 'reap> already terminated:', id
191 continue
192 log.debug('XendDomain>reap> domain died name=%s id=%s', name, id)
193 if d['shutdown']:
194 reason = XendDomainInfo.shutdown_reason(d['shutdown_reason'])
195 log.debug('XendDomain>reap> shutdown name=%s id=%s reason=%s', name, id, reason)
196 if reason in ['suspend']:
197 if dominfo and dominfo.is_terminated():
198 log.debug('XendDomain>reap> Suspended domain died id=%s', id)
199 else:
200 eserver.inject('xend.domain.suspended', [name, id])
201 continue
202 if reason in ['poweroff', 'reboot']:
203 eserver.inject('xend.domain.exit', [name, id, reason])
204 self.domain_restart_schedule(id, reason)
205 else:
206 if xroot.get_enable_dump() == 'true':
207 xc.domain_dumpcore(dom = int(id), corefile = "/var/xen/dump/%s.%s.core"%(name,id))
208 eserver.inject('xend.domain.exit', [name, id, 'crash'])
209 destroyed += 1
210 self.final_domain_destroy(id)
212 def refresh(self, cleanup=False):
213 """Refresh domain list from Xen.
214 """
215 if cleanup:
216 self.reap()
217 doms = self.xen_domains()
218 # Add entries for any domains we don't know about.
219 for (id, d) in doms.items():
220 if id not in self.domains:
221 self.domain_lookup(id)
222 # Remove entries for domains that no longer exist.
223 # Update entries for existing domains.
224 do_domain_restarts = False
225 for d in self.domains.values():
226 info = doms.get(d.id)
227 if info:
228 d.update(info)
229 elif d.restart_pending():
230 do_domain_restarts = True
231 else:
232 self._delete_domain(d.id)
233 if cleanup and do_domain_restarts:
234 scheduler.now(self.domain_restarts)
236 def update_domain(self, id):
237 """Update the saved info for a domain.
239 @param id: domain id
240 """
241 dominfo = self.domains.get(id)
242 if dominfo:
243 self.sync_domain(dominfo)
245 def refresh_domain(self, id):
246 """Refresh information for a single domain.
248 @param id: domain id
249 """
250 dominfo = self.xen_domain(id)
251 if dominfo:
252 d = self.domains.get(id)
253 if d:
254 d.update(dominfo)
255 else:
256 self._delete_domain(id)
258 def domain_ls(self):
259 """Get list of domain names.
261 @return: domain names
262 """
263 self.refresh()
264 doms = self.domains.values()
265 doms.sort(lambda x, y: cmp(x.name, y.name))
266 return map(lambda x: x.name, doms)
268 def domain_ls_ids(self):
269 """Get list of domain ids.
271 @return: domain names
272 """
273 self.refresh()
274 return self.domains.keys()
276 def domain_create(self, config):
277 """Create a domain from a configuration.
279 @param config: configuration
280 @return: domain
281 """
282 dominfo = XendDomainInfo.vm_create(config)
283 self._add_domain(dominfo)
284 return dominfo
286 def domain_restart(self, dominfo):
287 """Restart a domain.
289 @param dominfo: domain object
290 """
291 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
292 eserver.inject("xend.domain.restart",
293 [dominfo.name, dominfo.id, "begin"])
294 try:
295 dominfo.restart()
296 self._add_domain(dominfo)
297 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
298 eserver.inject("xend.domain.restart",
299 [dominfo.name, dominfo.id, "success"])
300 self.domain_unpause(dominfo.id)
301 except Exception, ex:
302 log.exception("Exception restarting domain: name=%s id=%s",
303 dominfo.name, dominfo.id)
304 eserver.inject("xend.domain.restart",
305 [dominfo.name, dominfo.id, "fail"])
306 return dominfo
308 def domain_configure(self, vmconfig):
309 """Configure an existing domain. This is intended for internal
310 use by domain restore and migrate.
312 @param id: domain id
313 @param vmconfig: vm configuration
314 """
315 config = sxp.child_value(vmconfig, 'config')
316 dominfo = XendDomainInfo.tmp_restore_create_domain()
317 dominfo.dom_construct(dominfo.dom, config)
318 self._add_domain(dominfo)
319 return dominfo
321 def domain_restore(self, src, progress=False):
322 """Restore a domain from file.
324 @param src: source file
325 @param progress: output progress if true
326 """
328 SIGNATURE = "LinuxGuestRecord"
329 sizeof_int = calcsize("i")
330 sizeof_unsigned_long = calcsize("L")
331 PAGE_SIZE = 4096
332 PATH_XC_RESTORE = "/usr/libexec/xen/xc_restore"
334 class XendFile(file):
335 def read_exact(self, size, error_msg):
336 buf = self.read(size)
337 if len(buf) != size:
338 raise XendError(error_msg)
339 return buf
341 try:
342 fd = XendFile(src, 'rb')
344 signature = fd.read_exact(len(SIGNATURE),
345 "not a valid guest state file: signature read")
346 if signature != SIGNATURE:
347 raise XendError("not a valid guest state file: found '%s'" %
348 signature)
350 l = fd.read_exact(sizeof_int,
351 "not a valid guest state file: config size read")
352 vmconfig_size = unpack("i", l)[0] # XXX endianess
353 vmconfig_buf = fd.read_exact(vmconfig_size,
354 "not a valid guest state file: config read")
356 p = sxp.Parser()
357 p.input(vmconfig_buf)
358 if not p.ready:
359 raise XendError("not a valid guest state file: config parse")
361 vmconfig = p.get_val()
362 dominfo = self.domain_configure(vmconfig)
364 l = fd.read_exact(sizeof_unsigned_long,
365 "not a valid guest state file: pfn count read")
366 nr_pfns = unpack("=L", l)[0] # XXX endianess
367 if nr_pfns > 1024*1024: # XXX
368 raise XendError(
369 "not a valid guest state file: pfn count out of range")
371 # XXXcl hack: fd.tell will sync up the object and
372 # underlying file descriptor
373 ignore = fd.tell()
375 cmd = [PATH_XC_RESTORE, str(xc.handle()), str(fd.fileno()),
376 dominfo.id, str(nr_pfns)]
377 log.info("[xc_restore] " + join(cmd))
378 child = xPopen3(cmd, True, -1, [fd.fileno(), xc.handle()])
379 child.tochild.close()
381 lasterr = ""
382 p = select.poll()
383 p.register(child.fromchild.fileno())
384 p.register(child.childerr.fileno())
385 while True:
386 r = p.poll()
387 for l in child.childerr.readlines():
388 log.error(l.rstrip())
389 lasterr = l.rstrip()
390 for l in child.fromchild.readlines():
391 log.info(l.rstrip())
392 if filter(lambda (fd, event): event & select.POLLHUP, r):
393 break
395 if child.wait() >> 8 == 127:
396 lasterr = "popen %s failed" % PATH_XC_RESTORE
397 if child.wait() != 0:
398 raise XendError("xc_restore failed: %s" % lasterr)
400 return dominfo
402 except IOError, ex:
403 if ex.errno == errno.ENOENT:
404 raise XendError("can't open guest state file %s" % src)
405 else:
406 raise
408 def domain_get(self, id):
409 """Get up-to-date info about a domain.
411 @param id: domain id
412 @return: domain object (or None)
413 """
414 id = str(id)
415 self.refresh_domain(id)
416 return self.domains.get(id)
418 def domain_lookup(self, name):
419 name = str(name)
420 dominfo = self.domains.get_by_name(name) or self.domains.get(name)
421 if dominfo:
422 return dominfo
423 try:
424 d = self.xen_domain(name)
425 if d:
426 log.info("Creating entry for unknown domain: id=%s", name)
427 dominfo = XendDomainInfo.vm_recreate(None, d)
428 self._add_domain(dominfo)
429 return dominfo
430 except Exception, ex:
431 log.exception("Error creating domain info: id=%s", name)
433 def domain_exists(self, name):
434 return self.domain_lookup(name) != None
436 def domain_unpause(self, id):
437 """Unpause domain execution.
439 @param id: domain id
440 """
441 dominfo = self.domain_lookup(id)
442 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
443 try:
444 return xc.domain_unpause(dom=dominfo.dom)
445 except Exception, ex:
446 raise XendError(str(ex))
448 def domain_pause(self, id):
449 """Pause domain execution.
451 @param id: domain id
452 """
453 dominfo = self.domain_lookup(id)
454 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
455 try:
456 return xc.domain_pause(dom=dominfo.dom)
457 except Exception, ex:
458 raise XendError(str(ex))
460 def domain_shutdown(self, id, reason='poweroff', key=0):
461 """Shutdown domain (nicely).
462 - poweroff: restart according to exit code and restart mode
463 - reboot: restart on exit
464 - halt: do not restart
466 Returns immediately.
468 @param id: domain id
469 @param reason: shutdown type: poweroff, reboot, suspend, halt
470 """
471 dominfo = self.domain_lookup(id)
472 self.domain_restart_schedule(dominfo.id, reason, force=True)
473 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
474 if reason == 'halt':
475 reason = 'poweroff'
476 val = dominfo.shutdown(reason, key=key)
477 if reason != 'sysrq':
478 self.domain_shutdowns()
479 return val
481 def domain_shutdowns(self):
482 """Process pending domain shutdowns.
483 Destroys domains whose shutdowns have timed out.
484 """
485 timeout = SHUTDOWN_TIMEOUT + 1
486 for dominfo in self.domains.values():
487 if not dominfo.shutdown_pending:
488 # domain doesn't need shutdown
489 continue
490 id = dominfo.id
491 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
492 if left <= 0:
493 # Shutdown expired - destroy domain.
494 try:
495 log.info("Domain shutdown timeout expired: name=%s id=%s",
496 dominfo.name, id)
497 self.domain_destroy(id, reason=
498 dominfo.shutdown_pending['reason'])
499 except Exception:
500 pass
501 else:
502 # Shutdown still pending.
503 print 'domain_shutdowns> pending: ', id
504 timeout = min(timeout, left)
505 if timeout <= SHUTDOWN_TIMEOUT:
506 # Pending shutdowns remain - reschedule.
507 scheduler.later(timeout, self.domain_shutdowns)
509 def domain_restart_schedule(self, id, reason, force=False):
510 """Schedule a restart for a domain if it needs one.
512 @param id: domain id
513 @param reason: shutdown reason
514 """
515 log.debug('domain_restart_schedule> %s %s %d', id, reason, force)
516 dominfo = self.domain_lookup(id)
517 if not dominfo:
518 return
519 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
520 if restart:
521 log.info('Scheduling restart for domain: name=%s id=%s',
522 dominfo.name, dominfo.id)
523 eserver.inject("xend.domain.restart",
524 [dominfo.name, dominfo.id, "schedule"])
525 dominfo.restarting()
526 else:
527 log.info('Cancelling restart for domain: name=%s id=%s',
528 dominfo.name, dominfo.id)
529 eserver.inject("xend.domain.restart",
530 [dominfo.name, dominfo.id, "cancel"])
531 dominfo.restart_cancel()
533 def domain_restarts(self):
534 """Execute any scheduled domain restarts for domains that have gone.
535 """
536 doms = self.xen_domains()
537 for dominfo in self.domains.values():
538 if not dominfo.restart_pending():
539 continue
540 print 'domain_restarts>', dominfo.name, dominfo.id
541 info = doms.get(dominfo.id)
542 if info:
543 # Don't execute restart for domains still running.
544 print 'domain_restarts> still runnning: ', dominfo.name
545 continue
546 # Remove it from the restarts.
547 print 'domain_restarts> restarting: ', dominfo.name
548 self.domain_restart(dominfo)
550 def final_domain_destroy(self, id):
551 """Final destruction of a domain..
553 @param id: domain id
554 """
555 try:
556 dominfo = self.domain_lookup(id)
557 log.info('Destroying domain: name=%s', dominfo.name)
558 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
559 val = dominfo.destroy()
560 except:
561 #todo
562 try:
563 val = xc.domain_destroy(dom=int(id))
564 except Exception, ex:
565 raise XendError(str(ex))
566 return val
568 def domain_destroy(self, id, reason='halt'):
569 """Terminate domain immediately.
570 - halt: cancel any restart for the domain
571 - reboot schedule a restart for the domain
573 @param id: domain id
574 """
575 self.domain_restart_schedule(id, reason, force=True)
576 val = self.final_domain_destroy(id)
577 return val
579 def domain_migrate(self, id, dst, live=False, resource=0):
580 """Start domain migration.
582 @param id: domain id
583 """
584 # Need a cancel too?
585 # Don't forget to cancel restart for it.
586 dominfo = self.domain_lookup(id)
587 xmigrate = XendMigrate.instance()
588 return xmigrate.migrate_begin(dominfo, dst, live=live, resource=resource)
590 def domain_save(self, id, dst, progress=False):
591 """Start saving a domain to file.
593 @param id: domain id
594 @param dst: destination file
595 @param progress: output progress if true
596 """
597 dominfo = self.domain_lookup(id)
598 xmigrate = XendMigrate.instance()
599 return xmigrate.save_begin(dominfo, dst)
601 def domain_pincpu(self, id, vcpu, cpumap):
602 """Set which cpus vcpu can use
604 @param id: domain
605 @param vcpu: vcpu number
606 @param cpumap: bitmap of usbale cpus
607 """
608 dominfo = self.domain_lookup(id)
609 try:
610 return xc.domain_pincpu(int(dominfo.id), vcpu, cpumap)
611 except Exception, ex:
612 raise XendError(str(ex))
614 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
615 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
616 """
617 dominfo = self.domain_lookup(id)
618 try:
619 return xc.bvtsched_domain_set(dom=dominfo.dom, mcuadv=mcuadv,
620 warpback=warpback, warpvalue=warpvalue,
621 warpl=warpl, warpu=warpu)
622 except Exception, ex:
623 raise XendError(str(ex))
625 def domain_cpu_bvt_get(self, id):
626 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
627 """
628 dominfo = self.domain_lookup(id)
629 try:
630 return xc.bvtsched_domain_get(dominfo.dom)
631 except Exception, ex:
632 raise XendError(str(ex))
635 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
636 """Set Simple EDF scheduler parameters for a domain.
637 """
638 dominfo = self.domain_lookup(id)
639 try:
640 return xc.sedf_domain_set(dominfo.dom, period, slice, latency, extratime, weight)
641 except Exception, ex:
642 raise XendError(str(ex))
644 def domain_cpu_sedf_get(self, id):
645 """Get Atropos scheduler parameters for a domain.
646 """
647 dominfo = self.domain_lookup(id)
648 try:
649 return xc.sedf_domain_get(dominfo.dom)
650 except Exception, ex:
651 raise XendError(str(ex))
652 def domain_device_create(self, id, devconfig):
653 """Create a new device for a domain.
655 @param id: domain id
656 @param devconfig: device configuration
657 """
658 dominfo = self.domain_lookup(id)
659 val = dominfo.device_create(devconfig)
660 self.update_domain(dominfo.id)
661 return val
663 def domain_device_configure(self, id, devconfig, idx):
664 """Configure an existing device for a domain.
666 @param id: domain id
667 @param devconfig: device configuration
668 @param idx: device index
669 @return: updated device configuration
670 """
671 dominfo = self.domain_lookup(id)
672 val = dominfo.device_configure(devconfig, idx)
673 self.update_domain(dominfo.id)
674 return val
676 def domain_device_refresh(self, id, type, idx):
677 """Refresh a device.
679 @param id: domain id
680 @param idx: device index
681 @param type: device type
682 """
683 dominfo = self.domain_lookup(id)
684 val = dominfo.device_refresh(type, idx)
685 self.update_domain(dominfo.id)
686 return val
688 def domain_device_destroy(self, id, type, idx):
689 """Destroy a device.
691 @param id: domain id
692 @param idx: device index
693 @param type: device type
694 """
695 dominfo = self.domain_lookup(id)
696 val = dominfo.device_destroy(type, idx)
697 self.update_domain(dominfo.id)
698 return val
700 def domain_devtype_ls(self, id, type):
701 """Get list of device indexes for a domain.
703 @param id: domain
704 @param type: device type
705 @return: device indexes
706 """
707 dominfo = self.domain_lookup(id)
708 return dominfo.getDeviceIndexes(type)
710 def domain_devtype_get(self, id, type, idx):
711 """Get a device from a domain.
713 @param id: domain
714 @param type: device type
715 @param idx: device index
716 @return: device object (or None)
717 """
718 dominfo = self.domain_lookup(id)
719 return dominfo.getDeviceByIndex(type, idx)
721 def domain_vif_limit_set(self, id, vif, credit, period):
722 """Limit the vif's transmission rate
723 """
724 dominfo = self.domain_lookup(id)
725 dev = dominfo.getDeviceById('vif', vif)
726 if not dev:
727 raise XendError("invalid vif")
728 return dev.setCreditLimit(credit, period)
730 def domain_shadow_control(self, id, op):
731 """Shadow page control.
733 @param id: domain
734 @param op: operation
735 """
736 dominfo = self.domain_lookup(id)
737 try:
738 return xc.shadow_control(dominfo.dom, op)
739 except Exception, ex:
740 raise XendError(str(ex))
742 def domain_maxmem_set(self, id, mem):
743 """Set the memory limit for a domain.
745 @param dom: domain
746 @param mem: memory limit (in MB)
747 @return: 0 on success, -1 on error
748 """
749 dominfo = self.domain_lookup(id)
750 maxmem = int(mem) * 1024
751 try:
752 return xc.domain_setmaxmem(dominfo.dom, maxmem_kb = maxmem)
753 except Exception, ex:
754 raise XendError(str(ex))
756 def domain_mem_target_set(self, id, target):
757 dominfo = self.domain_lookup(id)
758 return dominfo.mem_target_set(target)
762 def instance():
763 """Singleton constructor. Use this instead of the class constructor.
764 """
765 global inst
766 try:
767 inst
768 except:
769 inst = XendDomain()
770 return inst