debuggers.hg

view tools/python/xen/xend/XendDomain.py @ 6644:29808fef9148

merge?
author cl349@firebug.cl.cam.ac.uk
date Sat Sep 03 18:24:46 2005 +0000 (2005-09-03)
parents a1de77c1486c f27205ea60ef
children b6c98fe62e1a
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 # Copyright (C) 2005 Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
17 #============================================================================
19 """Handler for domain operations.
20 Nothing here is persistent (across reboots).
21 Needs to be persistent for one uptime.
22 """
23 import errno
24 import os
25 import sys
26 import time
27 import traceback
29 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
31 from xen.xend import sxp
32 from xen.xend import XendRoot; xroot = XendRoot.instance()
33 from xen.xend import XendCheckpoint
34 from xen.xend.XendDomainInfo import XendDomainInfo, shutdown_reason
35 from xen.xend import EventServer; eserver = EventServer.instance()
36 from xen.xend.XendError import XendError
37 from xen.xend.XendLogging import log
38 from xen.xend import scheduler
39 from xen.xend.server import channel
40 from xen.xend.server import relocate
41 from xen.xend.uuid import getUuid
42 from xen.xend.xenstore import XenNode, DBMap
44 __all__ = [ "XendDomain" ]
46 SHUTDOWN_TIMEOUT = 30
48 class XendDomainDict(dict):
49 def get_by_name(self, name):
50 try:
51 return filter(lambda d: d.name == name, self.values())[0]
52 except IndexError, err:
53 return None
55 class XendDomain:
56 """Index of all domains. Singleton.
57 """
59 """Dict of domain info indexed by domain id."""
60 domains = None
62 def __init__(self):
63 # Hack alert. Python does not support mutual imports, but XendDomainInfo
64 # needs access to the XendDomain instance to look up domains. Attempting
65 # to import XendDomain from XendDomainInfo causes unbounded recursion.
66 # So we stuff the XendDomain instance (self) into xroot's components.
67 xroot.add_component("xen.xend.XendDomain", self)
68 self.domains = XendDomainDict()
69 self.dbmap = DBMap(db=XenNode("/domain"))
70 eserver.subscribe('xend.virq', self.onVirq)
71 self.initial_refresh()
73 def list(self):
74 """Get list of domain objects.
76 @return: domain objects
77 """
78 self.refresh()
79 return self.domains.values()
81 def list_sorted(self):
82 """Get list of domain objects, sorted by name.
84 @return: domain objects
85 """
86 doms = self.list()
87 doms.sort(lambda x, y: cmp(x.name, y.name))
88 return doms
90 def list_names(self):
91 """Get list of domain names.
93 @return: domain names
94 """
95 doms = self.list_sorted()
96 return map(lambda x: x.name, doms)
98 def onVirq(self, event, val):
99 """Event handler for virq.
100 """
101 self.refresh(cleanup=True)
103 def xen_domains(self):
104 """Get table of domains indexed by id from xc.
105 """
106 domlist = xc.domain_getinfo()
107 doms = {}
108 for d in domlist:
109 domid = d['dom']
110 doms[domid] = d
111 return doms
113 def xen_domain(self, dom):
114 """Get info about a single domain from xc.
115 Returns None if not found.
117 @param dom domain id (int)
118 """
119 dominfo = xc.domain_getinfo(dom, 1)
120 if dominfo == [] or dominfo[0]['dom'] != dom:
121 dominfo = None
122 else:
123 dominfo = dominfo[0]
124 return dominfo
126 def initial_refresh(self):
127 """Refresh initial domain info from db.
128 """
129 doms = self.xen_domains()
130 self.dbmap.readDB()
131 for domdb in self.dbmap.values():
132 try:
133 domid = int(domdb.id)
134 except:
135 domid = None
136 # XXX if domid in self.domains, then something went wrong
137 if (domid is None) or (domid in self.domains):
138 domdb.delete()
139 elif domid in doms:
140 try:
141 self._new_domain(domdb, doms[domid])
142 except Exception, ex:
143 log.exception("Error recreating domain info: id=%d", domid)
144 self._delete_domain(domid)
145 else:
146 self._delete_domain(domid)
147 self.refresh(cleanup=True)
149 dom0 = self.domain_lookup(0)
150 if not dom0:
151 dom0 = self.domain_unknown(0)
152 dom0.dom0_init_store()
154 def close(self):
155 pass
157 def _new_domain(self, db, info):
158 """Create a domain entry from saved info.
160 @param db: saved info from the db
161 @param info: domain info from xen
162 @return: domain
163 """
164 dominfo = XendDomainInfo.recreate(db, info)
165 self.domains[dominfo.id] = dominfo
166 return dominfo
168 def _add_domain(self, info, notify=True):
169 """Add a domain entry to the tables.
171 @param info: domain info object
172 @param notify: send a domain created event if true
173 """
174 # Remove entries under the wrong id.
175 for i, d in self.domains.items():
176 if i != d.id:
177 del self.domains[i]
178 self.dbmap.delete(d.uuid)
179 if info.id in self.domains:
180 notify = False
181 self.domains[info.id] = info
182 info.exportToDB(save=True)
183 if notify:
184 eserver.inject('xend.domain.create', [info.name, info.id])
186 def _delete_domain(self, id, notify=True):
187 """Remove a domain from the tables.
189 @param id: domain id
190 @param notify: send a domain died event if true
191 """
192 try:
193 if self.xen_domain(id):
194 return
195 except:
196 pass
197 info = self.domains.get(id)
198 if info:
199 del self.domains[id]
200 info.cleanup()
201 info.delete()
202 if notify:
203 eserver.inject('xend.domain.died', [info.name, info.id])
204 # XXX this should not be needed
205 for domdb in self.dbmap.values():
206 try:
207 domid = int(domdb.id)
208 except:
209 domid = None
210 if (domid is None) or (domid == id):
211 domdb.delete()
213 def reap(self):
214 """Look for domains that have crashed or stopped.
215 Tidy them up.
216 """
217 casualties = []
218 doms = self.xen_domains()
219 for d in doms.values():
220 dead = 0
221 dead = dead or (d['crashed'] or d['shutdown'])
222 dead = dead or (d['dying'] and
223 not(d['running'] or d['paused'] or d['blocked']))
224 if dead:
225 casualties.append(d)
226 for d in casualties:
227 id = d['dom']
228 dominfo = self.domains.get(id)
229 name = (dominfo and dominfo.name) or '??'
230 if dominfo and dominfo.is_terminated():
231 continue
232 log.debug('XendDomain>reap> domain died name=%s id=%d', name, id)
233 if d['shutdown']:
234 reason = shutdown_reason(d['shutdown_reason'])
235 log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason)
236 if reason in ['suspend']:
237 if dominfo and dominfo.is_terminated():
238 log.debug('XendDomain>reap> Suspended domain died id=%d', id)
239 else:
240 eserver.inject('xend.domain.suspended', [name, id])
241 if dominfo:
242 dominfo.state_set("suspended")
243 continue
244 if reason in ['poweroff', 'reboot']:
245 eserver.inject('xend.domain.exit', [name, id, reason])
246 self.domain_restart_schedule(id, reason)
247 else:
248 if xroot.get_enable_dump():
249 self.domain_dumpcore(id)
250 eserver.inject('xend.domain.exit', [name, id, 'crash'])
251 self.final_domain_destroy(id)
253 def refresh(self, cleanup=False):
254 """Refresh domain list from Xen.
255 """
256 if cleanup:
257 self.reap()
258 doms = self.xen_domains()
259 # Remove entries for domains that no longer exist.
260 # Update entries for existing domains.
261 do_domain_restarts = False
262 for d in self.domains.values():
263 info = doms.get(d.id)
264 if info:
265 d.update(info)
266 elif d.restart_pending():
267 do_domain_restarts = True
268 else:
269 self._delete_domain(d.id)
270 if cleanup and do_domain_restarts:
271 scheduler.now(self.domain_restarts)
273 def update_domain(self, id):
274 """Update information for a single domain.
276 @param id: domain id
277 """
278 dominfo = self.xen_domain(id)
279 if dominfo:
280 d = self.domains.get(id)
281 if d:
282 d.update(dominfo)
283 else:
284 self._delete_domain(id)
286 def domain_create(self, config):
287 """Create a domain from a configuration.
289 @param config: configuration
290 @return: domain
291 """
292 dominfo = XendDomainInfo.create(self.dbmap, config)
293 return dominfo
295 def domain_restart(self, dominfo):
296 """Restart a domain.
298 @param dominfo: domain object
299 """
300 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
301 eserver.inject("xend.domain.restart",
302 [dominfo.name, dominfo.id, "begin"])
303 try:
304 dominfo.restart()
305 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
306 eserver.inject("xend.domain.restart",
307 [dominfo.name, dominfo.id, "success"])
308 self.domain_unpause(dominfo.id)
309 except Exception, ex:
310 log.exception("Exception restarting domain: name=%s id=%s",
311 dominfo.name, dominfo.id)
312 eserver.inject("xend.domain.restart",
313 [dominfo.name, dominfo.id, "fail"])
314 return dominfo
316 def domain_configure(self, vmconfig):
317 """Configure an existing domain. This is intended for internal
318 use by domain restore and migrate.
320 @param vmconfig: vm configuration
321 """
322 config = sxp.child_value(vmconfig, 'config')
323 dominfo = XendDomainInfo.restore(self.dbmap, config)
324 return dominfo
326 def domain_restore(self, src, progress=False):
327 """Restore a domain from file.
329 @param src: source file
330 @param progress: output progress if true
331 """
333 try:
334 fd = os.open(src, os.O_RDONLY)
335 return XendCheckpoint.restore(self, fd)
336 except OSError, ex:
337 raise XendError("can't read guest state file %s: %s" %
338 (src, ex[1]))
340 def domain_get(self, id):
341 """Get up-to-date info about a domain.
343 @param id: domain id
344 @return: domain object (or None)
345 """
346 self.update_domain(id)
347 return self.domains.get(id)
349 def domain_unknown(self, id):
350 try:
351 info = self.xen_domain(id)
352 if info:
353 uuid = getUuid()
354 log.info(
355 "Creating entry for unknown domain: id=%d uuid=%s",
356 id, uuid)
357 db = self.dbmap.addChild(uuid)
358 dominfo = XendDomainInfo.recreate(db, info)
359 dominfo.setdom(id)
360 self._add_domain(dominfo)
361 return dominfo
362 except Exception, ex:
363 log.exception("Error creating domain info: id=%d", id)
364 return None
366 def domain_lookup(self, id):
367 return self.domains.get(id)
369 def domain_lookup_by_name(self, name):
370 dominfo = self.domains.get_by_name(name)
371 if not dominfo:
372 try:
373 id = int(name)
374 dominfo = self.domain_lookup(id)
375 except ValueError:
376 pass
377 return dominfo
379 def domain_unpause(self, id):
380 """Unpause domain execution.
382 @param id: domain id
383 """
384 dominfo = self.domain_lookup(id)
385 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
386 try:
387 return xc.domain_unpause(dom=dominfo.id)
388 except Exception, ex:
389 raise XendError(str(ex))
391 def domain_pause(self, id):
392 """Pause domain execution.
394 @param id: domain id
395 """
396 dominfo = self.domain_lookup(id)
397 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
398 try:
399 return xc.domain_pause(dom=dominfo.id)
400 except Exception, ex:
401 raise XendError(str(ex))
403 def domain_shutdown(self, id, reason='poweroff'):
404 """Shutdown domain (nicely).
405 - poweroff: restart according to exit code and restart mode
406 - reboot: restart on exit
407 - halt: do not restart
409 Returns immediately.
411 @param id: domain id
412 @param reason: shutdown type: poweroff, reboot, suspend, halt
413 """
414 dominfo = self.domain_lookup(id)
415 self.domain_restart_schedule(dominfo.id, reason, force=True)
416 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
417 if reason == 'halt':
418 reason = 'poweroff'
419 val = dominfo.shutdown(reason)
420 if not reason in ['suspend']:
421 self.domain_shutdowns()
422 return val
424 def domain_sysrq(self, id, key):
425 """Send a SysRq to a domain
426 """
427 dominfo = self.domain_lookup(id)
428 val = dominfo.send_sysrq(key)
429 return val
431 def domain_shutdowns(self):
432 """Process pending domain shutdowns.
433 Destroys domains whose shutdowns have timed out.
434 """
435 timeout = SHUTDOWN_TIMEOUT + 1
436 for dominfo in self.domains.values():
437 if not dominfo.shutdown_pending:
438 # domain doesn't need shutdown
439 continue
440 id = dominfo.id
441 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
442 if left <= 0:
443 # Shutdown expired - destroy domain.
444 try:
445 log.info("Domain shutdown timeout expired: name=%s id=%s",
446 dominfo.name, id)
447 self.domain_destroy(id, reason=
448 dominfo.shutdown_pending['reason'])
449 except Exception:
450 pass
451 else:
452 # Shutdown still pending.
453 timeout = min(timeout, left)
454 if timeout <= SHUTDOWN_TIMEOUT:
455 # Pending shutdowns remain - reschedule.
456 scheduler.later(timeout, self.domain_shutdowns)
458 def domain_restart_schedule(self, id, reason, force=False):
459 """Schedule a restart for a domain if it needs one.
461 @param id: domain id
462 @param reason: shutdown reason
463 """
464 log.debug('domain_restart_schedule> %d %s %d', id, reason, force)
465 dominfo = self.domain_lookup(id)
466 if not dominfo:
467 return
468 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
469 if restart:
470 log.info('Scheduling restart for domain: name=%s id=%s',
471 dominfo.name, dominfo.id)
472 eserver.inject("xend.domain.restart",
473 [dominfo.name, dominfo.id, "schedule"])
474 dominfo.restarting()
475 else:
476 log.info('Cancelling restart for domain: name=%s id=%s',
477 dominfo.name, dominfo.id)
478 eserver.inject("xend.domain.restart",
479 [dominfo.name, dominfo.id, "cancel"])
480 dominfo.restart_cancel()
482 def domain_restarts(self):
483 """Execute any scheduled domain restarts for domains that have gone.
484 """
485 doms = self.xen_domains()
486 for dominfo in self.domains.values():
487 if not dominfo.restart_pending():
488 continue
489 print 'domain_restarts>', dominfo.name, dominfo.id
490 info = doms.get(dominfo.id)
491 if info:
492 # Don't execute restart for domains still running.
493 print 'domain_restarts> still runnning: ', dominfo.name
494 continue
495 # Remove it from the restarts.
496 print 'domain_restarts> restarting: ', dominfo.name
497 self.domain_restart(dominfo)
499 def final_domain_destroy(self, id):
500 """Final destruction of a domain..
502 @param id: domain id
503 """
504 try:
505 dominfo = self.domain_lookup(id)
506 log.info('Destroying domain: name=%s', dominfo.name)
507 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
508 val = dominfo.destroy()
509 except:
510 #todo
511 try:
512 val = xc.domain_destroy(dom=id)
513 except Exception, ex:
514 raise XendError(str(ex))
515 return val
517 def domain_destroy(self, id, reason='halt'):
518 """Terminate domain immediately.
519 - halt: cancel any restart for the domain
520 - reboot schedule a restart for the domain
522 @param id: domain id
523 """
524 self.domain_restart_schedule(id, reason, force=True)
525 val = self.final_domain_destroy(id)
526 return val
528 def domain_migrate(self, id, dst, live=False, resource=0):
529 """Start domain migration.
531 @param id: domain id
532 """
533 # Need a cancel too?
534 # Don't forget to cancel restart for it.
535 dominfo = self.domain_lookup(id)
537 port = xroot.get_xend_relocation_port()
538 sock = relocate.setupRelocation(dst, port)
540 # temporarily rename domain for localhost migration
541 if dst == "localhost":
542 dominfo.name = "tmp-" + dominfo.name
544 try:
545 XendCheckpoint.save(self, sock.fileno(), dominfo, live)
546 except:
547 if dst == "localhost":
548 dominfo.name = string.replace(dominfo.name, "tmp-", "", 1)
549 raise
551 return None
553 def domain_save(self, id, dst, progress=False):
554 """Start saving a domain to file.
556 @param id: domain id
557 @param dst: destination file
558 @param progress: output progress if true
559 """
561 try:
562 dominfo = self.domain_lookup(id)
564 fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
566 # For now we don't support 'live checkpoint'
567 return XendCheckpoint.save(self, fd, dominfo, False)
569 except OSError, ex:
570 raise XendError("can't write guest state file %s: %s" %
571 (dst, ex[1]))
573 def domain_pincpu(self, id, vcpu, cpumap):
574 """Set which cpus vcpu can use
576 @param id: domain
577 @param vcpu: vcpu number
578 @param cpumap: bitmap of usbale cpus
579 """
580 dominfo = self.domain_lookup(id)
581 try:
582 return xc.domain_pincpu(dominfo.id, vcpu, cpumap)
583 except Exception, ex:
584 raise XendError(str(ex))
586 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
587 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
588 """
589 dominfo = self.domain_lookup(id)
590 try:
591 return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv,
592 warpback=warpback, warpvalue=warpvalue,
593 warpl=warpl, warpu=warpu)
594 except Exception, ex:
595 raise XendError(str(ex))
597 def domain_cpu_bvt_get(self, id):
598 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
599 """
600 dominfo = self.domain_lookup(id)
601 try:
602 return xc.bvtsched_domain_get(dominfo.id)
603 except Exception, ex:
604 raise XendError(str(ex))
607 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
608 """Set Simple EDF scheduler parameters for a domain.
609 """
610 dominfo = self.domain_lookup(id)
611 try:
612 return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight)
613 except Exception, ex:
614 raise XendError(str(ex))
616 def domain_cpu_sedf_get(self, id):
617 """Get Simple EDF scheduler parameters for a domain.
618 """
619 dominfo = self.domain_lookup(id)
620 try:
621 return xc.sedf_domain_get(dominfo.id)
622 except Exception, ex:
623 raise XendError(str(ex))
625 def domain_device_create(self, id, devconfig):
626 """Create a new device for a domain.
628 @param id: domain id
629 @param devconfig: device configuration
630 """
631 dominfo = self.domain_lookup(id)
632 val = dominfo.device_create(devconfig)
633 dominfo.exportToDB()
634 return val
636 def domain_device_configure(self, id, devconfig, devid):
637 """Configure an existing device for a domain.
639 @param id: domain id
640 @param devconfig: device configuration
641 @param devid: device id
642 @return: updated device configuration
643 """
644 dominfo = self.domain_lookup(id)
645 val = dominfo.device_configure(devconfig, devid)
646 dominfo.exportToDB()
647 return val
649 def domain_device_refresh(self, id, type, devid):
650 """Refresh a device.
652 @param id: domain id
653 @param devid: device id
654 @param type: device type
655 """
656 dominfo = self.domain_lookup(id)
657 val = dominfo.device_refresh(type, devid)
658 dominfo.exportToDB()
659 return val
661 def domain_device_destroy(self, id, type, devid):
662 """Destroy a device.
664 @param id: domain id
665 @param devid: device id
666 @param type: device type
667 """
668 dominfo = self.domain_lookup(id)
669 val = dominfo.device_destroy(type, devid)
670 dominfo.exportToDB()
671 return val
673 def domain_devtype_ls(self, id, type):
674 """Get list of device sxprs for a domain.
676 @param id: domain
677 @param type: device type
678 @return: device sxprs
679 """
680 dominfo = self.domain_lookup(id)
681 return dominfo.getDeviceSxprs(type)
683 def domain_devtype_get(self, id, type, devid):
684 """Get a device from a domain.
686 @param id: domain
687 @param type: device type
688 @param devid: device id
689 @return: device object (or None)
690 """
691 dominfo = self.domain_lookup(id)
692 return dominfo.getDevice(type, devid)
694 def domain_vif_limit_set(self, id, vif, credit, period):
695 """Limit the vif's transmission rate
696 """
697 dominfo = self.domain_lookup(id)
698 dev = dominfo.getDevice('vif', vif)
699 if not dev:
700 raise XendError("invalid vif")
701 return dev.setCreditLimit(credit, period)
703 def domain_shadow_control(self, id, op):
704 """Shadow page control.
706 @param id: domain
707 @param op: operation
708 """
709 dominfo = self.domain_lookup(id)
710 try:
711 return xc.shadow_control(dominfo.id, op)
712 except Exception, ex:
713 raise XendError(str(ex))
715 def domain_maxmem_set(self, id, mem):
716 """Set the memory limit for a domain.
718 @param id: domain
719 @param mem: memory limit (in MB)
720 @return: 0 on success, -1 on error
721 """
722 dominfo = self.domain_lookup(id)
723 maxmem = int(mem) * 1024
724 try:
725 return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem)
726 except Exception, ex:
727 raise XendError(str(ex))
729 def domain_mem_target_set(self, id, mem):
730 """Set the memory target for a domain.
732 @param id: domain
733 @param mem: memory target (in MB)
734 @return: 0 on success, -1 on error
735 """
736 dominfo = self.domain_lookup(id)
737 return dominfo.mem_target_set(mem)
739 def domain_vcpu_hotplug(self, id, vcpu, state):
740 """Enable or disable VCPU vcpu in DOM id
742 @param id: domain
743 @param vcpu: target VCPU in domain
744 @param state: which state VCPU will become
745 @return: 0 on success, -1 on error
746 """
748 dominfo = self.domain_lookup(id)
749 return dominfo.vcpu_hotplug(vcpu, state)
751 def domain_dumpcore(self, id):
752 """Save a core dump for a crashed domain.
754 @param id: domain
755 """
756 dominfo = self.domain_lookup(id)
757 corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id)
758 try:
759 xc.domain_dumpcore(dom=dominfo.id, corefile=corefile)
760 except Exception, ex:
761 log.warning("Dumpcore failed, id=%s name=%s: %s",
762 dominfo.id, dominfo.name, ex)
764 def instance():
765 """Singleton constructor. Use this instead of the class constructor.
766 """
767 global inst
768 try:
769 inst
770 except:
771 inst = XendDomain()
772 return inst