ia64/xen-unstable

view tools/python/xen/xend/XendDomain.py @ 5090:eb32745a29e8

bitkeeper revision 1.1512 (4291e819kp5stS-T-KsgGXWV2Tvfow)

XendDomain.py:
Get sizeof right.
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
author cl349@firebug.cl.cam.ac.uk
date Mon May 23 14:26:33 2005 +0000 (2005-05-23)
parents b04f8c2a95d6
children 187e3a02e0d5 18001959ba29
line source
1 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
3 """Handler for domain operations.
4 Nothing here is persistent (across reboots).
5 Needs to be persistent for one uptime.
6 """
7 import sys
8 import traceback
9 import time
11 import xen.lowlevel.xc; xc = xen.lowlevel.xc.new()
13 import sxp
14 import XendRoot; xroot = XendRoot.instance()
15 import XendDB
16 import XendDomainInfo
17 import XendMigrate
18 import EventServer; eserver = EventServer.instance()
19 from XendError import XendError
20 from XendLogging import log
22 import scheduler
24 from xen.xend.server import channel
27 import errno
28 from struct import pack, unpack, calcsize
30 __all__ = [ "XendDomain" ]
32 SHUTDOWN_TIMEOUT = 30
34 class XendDomain:
35 """Index of all domains. Singleton.
36 """
38 """Path to domain database."""
39 dbpath = "domain"
41 class XendDomainDict(dict):
42 def get_by_name(self, name):
43 try:
44 return filter(lambda d: d.name == name, self.values())[0]
45 except IndexError, err:
46 return None
48 """Dict of domain info indexed by domain id."""
49 domains = XendDomainDict()
51 def __init__(self):
52 # Hack alert. Python does not support mutual imports, but XendDomainInfo
53 # needs access to the XendDomain instance to look up domains. Attempting
54 # to import XendDomain from XendDomainInfo causes unbounded recursion.
55 # So we stuff the XendDomain instance (self) into xroot's components.
56 xroot.add_component("xen.xend.XendDomain", self)
57 # Table of domain info indexed by domain id.
58 self.db = XendDB.XendDB(self.dbpath)
59 eserver.subscribe('xend.virq', self.onVirq)
60 self.initial_refresh()
62 def list(self):
63 """Get list of domain objects.
65 @return: domain objects
66 """
67 return self.domains.values()
69 def onVirq(self, event, val):
70 """Event handler for virq.
71 """
72 print 'onVirq>', val
73 self.refresh(cleanup=True)
75 def xen_domains(self):
76 """Get table of domains indexed by id from xc.
77 """
78 domlist = xc.domain_getinfo()
79 doms = {}
80 for d in domlist:
81 domid = str(d['dom'])
82 doms[domid] = d
83 return doms
85 def xen_domain(self, dom):
86 """Get info about a single domain from xc.
87 Returns None if not found.
88 """
89 dom = int(dom)
90 dominfo = xc.domain_getinfo(dom, 1)
91 if dominfo == [] or dominfo[0]['dom'] != dom:
92 dominfo = None
93 else:
94 dominfo = dominfo[0]
95 return dominfo
97 def initial_refresh(self):
98 """Refresh initial domain info from db.
99 """
100 doms = self.xen_domains()
101 for config in self.db.fetchall("").values():
102 domid = str(sxp.child_value(config, 'id'))
103 if domid in doms:
104 try:
105 self._new_domain(config, doms[domid])
106 self.update_domain(domid)
107 except Exception, ex:
108 log.exception("Error recreating domain info: id=%s", domid)
109 self._delete_domain(domid)
110 else:
111 self._delete_domain(domid)
112 self.refresh(cleanup=True)
114 def sync_domain(self, info):
115 """Sync info for a domain to disk.
117 info domain info
118 """
119 self.db.save(info.id, info.sxpr())
121 def close(self):
122 pass
124 def _new_domain(self, savedinfo, info):
125 """Create a domain entry from saved info.
127 @param savedinfo: saved info from the db
128 @param info: domain info from xen
129 @return: domain
130 """
131 dominfo = XendDomainInfo.vm_recreate(savedinfo, info)
132 self.domains[dominfo.id] = dominfo
133 return dominfo
135 def _add_domain(self, info, notify=True):
136 """Add a domain entry to the tables.
138 @param info: domain info object
139 @param notify: send a domain created event if true
140 """
141 # Remove entries under the wrong id.
142 for i, d in self.domains.items():
143 if i != d.id:
144 del self.domains[i]
145 self.db.delete(i)
146 if info.id in self.domains:
147 notify = False
148 self.domains[info.id] = info
149 self.sync_domain(info)
150 if notify:
151 eserver.inject('xend.domain.create', [info.name, info.id])
153 def _delete_domain(self, id, notify=True):
154 """Remove a domain from the tables.
156 @param id: domain id
157 @param notify: send a domain died event if true
158 """
159 info = self.domains.get(id)
160 if info:
161 del self.domains[id]
162 if notify:
163 eserver.inject('xend.domain.died', [info.name, info.id])
164 self.db.delete(id)
166 def reap(self):
167 """Look for domains that have crashed or stopped.
168 Tidy them up.
169 """
170 casualties = []
171 doms = self.xen_domains()
172 for d in doms.values():
173 dead = 0
174 dead = dead or (d['crashed'] or d['shutdown'])
175 dead = dead or (d['dying'] and
176 not(d['running'] or d['paused'] or d['blocked']))
177 if dead:
178 casualties.append(d)
179 destroyed = 0
180 for d in casualties:
181 id = str(d['dom'])
182 #print 'reap>', id
183 dominfo = self.domains.get(id)
184 name = (dominfo and dominfo.name) or '??'
185 if dominfo and dominfo.is_terminated():
186 #print 'reap> already terminated:', id
187 continue
188 log.debug('XendDomain>reap> domain died name=%s id=%s', name, id)
189 if d['shutdown']:
190 reason = XendDomainInfo.shutdown_reason(d['shutdown_reason'])
191 log.debug('XendDomain>reap> shutdown name=%s id=%s reason=%s', name, id, reason)
192 if reason in ['suspend']:
193 if dominfo and dominfo.is_terminated():
194 log.debug('XendDomain>reap> Suspended domain died id=%s', id)
195 else:
196 eserver.inject('xend.domain.suspended', [name, id])
197 continue
198 if reason in ['poweroff', 'reboot']:
199 eserver.inject('xend.domain.exit', [name, id, reason])
200 self.domain_restart_schedule(id, reason)
201 else:
202 if xroot.get_enable_dump() == 'true':
203 xc.domain_dumpcore(dom = int(id), corefile = "/var/xen/dump/%s.%s.core"%(name,id))
204 eserver.inject('xend.domain.exit', [name, id, 'crash'])
205 destroyed += 1
206 self.final_domain_destroy(id)
208 def refresh(self, cleanup=False):
209 """Refresh domain list from Xen.
210 """
211 if cleanup:
212 self.reap()
213 doms = self.xen_domains()
214 # Add entries for any domains we don't know about.
215 for (id, d) in doms.items():
216 if id not in self.domains:
217 self.domain_lookup(id)
218 # Remove entries for domains that no longer exist.
219 # Update entries for existing domains.
220 do_domain_restarts = False
221 for d in self.domains.values():
222 info = doms.get(d.id)
223 if info:
224 d.update(info)
225 elif d.restart_pending():
226 do_domain_restarts = True
227 else:
228 self._delete_domain(d.id)
229 if cleanup and do_domain_restarts:
230 scheduler.now(self.domain_restarts)
232 def update_domain(self, id):
233 """Update the saved info for a domain.
235 @param id: domain id
236 """
237 dominfo = self.domains.get(id)
238 if dominfo:
239 self.sync_domain(dominfo)
241 def refresh_domain(self, id):
242 """Refresh information for a single domain.
244 @param id: domain id
245 """
246 dominfo = self.xen_domain(id)
247 if dominfo:
248 d = self.domains.get(id)
249 if d:
250 d.update(dominfo)
251 else:
252 self._delete_domain(id)
254 def domain_ls(self):
255 """Get list of domain names.
257 @return: domain names
258 """
259 self.refresh()
260 doms = self.domains.values()
261 doms.sort(lambda x, y: cmp(x.name, y.name))
262 return map(lambda x: x.name, doms)
264 def domain_ls_ids(self):
265 """Get list of domain ids.
267 @return: domain names
268 """
269 self.refresh()
270 return self.domains.keys()
272 def domain_create(self, config):
273 """Create a domain from a configuration.
275 @param config: configuration
276 @return: domain
277 """
278 dominfo = XendDomainInfo.vm_create(config)
279 self._add_domain(dominfo)
280 return dominfo
282 def domain_restart(self, dominfo):
283 """Restart a domain.
285 @param dominfo: domain object
286 """
287 log.info("Restarting domain: name=%s id=%s", dominfo.name, dominfo.id)
288 eserver.inject("xend.domain.restart",
289 [dominfo.name, dominfo.id, "begin"])
290 try:
291 dominfo.restart()
292 self._add_domain(dominfo)
293 log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id)
294 eserver.inject("xend.domain.restart",
295 [dominfo.name, dominfo.id, "success"])
296 self.domain_unpause(dominfo.id)
297 except Exception, ex:
298 log.exception("Exception restarting domain: name=%s id=%s",
299 dominfo.name, dominfo.id)
300 eserver.inject("xend.domain.restart",
301 [dominfo.name, dominfo.id, "fail"])
302 return dominfo
304 def domain_configure(self, vmconfig):
305 """Configure an existing domain. This is intended for internal
306 use by domain restore and migrate.
308 @param id: domain id
309 @param vmconfig: vm configuration
310 """
311 config = sxp.child_value(vmconfig, 'config')
312 dominfo = XendDomainInfo.tmp_restore_create_domain()
313 dominfo.dom_construct(dominfo.dom, config)
314 self._add_domain(dominfo)
315 return dominfo
317 def domain_restore(self, src, progress=False):
318 """Restore a domain from file.
320 @param src: source file
321 @param progress: output progress if true
322 """
324 SIGNATURE = "LinuxGuestRecord"
325 sizeof_int = calcsize("i")
326 sizeof_unsigned_long = calcsize("L")
327 PAGE_SIZE = 4096
329 class XendFile(file):
330 def read_exact(self, size, error_msg):
331 buf = self.read(size)
332 if len(buf) != size:
333 raise XendError(error_msg)
334 return buf
336 try:
337 fd = XendFile(src, 'rb')
339 signature = fd.read_exact(len(SIGNATURE),
340 "not a valid guest state file: signature read")
341 if signature != SIGNATURE:
342 raise XendError("not a valid guest state file: found '%s'" %
343 signature)
345 l = fd.read_exact(sizeof_int,
346 "not a valid guest state file: config size read")
347 vmconfig_size = unpack("i", l)[0] # XXX endianess
348 vmconfig_buf = fd.read_exact(vmconfig_size,
349 "not a valid guest state file: config read")
351 p = sxp.Parser()
352 p.input(vmconfig_buf)
353 if not p.ready:
354 raise XendError("not a valid guest state file: config parse")
356 vmconfig = p.get_val()
357 dominfo = self.domain_configure(vmconfig)
359 l = fd.read_exact(sizeof_unsigned_long,
360 "not a valid guest state file: pfn count read")
361 nr_pfns = unpack("=L", l)[0] # XXX endianess
362 if nr_pfns > 1024*1024: # XXX
363 raise XendError(
364 "not a valid guest state file: pfn count out of range")
366 # XXXcl hack: fd.tell will sync up the object and
367 # underlying file descriptor
368 ignore = fd.tell()
370 xc.linux_restore(fd.fileno(), int(dominfo.id), nr_pfns)
371 return dominfo
373 except IOError, ex:
374 if ex.errno == errno.ENOENT:
375 raise XendError("can't open guest state file %s" % src)
376 else:
377 raise
379 def domain_get(self, id):
380 """Get up-to-date info about a domain.
382 @param id: domain id
383 @return: domain object (or None)
384 """
385 id = str(id)
386 self.refresh_domain(id)
387 return self.domains.get(id)
389 def domain_lookup(self, name):
390 name = str(name)
391 dominfo = self.domains.get_by_name(name) or self.domains.get(name)
392 if dominfo:
393 return dominfo
394 try:
395 d = self.xen_domain(name)
396 if d:
397 log.info("Creating entry for unknown domain: id=%s", name)
398 dominfo = XendDomainInfo.vm_recreate(None, d)
399 self._add_domain(dominfo)
400 return dominfo
401 except Exception, ex:
402 log.exception("Error creating domain info: id=%s", name)
404 def domain_exists(self, name):
405 return self.domain_lookup(name) != None
407 def domain_unpause(self, id):
408 """Unpause domain execution.
410 @param id: domain id
411 """
412 dominfo = self.domain_lookup(id)
413 eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id])
414 try:
415 return xc.domain_unpause(dom=dominfo.dom)
416 except Exception, ex:
417 raise XendError(str(ex))
419 def domain_pause(self, id):
420 """Pause domain execution.
422 @param id: domain id
423 """
424 dominfo = self.domain_lookup(id)
425 eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id])
426 try:
427 return xc.domain_pause(dom=dominfo.dom)
428 except Exception, ex:
429 raise XendError(str(ex))
431 def domain_shutdown(self, id, reason='poweroff', key=0):
432 """Shutdown domain (nicely).
433 - poweroff: restart according to exit code and restart mode
434 - reboot: restart on exit
435 - halt: do not restart
437 Returns immediately.
439 @param id: domain id
440 @param reason: shutdown type: poweroff, reboot, suspend, halt
441 """
442 dominfo = self.domain_lookup(id)
443 self.domain_restart_schedule(dominfo.id, reason, force=True)
444 eserver.inject('xend.domain.shutdown', [dominfo.name, dominfo.id, reason])
445 if reason == 'halt':
446 reason = 'poweroff'
447 val = dominfo.shutdown(reason, key=key)
448 if reason != 'sysrq':
449 self.domain_shutdowns()
450 return val
452 def domain_shutdowns(self):
453 """Process pending domain shutdowns.
454 Destroys domains whose shutdowns have timed out.
455 """
456 timeout = SHUTDOWN_TIMEOUT + 1
457 for dominfo in self.domains.values():
458 if not dominfo.shutdown_pending:
459 # domain doesn't need shutdown
460 continue
461 id = dominfo.id
462 left = dominfo.shutdown_time_left(SHUTDOWN_TIMEOUT)
463 if left <= 0:
464 # Shutdown expired - destroy domain.
465 try:
466 log.info("Domain shutdown timeout expired: name=%s id=%s",
467 dominfo.name, id)
468 self.domain_destroy(id, reason=
469 dominfo.shutdown_pending['reason'])
470 except Exception:
471 pass
472 else:
473 # Shutdown still pending.
474 print 'domain_shutdowns> pending: ', id
475 timeout = min(timeout, left)
476 if timeout <= SHUTDOWN_TIMEOUT:
477 # Pending shutdowns remain - reschedule.
478 scheduler.later(timeout, self.domain_shutdowns)
480 def domain_restart_schedule(self, id, reason, force=False):
481 """Schedule a restart for a domain if it needs one.
483 @param id: domain id
484 @param reason: shutdown reason
485 """
486 log.debug('domain_restart_schedule> %s %s %d', id, reason, force)
487 dominfo = self.domain_lookup(id)
488 if not dominfo:
489 return
490 restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
491 if restart:
492 log.info('Scheduling restart for domain: name=%s id=%s',
493 dominfo.name, dominfo.id)
494 eserver.inject("xend.domain.restart",
495 [dominfo.name, dominfo.id, "schedule"])
496 dominfo.restarting()
497 else:
498 log.info('Cancelling restart for domain: name=%s id=%s',
499 dominfo.name, dominfo.id)
500 eserver.inject("xend.domain.restart",
501 [dominfo.name, dominfo.id, "cancel"])
502 dominfo.restart_cancel()
504 def domain_restarts(self):
505 """Execute any scheduled domain restarts for domains that have gone.
506 """
507 doms = self.xen_domains()
508 for dominfo in self.domains.values():
509 if not dominfo.restart_pending():
510 continue
511 print 'domain_restarts>', dominfo.name, dominfo.id
512 info = doms.get(dominfo.id)
513 if info:
514 # Don't execute restart for domains still running.
515 print 'domain_restarts> still runnning: ', dominfo.name
516 continue
517 # Remove it from the restarts.
518 print 'domain_restarts> restarting: ', dominfo.name
519 self.domain_restart(dominfo)
521 def final_domain_destroy(self, id):
522 """Final destruction of a domain..
524 @param id: domain id
525 """
526 try:
527 dominfo = self.domain_lookup(id)
528 log.info('Destroying domain: name=%s', dominfo.name)
529 eserver.inject('xend.domain.destroy', [dominfo.name, dominfo.id])
530 val = dominfo.destroy()
531 except:
532 #todo
533 try:
534 val = xc.domain_destroy(dom=int(id))
535 except Exception, ex:
536 raise XendError(str(ex))
537 return val
539 def domain_destroy(self, id, reason='halt'):
540 """Terminate domain immediately.
541 - halt: cancel any restart for the domain
542 - reboot schedule a restart for the domain
544 @param id: domain id
545 """
546 self.domain_restart_schedule(id, reason, force=True)
547 val = self.final_domain_destroy(id)
548 return val
550 def domain_migrate(self, id, dst, live=False, resource=0):
551 """Start domain migration.
553 @param id: domain id
554 """
555 # Need a cancel too?
556 # Don't forget to cancel restart for it.
557 dominfo = self.domain_lookup(id)
558 xmigrate = XendMigrate.instance()
559 return xmigrate.migrate_begin(dominfo, dst, live=live, resource=resource)
561 def domain_save(self, id, dst, progress=False):
562 """Start saving a domain to file.
564 @param id: domain id
565 @param dst: destination file
566 @param progress: output progress if true
567 """
568 dominfo = self.domain_lookup(id)
569 xmigrate = XendMigrate.instance()
570 return xmigrate.save_begin(dominfo, dst)
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(int(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.dom, 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.dom)
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.dom, period, slice, latency, extratime, weight)
612 except Exception, ex:
613 raise XendError(str(ex))
615 def domain_cpu_sedf_get(self, id):
616 """Get Atropos scheduler parameters for a domain.
617 """
618 dominfo = self.domain_lookup(id)
619 try:
620 return xc.sedf_domain_get(dominfo.dom)
621 except Exception, ex:
622 raise XendError(str(ex))
623 def domain_device_create(self, id, devconfig):
624 """Create a new device for a domain.
626 @param id: domain id
627 @param devconfig: device configuration
628 """
629 dominfo = self.domain_lookup(id)
630 val = dominfo.device_create(devconfig)
631 self.update_domain(dominfo.id)
632 return val
634 def domain_device_configure(self, id, devconfig, idx):
635 """Configure an existing device for a domain.
637 @param id: domain id
638 @param devconfig: device configuration
639 @param idx: device index
640 @return: updated device configuration
641 """
642 dominfo = self.domain_lookup(id)
643 val = dominfo.device_configure(devconfig, idx)
644 self.update_domain(dominfo.id)
645 return val
647 def domain_device_refresh(self, id, type, idx):
648 """Refresh a device.
650 @param id: domain id
651 @param idx: device index
652 @param type: device type
653 """
654 dominfo = self.domain_lookup(id)
655 val = dominfo.device_refresh(type, idx)
656 self.update_domain(dominfo.id)
657 return val
659 def domain_device_destroy(self, id, type, idx):
660 """Destroy a device.
662 @param id: domain id
663 @param idx: device index
664 @param type: device type
665 """
666 dominfo = self.domain_lookup(id)
667 val = dominfo.device_destroy(type, idx)
668 self.update_domain(dominfo.id)
669 return val
671 def domain_devtype_ls(self, id, type):
672 """Get list of device indexes for a domain.
674 @param id: domain
675 @param type: device type
676 @return: device indexes
677 """
678 dominfo = self.domain_lookup(id)
679 return dominfo.getDeviceIndexes(type)
681 def domain_devtype_get(self, id, type, idx):
682 """Get a device from a domain.
684 @param id: domain
685 @param type: device type
686 @param idx: device index
687 @return: device object (or None)
688 """
689 dominfo = self.domain_lookup(id)
690 return dominfo.getDeviceByIndex(type, idx)
692 def domain_vif_limit_set(self, id, vif, credit, period):
693 """Limit the vif's transmission rate
694 """
695 dominfo = self.domain_lookup(id)
696 dev = dominfo.getDeviceById('vif', vif)
697 if not dev:
698 raise XendError("invalid vif")
699 return dev.setCreditLimit(credit, period)
701 def domain_shadow_control(self, id, op):
702 """Shadow page control.
704 @param id: domain
705 @param op: operation
706 """
707 dominfo = self.domain_lookup(id)
708 try:
709 return xc.shadow_control(dominfo.dom, op)
710 except Exception, ex:
711 raise XendError(str(ex))
713 def domain_maxmem_set(self, id, mem):
714 """Set the memory limit for a domain.
716 @param dom: domain
717 @param mem: memory limit (in MB)
718 @return: 0 on success, -1 on error
719 """
720 dominfo = self.domain_lookup(id)
721 maxmem = int(mem) * 1024
722 try:
723 return xc.domain_setmaxmem(dominfo.dom, maxmem_kb = maxmem)
724 except Exception, ex:
725 raise XendError(str(ex))
727 def domain_mem_target_set(self, id, target):
728 dominfo = self.domain_lookup(id)
729 return dominfo.mem_target_set(target)
733 def instance():
734 """Singleton constructor. Use this instead of the class constructor.
735 """
736 global inst
737 try:
738 inst
739 except:
740 inst = XendDomain()
741 return inst