direct-io.hg

view tools/python/xen/xend/XendDomain.py @ 6802:cc1572db6a3d

Call dominfo.device_delete instead of non-existant dominfo.device_destroy.
Signed-off-by: Sean Dague <sean@dague.net>
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Wed Sep 14 12:49:13 2005 +0000 (2005-09-14)
parents 22d08cc7f739
children 383f1336c305 e2f0a6fdb7d9
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 if not domdb.has_key("xend"):
134 continue
135 db = domdb.addChild("xend")
136 try:
137 domid = int(db.id)
138 except:
139 domid = None
140 # XXX if domid in self.domains, then something went wrong
141 if (domid is None) or (domid in self.domains):
142 domdb.delete()
143 elif domid in doms:
144 try:
145 self._new_domain(db.uuid, db, doms[domid])
146 except Exception, ex:
147 log.exception("Error recreating domain info: id=%d", domid)
148 self._delete_domain(domid)
149 else:
150 self._delete_domain(domid)
151 self.refresh(cleanup=True)
153 dom0 = self.domain_lookup(0)
154 if not dom0:
155 dom0 = self.domain_unknown(0)
156 dom0.dom0_init_store()
158 def close(self):
159 pass
161 def _new_domain(self, uuid, db, info):
162 """Create a domain entry from saved info.
164 @param db: saved info from the db
165 @param info: domain info from xen
166 @return: domain
167 """
168 dominfo = XendDomainInfo.recreate(uuid, db, info)
169 self.domains[dominfo.id] = dominfo
170 return dominfo
172 def _add_domain(self, info, notify=True):
173 """Add a domain entry to the tables.
175 @param info: domain info object
176 @param notify: send a domain created event if true
177 """
178 # Remove entries under the wrong id.
179 for i, d in self.domains.items():
180 if i != d.id:
181 del self.domains[i]
182 self.dbmap.delete(d.uuid)
183 if info.id in self.domains:
184 notify = False
185 self.domains[info.id] = info
186 info.exportToDB(save=True)
187 if notify:
188 eserver.inject('xend.domain.create', [info.name, info.id])
190 def _delete_domain(self, id, notify=True):
191 """Remove a domain from the tables.
193 @param id: domain id
194 @param notify: send a domain died event if true
195 """
196 try:
197 if self.xen_domain(id):
198 return
199 except:
200 pass
201 info = self.domains.get(id)
202 if info:
203 del self.domains[id]
204 info.cleanup()
205 info.delete()
206 if notify:
207 eserver.inject('xend.domain.died', [info.name, info.id])
208 # XXX this should not be needed
209 for domdb in self.dbmap.values():
210 if not domdb.has_key("xend"):
211 continue
212 db = domdb.addChild("xend")
213 try:
214 domid = int(db.id)
215 except:
216 domid = None
217 if (domid is None) or (domid == id):
218 domdb.delete()
220 def reap(self):
221 """Look for domains that have crashed or stopped.
222 Tidy them up.
223 """
224 casualties = []
225 doms = self.xen_domains()
226 for d in doms.values():
227 dead = 0
228 dead = dead or (d['crashed'] or d['shutdown'])
229 dead = dead or (d['dying'] and
230 not(d['running'] or d['paused'] or d['blocked']))
231 if dead:
232 casualties.append(d)
233 for d in casualties:
234 id = d['dom']
235 dominfo = self.domains.get(id)
236 name = (dominfo and dominfo.name) or '??'
237 if dominfo and dominfo.is_terminated():
238 continue
239 log.debug('XendDomain>reap> domain died name=%s id=%d', name, id)
240 if d['shutdown']:
241 reason = shutdown_reason(d['shutdown_reason'])
242 log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason)
243 if reason in ['suspend']:
244 if dominfo and dominfo.is_terminated():
245 log.debug('XendDomain>reap> Suspended domain died id=%d', id)
246 else:
247 eserver.inject('xend.domain.suspended', [name, id])
248 if dominfo:
249 dominfo.state_set("suspended")
250 continue
251 if reason in ['poweroff', 'reboot']:
252 eserver.inject('xend.domain.exit', [name, id, reason])
253 self.domain_restart_schedule(id, reason)
254 else:
255 if xroot.get_enable_dump():
256 self.domain_dumpcore(id)
257 eserver.inject('xend.domain.exit', [name, id, 'crash'])
258 self.final_domain_destroy(id)
260 def refresh(self, cleanup=False):
261 """Refresh domain list from Xen.
262 """
263 if cleanup:
264 self.reap()
265 doms = self.xen_domains()
266 # Remove entries for domains that no longer exist.
267 # Update entries for existing domains.
268 do_domain_restarts = False
269 for d in self.domains.values():
270 info = doms.get(d.id)
271 if info:
272 d.update(info)
273 elif d.restart_pending():
274 do_domain_restarts = True
275 else:
276 self._delete_domain(d.id)
277 if cleanup and do_domain_restarts:
278 scheduler.now(self.domain_restarts)
280 def update_domain(self, id):
281 """Update information for a single domain.
283 @param id: domain id
284 """
285 dominfo = self.xen_domain(id)
286 if dominfo:
287 d = self.domains.get(id)
288 if d:
289 d.update(dominfo)
290 else:
291 self._delete_domain(id)
293 def domain_create(self, config):
294 """Create a domain from a configuration.
296 @param config: configuration
297 @return: domain
298 """
299 dominfo = XendDomainInfo.create(self.dbmap, config)
300 return dominfo
302 def domain_restart(self, dominfo):
303 """Restart a domain.
305 @param dominfo: domain object
306 """
307 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
308 eserver.inject("xend.domain.restart",
309 [dominfo.name, dominfo.id, "begin"])
310 try:
311 dominfo.restart()
312 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
313 eserver.inject("xend.domain.restart",
314 [dominfo.name, dominfo.id, "success"])
315 self.domain_unpause(dominfo.id)
316 except Exception, ex:
317 log.exception("Exception restarting domain: name=%s id=%s",
318 dominfo.name, dominfo.id)
319 eserver.inject("xend.domain.restart",
320 [dominfo.name, dominfo.id, "fail"])
321 return dominfo
323 def domain_configure(self, vmconfig):
324 """Configure an existing domain. This is intended for internal
325 use by domain restore and migrate.
327 @param vmconfig: vm configuration
328 """
329 config = sxp.child_value(vmconfig, 'config')
330 dominfo = XendDomainInfo.restore(self.dbmap, config)
331 return dominfo
333 def domain_restore(self, src, progress=False):
334 """Restore a domain from file.
336 @param src: source file
337 @param progress: output progress if true
338 """
340 try:
341 fd = os.open(src, os.O_RDONLY)
342 return XendCheckpoint.restore(self, fd)
343 except OSError, ex:
344 raise XendError("can't read guest state file %s: %s" %
345 (src, ex[1]))
347 def domain_get(self, id):
348 """Get up-to-date info about a domain.
350 @param id: domain id
351 @return: domain object (or None)
352 """
353 self.update_domain(id)
354 return self.domains.get(id)
356 def domain_unknown(self, id):
357 try:
358 info = self.xen_domain(id)
359 if info:
360 uuid = getUuid()
361 log.info(
362 "Creating entry for unknown domain: id=%d uuid=%s",
363 id, uuid)
364 db = self.dbmap.addChild("%s/xend" % uuid)
365 dominfo = XendDomainInfo.recreate(uuid, db, info)
366 dominfo.setdom(id)
367 self._add_domain(dominfo)
368 return dominfo
369 except Exception, ex:
370 log.exception("Error creating domain info: id=%d", id)
371 return None
373 def domain_lookup(self, id):
374 return self.domains.get(id)
376 def domain_lookup_by_name(self, name):
377 dominfo = self.domains.get_by_name(name)
378 if not dominfo:
379 try:
380 id = int(name)
381 dominfo = self.domain_lookup(id)
382 except ValueError:
383 pass
384 return dominfo
386 def domain_unpause(self, id):
387 """Unpause domain execution.
389 @param id: domain id
390 """
391 dominfo = self.domain_lookup(id)
392 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
393 try:
394 return xc.domain_unpause(dom=dominfo.id)
395 except Exception, ex:
396 raise XendError(str(ex))
398 def domain_pause(self, id):
399 """Pause domain execution.
401 @param id: domain id
402 """
403 dominfo = self.domain_lookup(id)
404 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
405 try:
406 return xc.domain_pause(dom=dominfo.id)
407 except Exception, ex:
408 raise XendError(str(ex))
410 def domain_shutdown(self, id, reason='poweroff'):
411 """Shutdown domain (nicely).
412 - poweroff: restart according to exit code and restart mode
413 - reboot: restart on exit
414 - halt: do not restart
416 Returns immediately.
418 @param id: domain id
419 @param reason: shutdown type: poweroff, reboot, suspend, halt
420 """
421 dominfo = self.domain_lookup(id)
422 self.domain_restart_schedule(dominfo.id, reason, force=True)
423 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
424 if reason == 'halt':
425 reason = 'poweroff'
426 val = dominfo.shutdown(reason)
427 if not reason in ['suspend']:
428 self.domain_shutdowns()
429 return val
431 def domain_sysrq(self, id, key):
432 """Send a SysRq to a domain
433 """
434 dominfo = self.domain_lookup(id)
435 val = dominfo.send_sysrq(key)
436 return val
438 def domain_shutdowns(self):
439 """Process pending domain shutdowns.
440 Destroys domains whose shutdowns have timed out.
441 """
442 timeout = SHUTDOWN_TIMEOUT + 1
443 for dominfo in self.domains.values():
444 if not dominfo.shutdown_pending:
445 # domain doesn't need shutdown
446 continue
447 id = dominfo.id
448 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
449 if left <= 0:
450 # Shutdown expired - destroy domain.
451 try:
452 log.info("Domain shutdown timeout expired: name=%s id=%s",
453 dominfo.name, id)
454 self.domain_destroy(id, reason=
455 dominfo.shutdown_pending['reason'])
456 except Exception:
457 pass
458 else:
459 # Shutdown still pending.
460 timeout = min(timeout, left)
461 if timeout <= SHUTDOWN_TIMEOUT:
462 # Pending shutdowns remain - reschedule.
463 scheduler.later(timeout, self.domain_shutdowns)
465 def domain_restart_schedule(self, id, reason, force=False):
466 """Schedule a restart for a domain if it needs one.
468 @param id: domain id
469 @param reason: shutdown reason
470 """
471 log.debug('domain_restart_schedule> %d %s %d', id, reason, force)
472 dominfo = self.domain_lookup(id)
473 if not dominfo:
474 return
475 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
476 if restart:
477 log.info('Scheduling restart for domain: name=%s id=%s',
478 dominfo.name, dominfo.id)
479 eserver.inject("xend.domain.restart",
480 [dominfo.name, dominfo.id, "schedule"])
481 dominfo.restarting()
482 else:
483 log.info('Cancelling restart for domain: name=%s id=%s',
484 dominfo.name, dominfo.id)
485 eserver.inject("xend.domain.restart",
486 [dominfo.name, dominfo.id, "cancel"])
487 dominfo.restart_cancel()
489 def domain_restarts(self):
490 """Execute any scheduled domain restarts for domains that have gone.
491 """
492 doms = self.xen_domains()
493 for dominfo in self.domains.values():
494 if not dominfo.restart_pending():
495 continue
496 print 'domain_restarts>', dominfo.name, dominfo.id
497 info = doms.get(dominfo.id)
498 if info:
499 # Don't execute restart for domains still running.
500 print 'domain_restarts> still runnning: ', dominfo.name
501 continue
502 # Remove it from the restarts.
503 print 'domain_restarts> restarting: ', dominfo.name
504 self.domain_restart(dominfo)
506 def final_domain_destroy(self, id):
507 """Final destruction of a domain..
509 @param id: domain id
510 """
511 try:
512 dominfo = self.domain_lookup(id)
513 log.info('Destroying domain: name=%s', dominfo.name)
514 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
515 val = dominfo.destroy()
516 except:
517 #todo
518 try:
519 val = xc.domain_destroy(dom=id)
520 except Exception, ex:
521 raise XendError(str(ex))
522 return val
524 def domain_destroy(self, id, reason='halt'):
525 """Terminate domain immediately.
526 - halt: cancel any restart for the domain
527 - reboot schedule a restart for the domain
529 @param id: domain id
530 """
531 self.domain_restart_schedule(id, reason, force=True)
532 val = self.final_domain_destroy(id)
533 return val
535 def domain_migrate(self, id, dst, live=False, resource=0):
536 """Start domain migration.
538 @param id: domain id
539 """
540 # Need a cancel too?
541 # Don't forget to cancel restart for it.
542 dominfo = self.domain_lookup(id)
544 port = xroot.get_xend_relocation_port()
545 sock = relocate.setupRelocation(dst, port)
547 # temporarily rename domain for localhost migration
548 if dst == "localhost":
549 dominfo.name = "tmp-" + dominfo.name
551 try:
552 XendCheckpoint.save(self, sock.fileno(), dominfo, live)
553 except:
554 if dst == "localhost":
555 dominfo.name = string.replace(dominfo.name, "tmp-", "", 1)
556 raise
558 return None
560 def domain_save(self, id, dst, progress=False):
561 """Start saving a domain to file.
563 @param id: domain id
564 @param dst: destination file
565 @param progress: output progress if true
566 """
568 try:
569 dominfo = self.domain_lookup(id)
571 fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
573 # For now we don't support 'live checkpoint'
574 return XendCheckpoint.save(self, fd, dominfo, False)
576 except OSError, ex:
577 raise XendError("can't write guest state file %s: %s" %
578 (dst, ex[1]))
580 def domain_pincpu(self, id, vcpu, cpumap):
581 """Set which cpus vcpu can use
583 @param id: domain
584 @param vcpu: vcpu number
585 @param cpumap: bitmap of usbale cpus
586 """
587 dominfo = self.domain_lookup(id)
588 try:
589 return xc.domain_pincpu(dominfo.id, vcpu, cpumap)
590 except Exception, ex:
591 raise XendError(str(ex))
593 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
594 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
595 """
596 dominfo = self.domain_lookup(id)
597 try:
598 return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv,
599 warpback=warpback, warpvalue=warpvalue,
600 warpl=warpl, warpu=warpu)
601 except Exception, ex:
602 raise XendError(str(ex))
604 def domain_cpu_bvt_get(self, id):
605 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
606 """
607 dominfo = self.domain_lookup(id)
608 try:
609 return xc.bvtsched_domain_get(dominfo.id)
610 except Exception, ex:
611 raise XendError(str(ex))
614 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
615 """Set Simple EDF scheduler parameters for a domain.
616 """
617 dominfo = self.domain_lookup(id)
618 try:
619 return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight)
620 except Exception, ex:
621 raise XendError(str(ex))
623 def domain_cpu_sedf_get(self, id):
624 """Get Simple EDF scheduler parameters for a domain.
625 """
626 dominfo = self.domain_lookup(id)
627 try:
628 return xc.sedf_domain_get(dominfo.id)
629 except Exception, ex:
630 raise XendError(str(ex))
632 def domain_device_create(self, id, devconfig):
633 """Create a new device for a domain.
635 @param id: domain id
636 @param devconfig: device configuration
637 """
638 dominfo = self.domain_lookup(id)
639 val = dominfo.device_create(devconfig)
640 dominfo.exportToDB()
641 return val
643 def domain_device_configure(self, id, devconfig, devid):
644 """Configure an existing device for a domain.
646 @param id: domain id
647 @param devconfig: device configuration
648 @param devid: device id
649 @return: updated device configuration
650 """
651 dominfo = self.domain_lookup(id)
652 val = dominfo.device_configure(devconfig, devid)
653 dominfo.exportToDB()
654 return val
656 def domain_device_refresh(self, id, type, devid):
657 """Refresh a device.
659 @param id: domain id
660 @param devid: device id
661 @param type: device type
662 """
663 dominfo = self.domain_lookup(id)
664 val = dominfo.device_refresh(type, devid)
665 dominfo.exportToDB()
666 return val
668 def domain_device_destroy(self, id, type, devid):
669 """Destroy a device.
671 @param id: domain id
672 @param devid: device id
673 @param type: device type
674 """
675 dominfo = self.domain_lookup(id)
676 val = dominfo.device_delete(type, devid)
677 dominfo.exportToDB()
678 return val
680 def domain_devtype_ls(self, id, type):
681 """Get list of device sxprs for a domain.
683 @param id: domain
684 @param type: device type
685 @return: device sxprs
686 """
687 dominfo = self.domain_lookup(id)
688 return dominfo.getDeviceSxprs(type)
690 def domain_devtype_get(self, id, type, devid):
691 """Get a device from a domain.
693 @param id: domain
694 @param type: device type
695 @param devid: device id
696 @return: device object (or None)
697 """
698 dominfo = self.domain_lookup(id)
699 return dominfo.getDevice(type, devid)
701 def domain_vif_limit_set(self, id, vif, credit, period):
702 """Limit the vif's transmission rate
703 """
704 dominfo = self.domain_lookup(id)
705 dev = dominfo.getDevice('vif', vif)
706 if not dev:
707 raise XendError("invalid vif")
708 return dev.setCreditLimit(credit, period)
710 def domain_shadow_control(self, id, op):
711 """Shadow page control.
713 @param id: domain
714 @param op: operation
715 """
716 dominfo = self.domain_lookup(id)
717 try:
718 return xc.shadow_control(dominfo.id, op)
719 except Exception, ex:
720 raise XendError(str(ex))
722 def domain_maxmem_set(self, id, mem):
723 """Set the memory limit for a domain.
725 @param id: domain
726 @param mem: memory limit (in MB)
727 @return: 0 on success, -1 on error
728 """
729 dominfo = self.domain_lookup(id)
730 maxmem = int(mem) * 1024
731 try:
732 return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem)
733 except Exception, ex:
734 raise XendError(str(ex))
736 def domain_mem_target_set(self, id, mem):
737 """Set the memory target for a domain.
739 @param id: domain
740 @param mem: memory target (in MB)
741 @return: 0 on success, -1 on error
742 """
743 dominfo = self.domain_lookup(id)
744 return dominfo.setMemoryTarget(mem * (1 << 20))
746 def domain_vcpu_hotplug(self, id, vcpu, state):
747 """Enable or disable VCPU vcpu in DOM id
749 @param id: domain
750 @param vcpu: target VCPU in domain
751 @param state: which state VCPU will become
752 @return: 0 on success, -1 on error
753 """
755 dominfo = self.domain_lookup(id)
756 return dominfo.vcpu_hotplug(vcpu, state)
758 def domain_dumpcore(self, id):
759 """Save a core dump for a crashed domain.
761 @param id: domain
762 """
763 dominfo = self.domain_lookup(id)
764 corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id)
765 try:
766 xc.domain_dumpcore(dom=dominfo.id, corefile=corefile)
767 except Exception, ex:
768 log.warning("Dumpcore failed, id=%s name=%s: %s",
769 dominfo.id, dominfo.name, ex)
771 def instance():
772 """Singleton constructor. Use this instead of the class constructor.
773 """
774 global inst
775 try:
776 inst
777 except:
778 inst = XendDomain()
779 return inst