ia64/xen-unstable

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

merge?
author cl349@firebug.cl.cam.ac.uk
date Wed Sep 07 19:01:31 2005 +0000 (2005-09-07)
parents 0e2b1e04d4cb c9e1ddf85324
children b2f4823b6ff0 b35215021b32 c2558a2fe658 8ca0f98ba8e2 72e4e2aab342
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 relocate
40 from xen.xend.uuid import getUuid
41 from xen.xend.xenstore import XenNode, DBMap
43 __all__ = [ "XendDomain" ]
45 SHUTDOWN_TIMEOUT = 30
47 class XendDomainDict(dict):
48 def get_by_name(self, name):
49 try:
50 return filter(lambda d: d.name == name, self.values())[0]
51 except IndexError, err:
52 return None
54 class XendDomain:
55 """Index of all domains. Singleton.
56 """
58 """Dict of domain info indexed by domain id."""
59 domains = None
61 def __init__(self):
62 # Hack alert. Python does not support mutual imports, but XendDomainInfo
63 # needs access to the XendDomain instance to look up domains. Attempting
64 # to import XendDomain from XendDomainInfo causes unbounded recursion.
65 # So we stuff the XendDomain instance (self) into xroot's components.
66 xroot.add_component("xen.xend.XendDomain", self)
67 self.domains = XendDomainDict()
68 self.dbmap = DBMap(db=XenNode("/domain"))
69 self.watchReleaseDomain()
70 self.initial_refresh()
72 def list(self):
73 """Get list of domain objects.
75 @return: domain objects
76 """
77 self.refresh()
78 return self.domains.values()
80 def list_sorted(self):
81 """Get list of domain objects, sorted by name.
83 @return: domain objects
84 """
85 doms = self.list()
86 doms.sort(lambda x, y: cmp(x.name, y.name))
87 return doms
89 def list_names(self):
90 """Get list of domain names.
92 @return: domain names
93 """
94 doms = self.list_sorted()
95 return map(lambda x: x.name, doms)
97 def onReleaseDomain(self):
98 self.refresh(cleanup=True)
100 def watchReleaseDomain(self):
101 from xen.xend.xenstore.xswatch import xswatch
102 self.releaseDomain = xswatch("@releaseDomain", self.onReleaseDomain)
104 def xen_domains(self):
105 """Get table of domains indexed by id from xc.
106 """
107 domlist = xc.domain_getinfo()
108 doms = {}
109 for d in domlist:
110 domid = d['dom']
111 doms[domid] = d
112 return doms
114 def xen_domain(self, dom):
115 """Get info about a single domain from xc.
116 Returns None if not found.
118 @param dom domain id (int)
119 """
120 dominfo = xc.domain_getinfo(dom, 1)
121 if dominfo == [] or dominfo[0]['dom'] != dom:
122 dominfo = None
123 else:
124 dominfo = dominfo[0]
125 return dominfo
127 def initial_refresh(self):
128 """Refresh initial domain info from db.
129 """
130 doms = self.xen_domains()
131 self.dbmap.readDB()
132 for domdb in self.dbmap.values():
133 try:
134 domid = int(domdb.id)
135 except:
136 domid = None
137 # XXX if domid in self.domains, then something went wrong
138 if (domid is None) or (domid in self.domains):
139 domdb.delete()
140 elif domid in doms:
141 try:
142 self._new_domain(domdb, doms[domid])
143 except Exception, ex:
144 log.exception("Error recreating domain info: id=%d", domid)
145 self._delete_domain(domid)
146 else:
147 self._delete_domain(domid)
148 self.refresh(cleanup=True)
150 dom0 = self.domain_lookup(0)
151 if not dom0:
152 dom0 = self.domain_unknown(0)
153 dom0.dom0_init_store()
155 def close(self):
156 pass
158 def _new_domain(self, db, info):
159 """Create a domain entry from saved info.
161 @param db: saved info from the db
162 @param info: domain info from xen
163 @return: domain
164 """
165 dominfo = XendDomainInfo.recreate(db, info)
166 self.domains[dominfo.id] = dominfo
167 return dominfo
169 def _add_domain(self, info, notify=True):
170 """Add a domain entry to the tables.
172 @param info: domain info object
173 @param notify: send a domain created event if true
174 """
175 # Remove entries under the wrong id.
176 for i, d in self.domains.items():
177 if i != d.id:
178 del self.domains[i]
179 self.dbmap.delete(d.uuid)
180 if info.id in self.domains:
181 notify = False
182 self.domains[info.id] = info
183 info.exportToDB(save=True)
184 if notify:
185 eserver.inject('xend.domain.create', [info.name, info.id])
187 def _delete_domain(self, id, notify=True):
188 """Remove a domain from the tables.
190 @param id: domain id
191 @param notify: send a domain died event if true
192 """
193 try:
194 if self.xen_domain(id):
195 return
196 except:
197 pass
198 info = self.domains.get(id)
199 if info:
200 del self.domains[id]
201 info.cleanup()
202 info.delete()
203 if notify:
204 eserver.inject('xend.domain.died', [info.name, info.id])
205 # XXX this should not be needed
206 for domdb in self.dbmap.values():
207 try:
208 domid = int(domdb.id)
209 except:
210 domid = None
211 if (domid is None) or (domid == id):
212 domdb.delete()
214 def reap(self):
215 """Look for domains that have crashed or stopped.
216 Tidy them up.
217 """
218 casualties = []
219 doms = self.xen_domains()
220 for d in doms.values():
221 dead = 0
222 dead = dead or (d['crashed'] or d['shutdown'])
223 dead = dead or (d['dying'] and
224 not(d['running'] or d['paused'] or d['blocked']))
225 if dead:
226 casualties.append(d)
227 for d in casualties:
228 id = d['dom']
229 dominfo = self.domains.get(id)
230 name = (dominfo and dominfo.name) or '??'
231 if dominfo and dominfo.is_terminated():
232 continue
233 log.debug('XendDomain>reap> domain died name=%s id=%d', name, id)
234 if d['shutdown']:
235 reason = shutdown_reason(d['shutdown_reason'])
236 log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason)
237 if reason in ['suspend']:
238 if dominfo and dominfo.is_terminated():
239 log.debug('XendDomain>reap> Suspended domain died id=%d', id)
240 else:
241 eserver.inject('xend.domain.suspended', [name, id])
242 if dominfo:
243 dominfo.state_set("suspended")
244 continue
245 if reason in ['poweroff', 'reboot']:
246 eserver.inject('xend.domain.exit', [name, id, reason])
247 self.domain_restart_schedule(id, reason)
248 else:
249 if xroot.get_enable_dump():
250 self.domain_dumpcore(id)
251 eserver.inject('xend.domain.exit', [name, id, 'crash'])
252 self.final_domain_destroy(id)
254 def refresh(self, cleanup=False):
255 """Refresh domain list from Xen.
256 """
257 if cleanup:
258 self.reap()
259 doms = self.xen_domains()
260 # Remove entries for domains that no longer exist.
261 # Update entries for existing domains.
262 do_domain_restarts = False
263 for d in self.domains.values():
264 info = doms.get(d.id)
265 if info:
266 d.update(info)
267 elif d.restart_pending():
268 do_domain_restarts = True
269 else:
270 self._delete_domain(d.id)
271 if cleanup and do_domain_restarts:
272 scheduler.now(self.domain_restarts)
274 def update_domain(self, id):
275 """Update information for a single domain.
277 @param id: domain id
278 """
279 dominfo = self.xen_domain(id)
280 if dominfo:
281 d = self.domains.get(id)
282 if d:
283 d.update(dominfo)
284 else:
285 self._delete_domain(id)
287 def domain_create(self, config):
288 """Create a domain from a configuration.
290 @param config: configuration
291 @return: domain
292 """
293 dominfo = XendDomainInfo.create(self.dbmap, config)
294 return dominfo
296 def domain_restart(self, dominfo):
297 """Restart a domain.
299 @param dominfo: domain object
300 """
301 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
302 eserver.inject("xend.domain.restart",
303 [dominfo.name, dominfo.id, "begin"])
304 try:
305 dominfo.restart()
306 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
307 eserver.inject("xend.domain.restart",
308 [dominfo.name, dominfo.id, "success"])
309 self.domain_unpause(dominfo.id)
310 except Exception, ex:
311 log.exception("Exception restarting domain: name=%s id=%s",
312 dominfo.name, dominfo.id)
313 eserver.inject("xend.domain.restart",
314 [dominfo.name, dominfo.id, "fail"])
315 return dominfo
317 def domain_configure(self, vmconfig):
318 """Configure an existing domain. This is intended for internal
319 use by domain restore and migrate.
321 @param vmconfig: vm configuration
322 """
323 config = sxp.child_value(vmconfig, 'config')
324 dominfo = XendDomainInfo.restore(self.dbmap, config)
325 return dominfo
327 def domain_restore(self, src, progress=False):
328 """Restore a domain from file.
330 @param src: source file
331 @param progress: output progress if true
332 """
334 try:
335 fd = os.open(src, os.O_RDONLY)
336 return XendCheckpoint.restore(self, fd)
337 except OSError, ex:
338 raise XendError("can't read guest state file %s: %s" %
339 (src, ex[1]))
341 def domain_get(self, id):
342 """Get up-to-date info about a domain.
344 @param id: domain id
345 @return: domain object (or None)
346 """
347 self.update_domain(id)
348 return self.domains.get(id)
350 def domain_unknown(self, id):
351 try:
352 info = self.xen_domain(id)
353 if info:
354 uuid = getUuid()
355 log.info(
356 "Creating entry for unknown domain: id=%d uuid=%s",
357 id, uuid)
358 db = self.dbmap.addChild(uuid)
359 dominfo = XendDomainInfo.recreate(db, info)
360 dominfo.setdom(id)
361 self._add_domain(dominfo)
362 return dominfo
363 except Exception, ex:
364 log.exception("Error creating domain info: id=%d", id)
365 return None
367 def domain_lookup(self, id):
368 return self.domains.get(id)
370 def domain_lookup_by_name(self, name):
371 dominfo = self.domains.get_by_name(name)
372 if not dominfo:
373 try:
374 id = int(name)
375 dominfo = self.domain_lookup(id)
376 except ValueError:
377 pass
378 return dominfo
380 def domain_unpause(self, id):
381 """Unpause domain execution.
383 @param id: domain id
384 """
385 dominfo = self.domain_lookup(id)
386 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
387 try:
388 return xc.domain_unpause(dom=dominfo.id)
389 except Exception, ex:
390 raise XendError(str(ex))
392 def domain_pause(self, id):
393 """Pause domain execution.
395 @param id: domain id
396 """
397 dominfo = self.domain_lookup(id)
398 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
399 try:
400 return xc.domain_pause(dom=dominfo.id)
401 except Exception, ex:
402 raise XendError(str(ex))
404 def domain_shutdown(self, id, reason='poweroff'):
405 """Shutdown domain (nicely).
406 - poweroff: restart according to exit code and restart mode
407 - reboot: restart on exit
408 - halt: do not restart
410 Returns immediately.
412 @param id: domain id
413 @param reason: shutdown type: poweroff, reboot, suspend, halt
414 """
415 dominfo = self.domain_lookup(id)
416 self.domain_restart_schedule(dominfo.id, reason, force=True)
417 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
418 if reason == 'halt':
419 reason = 'poweroff'
420 val = dominfo.shutdown(reason)
421 if not reason in ['suspend']:
422 self.domain_shutdowns()
423 return val
425 def domain_sysrq(self, id, key):
426 """Send a SysRq to a domain
427 """
428 dominfo = self.domain_lookup(id)
429 val = dominfo.send_sysrq(key)
430 return val
432 def domain_shutdowns(self):
433 """Process pending domain shutdowns.
434 Destroys domains whose shutdowns have timed out.
435 """
436 timeout = SHUTDOWN_TIMEOUT + 1
437 for dominfo in self.domains.values():
438 if not dominfo.shutdown_pending:
439 # domain doesn't need shutdown
440 continue
441 id = dominfo.id
442 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
443 if left <= 0:
444 # Shutdown expired - destroy domain.
445 try:
446 log.info("Domain shutdown timeout expired: name=%s id=%s",
447 dominfo.name, id)
448 self.domain_destroy(id, reason=
449 dominfo.shutdown_pending['reason'])
450 except Exception:
451 pass
452 else:
453 # Shutdown still pending.
454 timeout = min(timeout, left)
455 if timeout <= SHUTDOWN_TIMEOUT:
456 # Pending shutdowns remain - reschedule.
457 scheduler.later(timeout, self.domain_shutdowns)
459 def domain_restart_schedule(self, id, reason, force=False):
460 """Schedule a restart for a domain if it needs one.
462 @param id: domain id
463 @param reason: shutdown reason
464 """
465 log.debug('domain_restart_schedule> %d %s %d', id, reason, force)
466 dominfo = self.domain_lookup(id)
467 if not dominfo:
468 return
469 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
470 if restart:
471 log.info('Scheduling restart for domain: name=%s id=%s',
472 dominfo.name, dominfo.id)
473 eserver.inject("xend.domain.restart",
474 [dominfo.name, dominfo.id, "schedule"])
475 dominfo.restarting()
476 else:
477 log.info('Cancelling restart for domain: name=%s id=%s',
478 dominfo.name, dominfo.id)
479 eserver.inject("xend.domain.restart",
480 [dominfo.name, dominfo.id, "cancel"])
481 dominfo.restart_cancel()
483 def domain_restarts(self):
484 """Execute any scheduled domain restarts for domains that have gone.
485 """
486 doms = self.xen_domains()
487 for dominfo in self.domains.values():
488 if not dominfo.restart_pending():
489 continue
490 print 'domain_restarts>', dominfo.name, dominfo.id
491 info = doms.get(dominfo.id)
492 if info:
493 # Don't execute restart for domains still running.
494 print 'domain_restarts> still runnning: ', dominfo.name
495 continue
496 # Remove it from the restarts.
497 print 'domain_restarts> restarting: ', dominfo.name
498 self.domain_restart(dominfo)
500 def final_domain_destroy(self, id):
501 """Final destruction of a domain..
503 @param id: domain id
504 """
505 try:
506 dominfo = self.domain_lookup(id)
507 log.info('Destroying domain: name=%s', dominfo.name)
508 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
509 val = dominfo.destroy()
510 except:
511 #todo
512 try:
513 val = xc.domain_destroy(dom=id)
514 except Exception, ex:
515 raise XendError(str(ex))
516 return val
518 def domain_destroy(self, id, reason='halt'):
519 """Terminate domain immediately.
520 - halt: cancel any restart for the domain
521 - reboot schedule a restart for the domain
523 @param id: domain id
524 """
525 self.domain_restart_schedule(id, reason, force=True)
526 val = self.final_domain_destroy(id)
527 return val
529 def domain_migrate(self, id, dst, live=False, resource=0):
530 """Start domain migration.
532 @param id: domain id
533 """
534 # Need a cancel too?
535 # Don't forget to cancel restart for it.
536 dominfo = self.domain_lookup(id)
538 port = xroot.get_xend_relocation_port()
539 sock = relocate.setupRelocation(dst, port)
541 # temporarily rename domain for localhost migration
542 if dst == "localhost":
543 dominfo.name = "tmp-" + dominfo.name
545 try:
546 XendCheckpoint.save(self, sock.fileno(), dominfo, live)
547 except:
548 if dst == "localhost":
549 dominfo.name = string.replace(dominfo.name, "tmp-", "", 1)
550 raise
552 return None
554 def domain_save(self, id, dst, progress=False):
555 """Start saving a domain to file.
557 @param id: domain id
558 @param dst: destination file
559 @param progress: output progress if true
560 """
562 try:
563 dominfo = self.domain_lookup(id)
565 fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
567 # For now we don't support 'live checkpoint'
568 return XendCheckpoint.save(self, fd, dominfo, False)
570 except OSError, ex:
571 raise XendError("can't write guest state file %s: %s" %
572 (dst, ex[1]))
574 def domain_pincpu(self, id, vcpu, cpumap):
575 """Set which cpus vcpu can use
577 @param id: domain
578 @param vcpu: vcpu number
579 @param cpumap: bitmap of usbale cpus
580 """
581 dominfo = self.domain_lookup(id)
582 try:
583 return xc.domain_pincpu(dominfo.id, vcpu, cpumap)
584 except Exception, ex:
585 raise XendError(str(ex))
587 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
588 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
589 """
590 dominfo = self.domain_lookup(id)
591 try:
592 return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv,
593 warpback=warpback, warpvalue=warpvalue,
594 warpl=warpl, warpu=warpu)
595 except Exception, ex:
596 raise XendError(str(ex))
598 def domain_cpu_bvt_get(self, id):
599 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
600 """
601 dominfo = self.domain_lookup(id)
602 try:
603 return xc.bvtsched_domain_get(dominfo.id)
604 except Exception, ex:
605 raise XendError(str(ex))
608 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
609 """Set Simple EDF scheduler parameters for a domain.
610 """
611 dominfo = self.domain_lookup(id)
612 try:
613 return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight)
614 except Exception, ex:
615 raise XendError(str(ex))
617 def domain_cpu_sedf_get(self, id):
618 """Get Simple EDF scheduler parameters for a domain.
619 """
620 dominfo = self.domain_lookup(id)
621 try:
622 return xc.sedf_domain_get(dominfo.id)
623 except Exception, ex:
624 raise XendError(str(ex))
626 def domain_device_create(self, id, devconfig):
627 """Create a new device for a domain.
629 @param id: domain id
630 @param devconfig: device configuration
631 """
632 dominfo = self.domain_lookup(id)
633 val = dominfo.device_create(devconfig)
634 dominfo.exportToDB()
635 return val
637 def domain_device_configure(self, id, devconfig, devid):
638 """Configure an existing device for a domain.
640 @param id: domain id
641 @param devconfig: device configuration
642 @param devid: device id
643 @return: updated device configuration
644 """
645 dominfo = self.domain_lookup(id)
646 val = dominfo.device_configure(devconfig, devid)
647 dominfo.exportToDB()
648 return val
650 def domain_device_refresh(self, id, type, devid):
651 """Refresh a device.
653 @param id: domain id
654 @param devid: device id
655 @param type: device type
656 """
657 dominfo = self.domain_lookup(id)
658 val = dominfo.device_refresh(type, devid)
659 dominfo.exportToDB()
660 return val
662 def domain_device_destroy(self, id, type, devid):
663 """Destroy a device.
665 @param id: domain id
666 @param devid: device id
667 @param type: device type
668 """
669 dominfo = self.domain_lookup(id)
670 val = dominfo.device_destroy(type, devid)
671 dominfo.exportToDB()
672 return val
674 def domain_devtype_ls(self, id, type):
675 """Get list of device sxprs for a domain.
677 @param id: domain
678 @param type: device type
679 @return: device sxprs
680 """
681 dominfo = self.domain_lookup(id)
682 return dominfo.getDeviceSxprs(type)
684 def domain_devtype_get(self, id, type, devid):
685 """Get a device from a domain.
687 @param id: domain
688 @param type: device type
689 @param devid: device id
690 @return: device object (or None)
691 """
692 dominfo = self.domain_lookup(id)
693 return dominfo.getDevice(type, devid)
695 def domain_vif_limit_set(self, id, vif, credit, period):
696 """Limit the vif's transmission rate
697 """
698 dominfo = self.domain_lookup(id)
699 dev = dominfo.getDevice('vif', vif)
700 if not dev:
701 raise XendError("invalid vif")
702 return dev.setCreditLimit(credit, period)
704 def domain_shadow_control(self, id, op):
705 """Shadow page control.
707 @param id: domain
708 @param op: operation
709 """
710 dominfo = self.domain_lookup(id)
711 try:
712 return xc.shadow_control(dominfo.id, op)
713 except Exception, ex:
714 raise XendError(str(ex))
716 def domain_maxmem_set(self, id, mem):
717 """Set the memory limit for a domain.
719 @param id: domain
720 @param mem: memory limit (in MB)
721 @return: 0 on success, -1 on error
722 """
723 dominfo = self.domain_lookup(id)
724 maxmem = int(mem) * 1024
725 try:
726 return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem)
727 except Exception, ex:
728 raise XendError(str(ex))
730 def domain_mem_target_set(self, id, mem):
731 """Set the memory target for a domain.
733 @param id: domain
734 @param mem: memory target (in MB)
735 @return: 0 on success, -1 on error
736 """
737 dominfo = self.domain_lookup(id)
738 return dominfo.mem_target_set(mem)
740 def domain_vcpu_hotplug(self, id, vcpu, state):
741 """Enable or disable VCPU vcpu in DOM id
743 @param id: domain
744 @param vcpu: target VCPU in domain
745 @param state: which state VCPU will become
746 @return: 0 on success, -1 on error
747 """
749 dominfo = self.domain_lookup(id)
750 return dominfo.vcpu_hotplug(vcpu, state)
752 def domain_dumpcore(self, id):
753 """Save a core dump for a crashed domain.
755 @param id: domain
756 """
757 dominfo = self.domain_lookup(id)
758 corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id)
759 try:
760 xc.domain_dumpcore(dom=dominfo.id, corefile=corefile)
761 except Exception, ex:
762 log.warning("Dumpcore failed, id=%s name=%s: %s",
763 dominfo.id, dominfo.name, ex)
765 def instance():
766 """Singleton constructor. Use this instead of the class constructor.
767 """
768 global inst
769 try:
770 inst
771 except:
772 inst = XendDomain()
773 return inst