ia64/xen-unstable

view tools/python/xen/xend/XendDomain.py @ 6227:6b6588e66b96

merge?
author cl349@firebug.cl.cam.ac.uk
date Wed Aug 17 12:26:05 2005 +0000 (2005-08-17)
parents 80291913492d f40c6650152e
children 18f04796ea89
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 return self.domains.values()
80 def onVirq(self, event, val):
81 """Event handler for virq.
82 """
83 self.refresh(cleanup=True)
85 def xen_domains(self):
86 """Get table of domains indexed by id from xc.
87 """
88 domlist = xc.domain_getinfo()
89 doms = {}
90 for d in domlist:
91 domid = d['dom']
92 doms[domid] = d
93 return doms
95 def xen_domain(self, dom):
96 """Get info about a single domain from xc.
97 Returns None if not found.
99 @param dom domain id (int)
100 """
101 dominfo = xc.domain_getinfo(dom, 1)
102 if dominfo == [] or dominfo[0]['dom'] != dom:
103 dominfo = None
104 else:
105 dominfo = dominfo[0]
106 return dominfo
108 def initial_refresh(self):
109 """Refresh initial domain info from db.
110 """
111 doms = self.xen_domains()
112 self.dbmap.readDB()
113 for domdb in self.dbmap.values():
114 try:
115 domid = int(domdb.id)
116 except:
117 domid = None
118 # XXX if domid in self.domains, then something went wrong
119 if (domid is None) or (domid in self.domains):
120 domdb.delete()
121 elif domid in doms:
122 try:
123 self._new_domain(domdb, doms[domid])
124 except Exception, ex:
125 log.exception("Error recreating domain info: id=%d", domid)
126 self._delete_domain(domid)
127 else:
128 self._delete_domain(domid)
129 self.refresh(cleanup=True)
131 dom0 = self.domain_lookup(0)
132 if not dom0:
133 dom0 = self.domain_unknown(0)
134 dom0.dom0_init_store()
136 def close(self):
137 pass
139 def _new_domain(self, db, info):
140 """Create a domain entry from saved info.
142 @param db: saved info from the db
143 @param info: domain info from xen
144 @return: domain
145 """
146 dominfo = XendDomainInfo.recreate(db, info)
147 self.domains[dominfo.id] = dominfo
148 return dominfo
150 def _add_domain(self, info, notify=True):
151 """Add a domain entry to the tables.
153 @param info: domain info object
154 @param notify: send a domain created event if true
155 """
156 # Remove entries under the wrong id.
157 for i, d in self.domains.items():
158 if i != d.id:
159 del self.domains[i]
160 self.dbmap.delete(d.uuid)
161 if info.id in self.domains:
162 notify = False
163 self.domains[info.id] = info
164 info.exportToDB(save=True)
165 if notify:
166 eserver.inject('xend.domain.create', [info.name, info.id])
168 def _delete_domain(self, id, notify=True):
169 """Remove a domain from the tables.
171 @param id: domain id
172 @param notify: send a domain died event if true
173 """
174 try:
175 if self.xen_domain(id):
176 return
177 except:
178 pass
179 info = self.domains.get(id)
180 if info:
181 del self.domains[id]
182 info.cleanup()
183 info.delete()
184 if notify:
185 eserver.inject('xend.domain.died', [info.name, info.id])
186 # XXX this should not be needed
187 for domdb in self.dbmap.values():
188 try:
189 domid = int(domdb.id)
190 except:
191 domid = None
192 if (domid is None) or (domid == id):
193 domdb.delete()
195 def reap(self):
196 """Look for domains that have crashed or stopped.
197 Tidy them up.
198 """
199 casualties = []
200 doms = self.xen_domains()
201 for d in doms.values():
202 dead = 0
203 dead = dead or (d['crashed'] or d['shutdown'])
204 dead = dead or (d['dying'] and
205 not(d['running'] or d['paused'] or d['blocked']))
206 if dead:
207 casualties.append(d)
208 for d in casualties:
209 id = d['dom']
210 dominfo = self.domains.get(id)
211 name = (dominfo and dominfo.name) or '??'
212 if dominfo and dominfo.is_terminated():
213 continue
214 log.debug('XendDomain>reap> domain died name=%s id=%d', name, id)
215 if d['shutdown']:
216 reason = shutdown_reason(d['shutdown_reason'])
217 log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason)
218 if reason in ['suspend']:
219 if dominfo and dominfo.is_terminated():
220 log.debug('XendDomain>reap> Suspended domain died id=%d', id)
221 else:
222 eserver.inject('xend.domain.suspended', [name, id])
223 if dominfo:
224 dominfo.state_set("suspended")
225 continue
226 if reason in ['poweroff', 'reboot']:
227 eserver.inject('xend.domain.exit', [name, id, reason])
228 self.domain_restart_schedule(id, reason)
229 else:
230 if xroot.get_enable_dump():
231 self.domain_dumpcore(id)
232 eserver.inject('xend.domain.exit', [name, id, 'crash'])
233 self.final_domain_destroy(id)
235 def refresh(self, cleanup=False):
236 """Refresh domain list from Xen.
237 """
238 if cleanup:
239 self.reap()
240 doms = self.xen_domains()
241 # Remove entries for domains that no longer exist.
242 # Update entries for existing domains.
243 do_domain_restarts = False
244 for d in self.domains.values():
245 info = doms.get(d.id)
246 if info:
247 d.update(info)
248 elif d.restart_pending():
249 do_domain_restarts = True
250 else:
251 self._delete_domain(d.id)
252 if cleanup and do_domain_restarts:
253 scheduler.now(self.domain_restarts)
255 def update_domain(self, id):
256 """Update information for a single domain.
258 @param id: domain id
259 """
260 dominfo = self.xen_domain(id)
261 if dominfo:
262 d = self.domains.get(id)
263 if d:
264 d.update(dominfo)
265 else:
266 self._delete_domain(id)
268 def domain_ls(self):
269 """Get list of domain names.
271 @return: domain names
272 """
273 self.refresh()
274 doms = self.domains.values()
275 doms.sort(lambda x, y: cmp(x.name, y.name))
276 return map(lambda x: x.name, doms)
278 def domain_ls_ids(self):
279 """Get list of domain ids.
281 @return: domain names
282 """
283 self.refresh()
284 return self.domains.keys()
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)
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 return XendCheckpoint.save(self, fd, dominfo)
568 except OSError, ex:
569 raise XendError("can't write guest state file %s: %s" %
570 (dst, ex[1]))
572 def domain_pincpu(self, id, vcpu, cpumap):
573 """Set which cpus vcpu can use
575 @param id: domain
576 @param vcpu: vcpu number
577 @param cpumap: bitmap of usbale cpus
578 """
579 dominfo = self.domain_lookup(id)
580 try:
581 return xc.domain_pincpu(dominfo.id, vcpu, cpumap)
582 except Exception, ex:
583 raise XendError(str(ex))
585 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
586 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
587 """
588 dominfo = self.domain_lookup(id)
589 try:
590 return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv,
591 warpback=warpback, warpvalue=warpvalue,
592 warpl=warpl, warpu=warpu)
593 except Exception, ex:
594 raise XendError(str(ex))
596 def domain_cpu_bvt_get(self, id):
597 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
598 """
599 dominfo = self.domain_lookup(id)
600 try:
601 return xc.bvtsched_domain_get(dominfo.id)
602 except Exception, ex:
603 raise XendError(str(ex))
606 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
607 """Set Simple EDF scheduler parameters for a domain.
608 """
609 dominfo = self.domain_lookup(id)
610 try:
611 return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight)
612 except Exception, ex:
613 raise XendError(str(ex))
615 def domain_cpu_sedf_get(self, id):
616 """Get Simple EDF scheduler parameters for a domain.
617 """
618 dominfo = self.domain_lookup(id)
619 try:
620 return xc.sedf_domain_get(dominfo.id)
621 except Exception, ex:
622 raise XendError(str(ex))
624 def domain_device_create(self, id, devconfig):
625 """Create a new device for a domain.
627 @param id: domain id
628 @param devconfig: device configuration
629 """
630 dominfo = self.domain_lookup(id)
631 val = dominfo.device_create(devconfig)
632 dominfo.exportToDB()
633 return val
635 def domain_device_configure(self, id, devconfig, devid):
636 """Configure an existing device for a domain.
638 @param id: domain id
639 @param devconfig: device configuration
640 @param devid: device id
641 @return: updated device configuration
642 """
643 dominfo = self.domain_lookup(id)
644 val = dominfo.device_configure(devconfig, devid)
645 dominfo.exportToDB()
646 return val
648 def domain_device_refresh(self, id, type, devid):
649 """Refresh a device.
651 @param id: domain id
652 @param devid: device id
653 @param type: device type
654 """
655 dominfo = self.domain_lookup(id)
656 val = dominfo.device_refresh(type, devid)
657 dominfo.exportToDB()
658 return val
660 def domain_device_destroy(self, id, type, devid):
661 """Destroy a device.
663 @param id: domain id
664 @param devid: device id
665 @param type: device type
666 """
667 dominfo = self.domain_lookup(id)
668 val = dominfo.device_destroy(type, devid)
669 dominfo.exportToDB()
670 return val
672 def domain_devtype_ls(self, id, type):
673 """Get list of device sxprs for a domain.
675 @param id: domain
676 @param type: device type
677 @return: device sxprs
678 """
679 dominfo = self.domain_lookup(id)
680 return dominfo.getDeviceSxprs(type)
682 def domain_devtype_get(self, id, type, devid):
683 """Get a device from a domain.
685 @param id: domain
686 @param type: device type
687 @param devid: device id
688 @return: device object (or None)
689 """
690 dominfo = self.domain_lookup(id)
691 return dominfo.getDevice(type, devid)
693 def domain_vif_limit_set(self, id, vif, credit, period):
694 """Limit the vif's transmission rate
695 """
696 dominfo = self.domain_lookup(id)
697 dev = dominfo.getDevice('vif', vif)
698 if not dev:
699 raise XendError("invalid vif")
700 return dev.setCreditLimit(credit, period)
702 def domain_shadow_control(self, id, op):
703 """Shadow page control.
705 @param id: domain
706 @param op: operation
707 """
708 dominfo = self.domain_lookup(id)
709 try:
710 return xc.shadow_control(dominfo.id, op)
711 except Exception, ex:
712 raise XendError(str(ex))
714 def domain_maxmem_set(self, id, mem):
715 """Set the memory limit for a domain.
717 @param id: domain
718 @param mem: memory limit (in MB)
719 @return: 0 on success, -1 on error
720 """
721 dominfo = self.domain_lookup(id)
722 maxmem = int(mem) * 1024
723 try:
724 return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem)
725 except Exception, ex:
726 raise XendError(str(ex))
728 def domain_mem_target_set(self, id, mem):
729 """Set the memory target for a domain.
731 @param id: domain
732 @param mem: memory target (in MB)
733 @return: 0 on success, -1 on error
734 """
735 dominfo = self.domain_lookup(id)
736 return dominfo.mem_target_set(mem)
738 def domain_vcpu_hotplug(self, id, vcpu, state):
739 """Enable or disable VCPU vcpu in DOM id
741 @param id: domain
742 @param vcpu: target VCPU in domain
743 @param state: which state VCPU will become
744 @return: 0 on success, -1 on error
745 """
747 dominfo = self.domain_lookup(id)
748 return dominfo.vcpu_hotplug(vcpu, state)
750 def domain_dumpcore(self, id):
751 """Save a core dump for a crashed domain.
753 @param id: domain
754 """
755 dominfo = self.domain_lookup(id)
756 corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id)
757 try:
758 xc.domain_dumpcore(dom=dominfo.id, corefile=corefile)
759 except Exception, ex:
760 log.warning("Dumpcore failed, id=%s name=%s: %s",
761 dominfo.id, dominfo.name, ex)
763 def instance():
764 """Singleton constructor. Use this instead of the class constructor.
765 """
766 global inst
767 try:
768 inst
769 except:
770 inst = XendDomain()
771 return inst