ia64/xen-unstable

view tools/python/xen/xend/XendDomain.py @ 5395:ccd879d035da

bitkeeper revision 1.1662.1.22 (42a852969xilS9HOsAWARXW2hEx3FQ)

XendDomain.py:
Fix commants and remove debugging output.
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Thu Jun 09 14:30:46 2005 +0000 (2005-06-09)
parents cb9679a15acd
children 6d3e8f90c2df
line source
1 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
2 # Copyright (C) 2005 Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
4 """Handler for domain operations.
5 Nothing here is persistent (across reboots).
6 Needs to be persistent for one uptime.
7 """
8 import errno
9 import os
10 import sys
11 import time
12 import traceback
14 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
16 from xen.xend import sxp
17 from xen.xend import XendRoot; xroot = XendRoot.instance()
18 from xen.xend import XendCheckpoint
19 from xen.xend.XendDomainInfo import XendDomainInfo, shutdown_reason
20 from xen.xend import EventServer; eserver = EventServer.instance()
21 from xen.xend.XendError import XendError
22 from xen.xend.XendLogging import log
23 from xen.xend import scheduler
24 from xen.xend.server import channel
25 from xen.xend.server import relocate
26 from xen.xend.uuid import getUuid
27 from xen.xend.xenstore import XenNode, DBMap
29 __all__ = [ "XendDomain" ]
31 SHUTDOWN_TIMEOUT = 30
33 class XendDomainDict(dict):
34 def get_by_name(self, name):
35 try:
36 return filter(lambda d: d.name == name, self.values())[0]
37 except IndexError, err:
38 return None
40 class XendDomain:
41 """Index of all domains. Singleton.
42 """
44 """Dict of domain info indexed by domain id."""
45 domains = None
47 def __init__(self):
48 # Hack alert. Python does not support mutual imports, but XendDomainInfo
49 # needs access to the XendDomain instance to look up domains. Attempting
50 # to import XendDomain from XendDomainInfo causes unbounded recursion.
51 # So we stuff the XendDomain instance (self) into xroot's components.
52 xroot.add_component("xen.xend.XendDomain", self)
53 self.domains = XendDomainDict()
54 self.dbmap = DBMap(db=XenNode("/domain"))
55 eserver.subscribe('xend.virq', self.onVirq)
56 self.initial_refresh()
58 def list(self):
59 """Get list of domain objects.
61 @return: domain objects
62 """
63 return self.domains.values()
65 def onVirq(self, event, val):
66 """Event handler for virq.
67 """
68 self.refresh(cleanup=True)
70 def xen_domains(self):
71 """Get table of domains indexed by id from xc.
72 """
73 domlist = xc.domain_getinfo()
74 doms = {}
75 for d in domlist:
76 domid = d['dom']
77 doms[domid] = d
78 return doms
80 def xen_domain(self, dom):
81 """Get info about a single domain from xc.
82 Returns None if not found.
84 @param dom domain id (int)
85 """
86 dominfo = xc.domain_getinfo(dom, 1)
87 if dominfo == [] or dominfo[0]['dom'] != dom:
88 dominfo = None
89 else:
90 dominfo = dominfo[0]
91 return dominfo
93 def initial_refresh(self):
94 """Refresh initial domain info from db.
95 """
96 doms = self.xen_domains()
97 self.dbmap.readDB()
98 for domdb in self.dbmap.values():
99 try:
100 domid = int(domdb.id)
101 except:
102 domid = None
103 # XXX if domid in self.domains, then something went wrong
104 if (domid is None) or (domid in self.domains):
105 domdb.delete()
106 elif domid in doms:
107 try:
108 self._new_domain(domdb, doms[domid])
109 except Exception, ex:
110 log.exception("Error recreating domain info: id=%d", domid)
111 self._delete_domain(domid)
112 else:
113 self._delete_domain(domid)
114 self.refresh(cleanup=True)
116 def close(self):
117 pass
119 def _new_domain(self, db, info):
120 """Create a domain entry from saved info.
122 @param db: saved info from the db
123 @param info: domain info from xen
124 @return: domain
125 """
126 dominfo = XendDomainInfo.recreate(db, info)
127 self.domains[dominfo.id] = dominfo
128 return dominfo
130 def _add_domain(self, info, notify=True):
131 """Add a domain entry to the tables.
133 @param info: domain info object
134 @param notify: send a domain created event if true
135 """
136 # Remove entries under the wrong id.
137 for i, d in self.domains.items():
138 if i != d.id:
139 del self.domains[i]
140 self.dbmap.delete(d.uuid)
141 if info.id in self.domains:
142 notify = False
143 self.domains[info.id] = info
144 info.exportToDB(save=True)
145 if notify:
146 eserver.inject('xend.domain.create', [info.name, info.id])
148 def _delete_domain(self, id, notify=True):
149 """Remove a domain from the tables.
151 @param id: domain id
152 @param notify: send a domain died event if true
153 """
154 try:
155 if self.xen_domain(id):
156 return
157 except:
158 pass
159 info = self.domains.get(id)
160 if info:
161 del self.domains[id]
162 info.cleanup()
163 info.delete()
164 if notify:
165 eserver.inject('xend.domain.died', [info.name, info.id])
166 # XXX this should not be needed
167 for domdb in self.dbmap.values():
168 try:
169 domid = int(domdb.id)
170 except:
171 domid = None
172 if (domid is None) or (domid == id):
173 domdb.delete()
175 def reap(self):
176 """Look for domains that have crashed or stopped.
177 Tidy them up.
178 """
179 casualties = []
180 doms = self.xen_domains()
181 for d in doms.values():
182 dead = 0
183 dead = dead or (d['crashed'] or d['shutdown'])
184 dead = dead or (d['dying'] and
185 not(d['running'] or d['paused'] or d['blocked']))
186 if dead:
187 casualties.append(d)
188 for d in casualties:
189 id = d['dom']
190 dominfo = self.domains.get(id)
191 name = (dominfo and dominfo.name) or '??'
192 if dominfo and dominfo.is_terminated():
193 continue
194 log.debug('XendDomain>reap> domain died name=%s id=%d', name, id)
195 if d['shutdown']:
196 reason = shutdown_reason(d['shutdown_reason'])
197 log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason)
198 if reason in ['suspend']:
199 if dominfo and dominfo.is_terminated():
200 log.debug('XendDomain>reap> Suspended domain died id=%d', id)
201 else:
202 eserver.inject('xend.domain.suspended', [name, id])
203 if dominfo:
204 dominfo.state_set("suspended")
205 continue
206 if reason in ['poweroff', 'reboot']:
207 eserver.inject('xend.domain.exit', [name, id, reason])
208 self.domain_restart_schedule(id, reason)
209 else:
210 if xroot.get_enable_dump():
211 self.domain_dumpcore(id)
212 eserver.inject('xend.domain.exit', [name, id, 'crash'])
213 self.final_domain_destroy(id)
215 def refresh(self, cleanup=False):
216 """Refresh domain list from Xen.
217 """
218 if cleanup:
219 self.reap()
220 doms = self.xen_domains()
221 # Add entries for any domains we don't know about.
222 for id in doms.keys():
223 if id not in self.domains:
224 self.domain_lookup(id)
225 # Remove entries for domains that no longer exist.
226 # Update entries for existing domains.
227 do_domain_restarts = False
228 for d in self.domains.values():
229 info = doms.get(d.id)
230 if info:
231 d.update(info)
232 elif d.restart_pending():
233 do_domain_restarts = True
234 else:
235 self._delete_domain(d.id)
236 if cleanup and do_domain_restarts:
237 scheduler.now(self.domain_restarts)
239 def update_domain(self, id):
240 """Update information for a single domain.
242 @param id: domain id
243 """
244 dominfo = self.xen_domain(id)
245 if dominfo:
246 d = self.domains.get(id)
247 if d:
248 d.update(dominfo)
249 else:
250 self._delete_domain(id)
252 def domain_ls(self):
253 """Get list of domain names.
255 @return: domain names
256 """
257 self.refresh()
258 doms = self.domains.values()
259 doms.sort(lambda x, y: cmp(x.name, y.name))
260 return map(lambda x: x.name, doms)
262 def domain_ls_ids(self):
263 """Get list of domain ids.
265 @return: domain names
266 """
267 self.refresh()
268 return self.domains.keys()
270 def domain_create(self, config):
271 """Create a domain from a configuration.
273 @param config: configuration
274 @return: domain
275 """
276 dominfo = XendDomainInfo.create(self.dbmap, config)
277 return dominfo
279 def domain_restart(self, dominfo):
280 """Restart a domain.
282 @param dominfo: domain object
283 """
284 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
285 eserver.inject("xend.domain.restart",
286 [dominfo.name, dominfo.id, "begin"])
287 try:
288 dominfo.restart()
289 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
290 eserver.inject("xend.domain.restart",
291 [dominfo.name, dominfo.id, "success"])
292 self.domain_unpause(dominfo.id)
293 except Exception, ex:
294 log.exception("Exception restarting domain: name=%s id=%s",
295 dominfo.name, dominfo.id)
296 eserver.inject("xend.domain.restart",
297 [dominfo.name, dominfo.id, "fail"])
298 return dominfo
300 def domain_configure(self, vmconfig):
301 """Configure an existing domain. This is intended for internal
302 use by domain restore and migrate.
304 @param vmconfig: vm configuration
305 """
306 config = sxp.child_value(vmconfig, 'config')
307 uuid = sxp.child_value(vmconfig, 'uuid')
308 dominfo = XendDomainInfo.restore(self.dbmap, config, uuid=uuid)
309 return dominfo
311 def domain_restore(self, src, progress=False):
312 """Restore a domain from file.
314 @param src: source file
315 @param progress: output progress if true
316 """
318 try:
319 fd = os.open(src, os.O_RDONLY)
320 return XendCheckpoint.restore(self, fd)
321 except OSError, ex:
322 raise XendError("can't read guest state file %s: %s" %
323 (src, ex[1]))
325 def domain_get(self, id):
326 """Get up-to-date info about a domain.
328 @param id: domain id
329 @return: domain object (or None)
330 """
331 self.update_domain(id)
332 return self.domains.get(id)
334 def domain_lookup(self, id):
335 dominfo = self.domains.get(id)
336 if not dominfo:
337 try:
338 info = self.xen_domain(id)
339 if info:
340 uuid = getUuid()
341 log.info(
342 "Creating entry for unknown domain: id=%d uuid=%s",
343 id, uuid)
344 db = self.dbmap.addChild(uuid)
345 dominfo = XendDomainInfo.recreate(db, info)
346 self._add_domain(dominfo)
347 except Exception, ex:
348 log.exception("Error creating domain info: id=%d", id)
349 return dominfo
351 def domain_lookup_by_name(self, name):
352 dominfo = self.domains.get_by_name(name)
353 if not dominfo:
354 try:
355 id = int(name)
356 dominfo = self.domain_lookup(id)
357 except ValueError:
358 pass
359 return dominfo
361 def domain_unpause(self, id):
362 """Unpause domain execution.
364 @param id: domain id
365 """
366 dominfo = self.domain_lookup(id)
367 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
368 try:
369 return xc.domain_unpause(dom=dominfo.id)
370 except Exception, ex:
371 raise XendError(str(ex))
373 def domain_pause(self, id):
374 """Pause domain execution.
376 @param id: domain id
377 """
378 dominfo = self.domain_lookup(id)
379 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
380 try:
381 return xc.domain_pause(dom=dominfo.id)
382 except Exception, ex:
383 raise XendError(str(ex))
385 def domain_shutdown(self, id, reason='poweroff', key=0):
386 """Shutdown domain (nicely).
387 - poweroff: restart according to exit code and restart mode
388 - reboot: restart on exit
389 - halt: do not restart
391 Returns immediately.
393 @param id: domain id
394 @param reason: shutdown type: poweroff, reboot, suspend, halt
395 """
396 dominfo = self.domain_lookup(id)
397 self.domain_restart_schedule(dominfo.id, reason, force=True)
398 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
399 if reason == 'halt':
400 reason = 'poweroff'
401 val = dominfo.shutdown(reason, key=key)
402 if not reason in ['suspend', 'sysrq']:
403 self.domain_shutdowns()
404 return val
406 def domain_shutdowns(self):
407 """Process pending domain shutdowns.
408 Destroys domains whose shutdowns have timed out.
409 """
410 timeout = SHUTDOWN_TIMEOUT + 1
411 for dominfo in self.domains.values():
412 if not dominfo.shutdown_pending:
413 # domain doesn't need shutdown
414 continue
415 id = dominfo.id
416 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
417 if left <= 0:
418 # Shutdown expired - destroy domain.
419 try:
420 log.info("Domain shutdown timeout expired: name=%s id=%s",
421 dominfo.name, id)
422 self.domain_destroy(id, reason=
423 dominfo.shutdown_pending['reason'])
424 except Exception:
425 pass
426 else:
427 # Shutdown still pending.
428 timeout = min(timeout, left)
429 if timeout <= SHUTDOWN_TIMEOUT:
430 # Pending shutdowns remain - reschedule.
431 scheduler.later(timeout, self.domain_shutdowns)
433 def domain_restart_schedule(self, id, reason, force=False):
434 """Schedule a restart for a domain if it needs one.
436 @param id: domain id
437 @param reason: shutdown reason
438 """
439 log.debug('domain_restart_schedule> %d %s %d', id, reason, force)
440 dominfo = self.domain_lookup(id)
441 if not dominfo:
442 return
443 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
444 if restart:
445 log.info('Scheduling restart for domain: name=%s id=%s',
446 dominfo.name, dominfo.id)
447 eserver.inject("xend.domain.restart",
448 [dominfo.name, dominfo.id, "schedule"])
449 dominfo.restarting()
450 else:
451 log.info('Cancelling restart for domain: name=%s id=%s',
452 dominfo.name, dominfo.id)
453 eserver.inject("xend.domain.restart",
454 [dominfo.name, dominfo.id, "cancel"])
455 dominfo.restart_cancel()
457 def domain_restarts(self):
458 """Execute any scheduled domain restarts for domains that have gone.
459 """
460 doms = self.xen_domains()
461 for dominfo in self.domains.values():
462 if not dominfo.restart_pending():
463 continue
464 print 'domain_restarts>', dominfo.name, dominfo.id
465 info = doms.get(dominfo.id)
466 if info:
467 # Don't execute restart for domains still running.
468 print 'domain_restarts> still runnning: ', dominfo.name
469 continue
470 # Remove it from the restarts.
471 print 'domain_restarts> restarting: ', dominfo.name
472 self.domain_restart(dominfo)
474 def final_domain_destroy(self, id):
475 """Final destruction of a domain..
477 @param id: domain id
478 """
479 try:
480 dominfo = self.domain_lookup(id)
481 log.info('Destroying domain: name=%s', dominfo.name)
482 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
483 val = dominfo.destroy()
484 except:
485 #todo
486 try:
487 val = xc.domain_destroy(dom=id)
488 except Exception, ex:
489 raise XendError(str(ex))
490 return val
492 def domain_destroy(self, id, reason='halt'):
493 """Terminate domain immediately.
494 - halt: cancel any restart for the domain
495 - reboot schedule a restart for the domain
497 @param id: domain id
498 """
499 self.domain_restart_schedule(id, reason, force=True)
500 val = self.final_domain_destroy(id)
501 return val
503 def domain_migrate(self, id, dst, live=False, resource=0):
504 """Start domain migration.
506 @param id: domain id
507 """
508 # Need a cancel too?
509 # Don't forget to cancel restart for it.
510 dominfo = self.domain_lookup(id)
512 port = xroot.get_xend_relocation_port()
513 sock = relocate.setupRelocation(dst, port)
515 # temporarily rename domain for localhost migration
516 if dst == "localhost":
517 dominfo.name = "tmp-" + dominfo.name
519 try:
520 XendCheckpoint.save(self, sock.fileno(), dominfo)
521 except:
522 if dst == "localhost":
523 dominfo.name = string.replace(dominfo.name, "tmp-", "", 1)
524 raise
526 return None
528 def domain_save(self, id, dst, progress=False):
529 """Start saving a domain to file.
531 @param id: domain id
532 @param dst: destination file
533 @param progress: output progress if true
534 """
536 try:
537 dominfo = self.domain_lookup(id)
539 fd = os.open(dst, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
541 return XendCheckpoint.save(self, fd, dominfo)
543 except OSError, ex:
544 raise XendError("can't write guest state file %s: %s" %
545 (dst, ex[1]))
547 def domain_pincpu(self, id, vcpu, cpumap):
548 """Set which cpus vcpu can use
550 @param id: domain
551 @param vcpu: vcpu number
552 @param cpumap: bitmap of usbale cpus
553 """
554 dominfo = self.domain_lookup(id)
555 try:
556 return xc.domain_pincpu(dominfo.id, vcpu, cpumap)
557 except Exception, ex:
558 raise XendError(str(ex))
560 def domain_cpu_bvt_set(self, id, mcuadv, warpback, warpvalue, warpl, warpu):
561 """Set BVT (Borrowed Virtual Time) scheduler parameters for a domain.
562 """
563 dominfo = self.domain_lookup(id)
564 try:
565 return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv,
566 warpback=warpback, warpvalue=warpvalue,
567 warpl=warpl, warpu=warpu)
568 except Exception, ex:
569 raise XendError(str(ex))
571 def domain_cpu_bvt_get(self, id):
572 """Get BVT (Borrowed Virtual Time) scheduler parameters for a domain.
573 """
574 dominfo = self.domain_lookup(id)
575 try:
576 return xc.bvtsched_domain_get(dominfo.id)
577 except Exception, ex:
578 raise XendError(str(ex))
581 def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight):
582 """Set Simple EDF scheduler parameters for a domain.
583 """
584 dominfo = self.domain_lookup(id)
585 try:
586 return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight)
587 except Exception, ex:
588 raise XendError(str(ex))
590 def domain_cpu_sedf_get(self, id):
591 """Get Simple EDF scheduler parameters for a domain.
592 """
593 dominfo = self.domain_lookup(id)
594 try:
595 return xc.sedf_domain_get(dominfo.id)
596 except Exception, ex:
597 raise XendError(str(ex))
599 def domain_device_create(self, id, devconfig):
600 """Create a new device for a domain.
602 @param id: domain id
603 @param devconfig: device configuration
604 """
605 dominfo = self.domain_lookup(id)
606 val = dominfo.device_create(devconfig)
607 dominfo.exportToDB()
608 return val
610 def domain_device_configure(self, id, devconfig, devid):
611 """Configure an existing device for a domain.
613 @param id: domain id
614 @param devconfig: device configuration
615 @param devid: device id
616 @return: updated device configuration
617 """
618 dominfo = self.domain_lookup(id)
619 val = dominfo.device_configure(devconfig, devid)
620 dominfo.exportToDB()
621 return val
623 def domain_device_refresh(self, id, type, devid):
624 """Refresh a device.
626 @param id: domain id
627 @param devid: device id
628 @param type: device type
629 """
630 dominfo = self.domain_lookup(id)
631 val = dominfo.device_refresh(type, devid)
632 dominfo.exportToDB()
633 return val
635 def domain_device_destroy(self, id, type, devid):
636 """Destroy a device.
638 @param id: domain id
639 @param devid: device id
640 @param type: device type
641 """
642 dominfo = self.domain_lookup(id)
643 val = dominfo.device_destroy(type, devid)
644 dominfo.exportToDB()
645 return val
647 def domain_devtype_ls(self, id, type):
648 """Get list of device sxprs for a domain.
650 @param id: domain
651 @param type: device type
652 @return: device sxprs
653 """
654 dominfo = self.domain_lookup(id)
655 return dominfo.getDeviceSxprs(type)
657 def domain_devtype_get(self, id, type, devid):
658 """Get a device from a domain.
660 @param id: domain
661 @param type: device type
662 @param devid: device id
663 @return: device object (or None)
664 """
665 dominfo = self.domain_lookup(id)
666 return dominfo.getDevice(type, devid)
668 def domain_vif_limit_set(self, id, vif, credit, period):
669 """Limit the vif's transmission rate
670 """
671 dominfo = self.domain_lookup(id)
672 dev = dominfo.getDevice('vif', vif)
673 if not dev:
674 raise XendError("invalid vif")
675 return dev.setCreditLimit(credit, period)
677 def domain_shadow_control(self, id, op):
678 """Shadow page control.
680 @param id: domain
681 @param op: operation
682 """
683 dominfo = self.domain_lookup(id)
684 try:
685 return xc.shadow_control(dominfo.id, op)
686 except Exception, ex:
687 raise XendError(str(ex))
689 def domain_maxmem_set(self, id, mem):
690 """Set the memory limit for a domain.
692 @param id: domain
693 @param mem: memory limit (in MB)
694 @return: 0 on success, -1 on error
695 """
696 dominfo = self.domain_lookup(id)
697 maxmem = int(mem) * 1024
698 try:
699 return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem)
700 except Exception, ex:
701 raise XendError(str(ex))
703 def domain_mem_target_set(self, id, mem):
704 """Set the memory target for a domain.
706 @param id: domain
707 @param mem: memory target (in MB)
708 @return: 0 on success, -1 on error
709 """
710 dominfo = self.domain_lookup(id)
711 return dominfo.mem_target_set(mem)
713 def domain_dumpcore(self, id):
714 """Save a core dump for a crashed domain.
716 @param id: domain
717 """
718 dominfo = self.domain_lookup(id)
719 corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id)
720 try:
721 xc.domain_dumpcore(dom=dominfo.id, corefile=corefile)
722 except Exception, ex:
723 log.warning("Dumpcore failed, id=%s name=%s: %s",
724 dominfo.id, dominfo.name, ex)
726 def instance():
727 """Singleton constructor. Use this instead of the class constructor.
728 """
729 global inst
730 try:
731 inst
732 except:
733 inst = XendDomain()
734 return inst