#!/usr/bin/python #============================================================================ # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #============================================================================ # Copyright (C) 2006 XenSource Ltd. #============================================================================ import sys import time import re import os from xen.util.xmlrpclib2 import ServerProxy from optparse import * from pprint import pprint from types import DictType from getpass import getpass # Get default values from the environment SERVER_URI = os.environ.get('XAPI_SERVER_URI', 'http://localhost:9363/') SERVER_USER = os.environ.get('XAPI_SERVER_USER', '') SERVER_PASS = os.environ.get('XAPI_SERVER_PASS', '') MB = 1024 * 1024 HOST_INFO_FORMAT = '%-20s: %-50s' VM_LIST_FORMAT = '%(name_label)-18s %(memory_actual)-5s %(VCPUs_number)-5s'\ ' %(power_state)-10s %(uuid)-36s' SR_LIST_FORMAT = '%(name_label)-18s %(uuid)-36s %(physical_size)-10s' \ '%(type)-10s' VDI_LIST_FORMAT = '%(name_label)-18s %(uuid)-36s %(virtual_size)-8s' VBD_LIST_FORMAT = '%(device)-6s %(uuid)-36s %(VDI)-8s' TASK_LIST_FORMAT = '%(name_label)-18s %(uuid)-36s %(status)-8s %(progress)-4s' VIF_LIST_FORMAT = '%(name)-8s %(device)-7s %(uuid)-36s %(MAC)-10s' CONSOLE_LIST_FORMAT = '%(uuid)-36s %(protocol)-8s %(location)-32s' COMMANDS = { 'host-info': ('', 'Get Xen Host Info'), 'host-set-name': ('', 'Set host name'), 'pif-list': ('', 'List all PIFs'), 'sr-list': ('', 'List all SRs'), 'vbd-list': ('', 'List all VBDs'), 'vbd-create': (' [opts]', 'Create VBD attached to domname'), 'vdi-create': (' [opts]', 'Create a VDI'), 'vdi-list' : ('', 'List all VDI'), 'vdi-rename': (' ', 'Rename VDI'), 'vdi-destroy': ('', 'Delete VDI'), 'vif-create': (' ', 'Create VIF attached to domname'), 'vm-create': ('', 'Create VM with python config'), 'vm-destroy': ('', 'Delete VM'), 'vm-list': ('[--long]', 'List all domains.'), 'vm-name': ('', 'Name of UUID.'), 'vm-shutdown': (' [opts]', 'Shutdown VM with name'), 'vm-start': ('', 'Start VM with name'), 'vm-uuid': ('', 'UUID of a domain by name.'), 'async-vm-start': ('', 'Start VM asynchronously'), } OPTIONS = { 'sr-list': [(('-l', '--long'), {'action':'store_true', 'help':'List all properties of SR'}) ], 'vdi-list': [(('-l', '--long'), {'action':'store_true', 'help':'List all properties of VDI'}) ], 'vif-list': [(('-l', '--long'), {'action':'store_true', 'help':'List all properties of VIF'}) ], 'vm-list': [(('-l', '--long'), {'action':'store_true', 'help':'List all properties of VMs'}) ], 'vm-shutdown': [(('-f', '--force'), {'help': 'Shutdown Forcefully', 'action': 'store_true'})], 'vdi-create': [(('--name-label',), {'help': 'Name for VDI'}), (('--name-description',), {'help': 'Description for VDI'}), (('--virtual-size',), {'type': 'int', 'default': 0, 'help': 'Size of VDI in bytes'}), (('--type',), {'choices': ['system', 'user', 'ephemeral'], 'default': 'system', 'help': 'VDI type'}), (('--sharable',), {'action': 'store_true', 'help': 'VDI sharable'}), (('--read-only',), {'action': 'store_true', 'help': 'Read only'}), (('--sr',), {})], 'vbd-create': [(('--VDI',), {'help': 'UUID of VDI to attach to.'}), (('--mode',), {'choices': ['RO', 'RW'], 'help': 'device mount mode'}), (('--driver',), {'choices':['paravirtualised', 'ioemu'], 'help': 'Driver for VBD'}), (('--device',), {'help': 'Device name on guest domain'})] } class OptionError(Exception): pass class XenAPIError(Exception): pass # # Extra utility functions # class IterableValues(Values): """Better interface to the list of values from optparse.""" def __iter__(self): for opt, val in self.__dict__.items(): if opt[0] == '_' or callable(val): continue yield opt, val def parse_args(cmd_name, args, set_defaults = False): argstring, desc = COMMANDS[cmd_name] parser = OptionParser(usage = 'xapi %s %s' % (cmd_name, argstring), description = desc) if cmd_name in OPTIONS: for optargs, optkwds in OPTIONS[cmd_name]: parser.add_option(*optargs, **optkwds) if set_defaults: default_values = parser.get_default_values() defaults = IterableValues(default_values.__dict__) else: defaults = IterableValues() (opts, extraargs) = parser.parse_args(args = list(args), values = defaults) return opts, extraargs def execute(server, fn, args, async = False): if async: func = eval('server.Async.%s' % fn) else: func = eval('server.%s' % fn) result = func(*args) if type(result) != DictType: raise TypeError("Function returned object of type: %s" % str(type(result))) if 'Value' not in result: raise XenAPIError(*result['ErrorDescription']) return result['Value'] _initialised = False _server = None _session = None def connect(*args): global _server, _session, _initialised if not _initialised: # try without password or default credentials try: _server = ServerProxy(SERVER_URI) _session = execute(_server.session, 'login_with_password', (SERVER_USER, SERVER_PASS)) except: login = raw_input("Login: ") password = getpass() creds = (login, password) _server = ServerProxy(SERVER_URI) _session = execute(_server.session, 'login_with_password', creds) _initialised = True return (_server, _session) def _stringify(adict): return dict([(k, str(v)) for k, v in adict.items()]) def _read_python_cfg(filename): cfg = {} execfile(filename, {}, cfg) return cfg def resolve_vm(server, session, vm_name): vm_uuid = execute(server, 'VM.get_by_name_label', (session, vm_name)) if not vm_uuid: return None else: return vm_uuid[0] def resolve_vdi(server, session, vdi_name): vdi_uuid = execute(server, 'VDI.get_by_name_label', (session, vdi_name)) if not vdi_uuid: return None else: return vdi_uuid[0] # # Actual commands # def xapi_host_info(args, async = False): server, session = connect() hosts = execute(server, 'host.get_all', (session,)) for host in hosts: # there is only one, but .. hostinfo = execute(server, 'host.get_record', (session, host)) print HOST_INFO_FORMAT % ('Name', hostinfo['name_label']) print HOST_INFO_FORMAT % ('Version', hostinfo['software_version']) print HOST_INFO_FORMAT % ('CPUs', len(hostinfo['host_CPUs'])) print HOST_INFO_FORMAT % ('VMs', len(hostinfo['resident_VMs'])) print HOST_INFO_FORMAT % ('UUID', host) for host_cpu_uuid in hostinfo['host_CPUs']: host_cpu = execute(server, 'host_cpu.get_record', (session, host_cpu_uuid)) print 'CPU %s Util: %.2f' % (host_cpu['number'], float(host_cpu['utilisation'])) def xapi_host_set_name(args, async = False): if len(args) < 1: raise OptionError("No hostname specified") server, session = connect() hosts = execute(server, 'host.get_all', (session,)) if len(hosts) > 0: execute(server, 'host.set_name_label', (session, hosts[0], args[0])) print 'Hostname: %s' % execute(server, 'host.get_name_label', (session, hosts[0])) def xapi_vm_uuid(args, async = False): if len(args) < 1: raise OptionError("No domain name specified") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print vm_uuid def xapi_vm_name(args, async = False): if len(args) < 1: raise OptionError("No UUID specified") server, session = connect() vm_name = execute(server, 'VM.get_name_label', (session, args[0])) print vm_name def xapi_vm_list(args, async = False): opts, args = parse_args('vm-list', args, set_defaults = True) is_long = opts and opts.long list_only = args server, session = connect() vm_uuids = execute(server, 'VM.get_all', (session,)) if not is_long: print VM_LIST_FORMAT % {'name_label':'Name', 'memory_actual':'Mem', 'VCPUs_number': 'VCPUs', 'power_state': 'State', 'uuid': 'UUID'} for uuid in vm_uuids: vm_info = execute(server, 'VM.get_record', (session, uuid)) # skip domain if we don't want if list_only and vm_info['name_label'] not in list_only: continue if is_long: vbds = vm_info['VBDs'] vifs = vm_info['VIFs'] vif_infos = [] vbd_infos = [] for vbd in vbds: vbd_info = execute(server, 'VBD.get_record', (session, vbd)) vbd_infos.append(vbd_info) for vif in vifs: vif_info = execute(server, 'VIF.get_record', (session, vif)) vif_infos.append(vif_info) vm_info['VBDs'] = vbd_infos vm_info['VIFs'] = vif_infos pprint(vm_info) else: print VM_LIST_FORMAT % _stringify(vm_info) def xapi_vm_create(args, async = False): if len(args) < 1: raise OptionError("Configuration file not specified") filename = args[0] cfg = _read_python_cfg(filename) print 'Creating VM from %s ..' % filename server, session = connect() uuid = execute(server, 'VM.create', (session, cfg), async = async) print 'Done. (%s)' % uuid print uuid def xapi_vm_destroy(args, async = False): if len(args) < 1: raise OptionError("No domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Destroying VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.destroy', (session, vm_uuid), async = async) print 'Done.' def xapi_vm_start(args, async = False): if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Starting VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.start', (session, vm_uuid, False), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_vm_suspend(args, async = False): if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Suspending VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.suspend', (session, vm_uuid), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_vm_resume(args, async = False): if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Resuming VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.resume', (session, vm_uuid, False), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_vm_pause(args, async = False): if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Pausing VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.pause', (session, vm_uuid), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_vm_unpause(args, async = False): if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) print 'Pausing VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.unpause', (session, vm_uuid), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_task_list(args, async = False): server, session = connect() all_tasks = execute(server, 'task.get_all', (session,)) print TASK_LIST_FORMAT % {'name_label': 'Task Name', 'uuid': 'UUID', 'status': 'Status', 'progress': '%'} for task_uuid in all_tasks: task = execute(server, 'task.get_record', (session, task_uuid)) print TASK_LIST_FORMAT % task def xapi_task_clear(args, async = False): server, session = connect() all_tasks = execute(server, 'task.get_all', (session,)) for task_uuid in all_tasks: success = execute(server, 'task.destroy', (session, task_uuid)) print 'Destroyed Task %s' % task_uuid def xapi_vm_shutdown(args, async = False): opts, args = parse_args("vm-shutdown", args, set_defaults = True) if len(args) < 1: raise OptionError("No Domain name specified.") server, session = connect() vm_uuid = resolve_vm(server, session, args[0]) if opts.force: print 'Forcefully shutting down VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.hard_shutdown', (session, vm_uuid), async = async) else: print 'Shutting down VM %s (%s)' % (args[0], vm_uuid) success = execute(server, 'VM.clean_shutdown', (session, vm_uuid), async = async) if async: print 'Task started: %s' % success else: print 'Done.' def xapi_vbd_create(args, async = False): opts, args = parse_args('vbd-create', args) if len(args) < 2: raise OptionError("Configuration file and domain not specified") domname = args[0] if len(args) > 1: filename = args[1] cfg = _read_python_cfg(filename) else: cfg = {} for opt, val in opts: cfg[opt] = val print 'Creating VBD ...', server, session = connect() vm_uuid = resolve_vm(server, session, domname) cfg['VM'] = vm_uuid vbd_uuid = execute(server, 'VBD.create', (session, cfg), async = async) if async: print 'Task started: %s' % vbd_uuid else: print 'Done. (%s)' % vbd_uuid def xapi_vif_create(args, async = False): if len(args) < 2: raise OptionError("Configuration file not specified") domname = args[0] filename = args[1] cfg = _read_python_cfg(filename) print 'Creating VIF from %s ..' % filename server, session = connect() vm_uuid = resolve_vm(server, session, domname) cfg['VM'] = vm_uuid vif_uuid = execute(server, 'VIF.create', (session, cfg), async = async) if async: print 'Task started: %s' % vif_uuid else: print 'Done. (%s)' % vif_uuid def xapi_vbd_list(args, async = False): server, session = connect() domname = args[0] dom_uuid = resolve_vm(server, session, domname) vbds = execute(server, 'VM.get_VBDs', (session, dom_uuid)) print VBD_LIST_FORMAT % {'device': 'Device', 'uuid' : 'UUID', 'VDI': 'VDI'} for vbd in vbds: vbd_struct = execute(server, 'VBD.get_record', (session, vbd)) print VBD_LIST_FORMAT % vbd_struct def xapi_vbd_stats(args, async = False): server, session = connect() domname = args[0] dom_uuid = resolve_vm(server, session, domname) vbds = execute(server, 'VM.get_VBDs', (session, dom_uuid)) for vbd_uuid in vbds: print execute(server, 'VBD.get_io_read_kbs', (session, vbd_uuid)) def xapi_vif_list(args, async = False): server, session = connect() opts, args = parse_args('vdi-list', args, set_defaults = True) is_long = opts and opts.long domname = args[0] dom_uuid = resolve_vm(server, session, domname) vifs = execute(server, 'VM.get_VIFs', (session, dom_uuid)) if not is_long: print VIF_LIST_FORMAT % {'name': 'Name', 'device': 'Device', 'uuid' : 'UUID', 'MAC': 'MAC'} for vif in vifs: vif_struct = execute(server, 'VIF.get_record', (session, vif)) print VIF_LIST_FORMAT % vif_struct else: for vif in vifs: vif_struct = execute(server, 'VIF.get_record', (session, vif)) pprint(vif_struct) def xapi_console_list(args, async = False): server, session = connect() opts, args = parse_args('vdi-list', args, set_defaults = True) is_long = opts and opts.long domname = args[0] dom_uuid = resolve_vm(server, session, domname) consoles = execute(server, 'VM.get_consoles', (session, dom_uuid)) if not is_long: print CONSOLE_LIST_FORMAT % {'protocol': 'Protocol', 'location': 'Location', 'uuid': 'UUID'} for console in consoles: console_struct = execute(server, 'console.get_record', (session, console)) print CONSOLE_LIST_FORMAT % console_struct else: for console in consoles: console_struct = execute(server, 'console.get_record', (session, console)) pprint(console_struct) def xapi_vdi_list(args, async = False): opts, args = parse_args('vdi-list', args, set_defaults = True) is_long = opts and opts.long server, session = connect() vdis = execute(server, 'VDI.get_all', (session,)) if not is_long: print VDI_LIST_FORMAT % {'name_label': 'VDI Label', 'uuid' : 'UUID', 'virtual_size': 'Bytes'} for vdi in vdis: vdi_struct = execute(server, 'VDI.get_record', (session, vdi)) print VDI_LIST_FORMAT % vdi_struct else: for vdi in vdis: vdi_struct = execute(server, 'VDI.get_record', (session, vdi)) pprint(vdi_struct) def xapi_sr_list(args, async = False): opts, args = parse_args('sr-list', args, set_defaults = True) is_long = opts and opts.long server, session = connect() srs = execute(server, 'SR.get_all', (session,)) if not is_long: print SR_LIST_FORMAT % {'name_label': 'SR Label', 'uuid' : 'UUID', 'physical_size': 'Size (MB)', 'type': 'Type'} for sr in srs: sr_struct = execute(server, 'SR.get_record', (session, sr)) sr_struct['physical_size'] = int(sr_struct['physical_size'])/MB print SR_LIST_FORMAT % sr_struct else: for sr in srs: sr_struct = execute(server, 'SR.get_record', (session, sr)) pprint(sr_struct) def xapi_sr_rename(args, async = False): server, session = connect() sr = execute(server, 'SR.get_by_name_label', (session, args[0])) execute(server, 'SR.set_name_label', (session, sr[0], args[1])) def xapi_vdi_create(args, async = False): opts, args = parse_args('vdi-create', args) if len(args) > 0: cfg = _read_python_cfg(args[0]) else: cfg = {} for opt, val in opts: cfg[opt] = val server, session = connect() srs = [] if cfg.get('SR'): srs = execute(server, 'SR.get_by_name_label', (session, cfg['SR'])) else: srs = execute(server, 'SR.get_all', (session,)) sr = srs[0] cfg['SR'] = sr size = cfg['virtual_size']/MB print 'Creating VDI of size: %dMB ..' % size, uuid = execute(server, 'VDI.create', (session, cfg), async = async) if async: print 'Task started: %s' % uuid else: print 'Done. (%s)' % uuid def xapi_vdi_destroy(args, async = False): server, session = connect() if len(args) < 1: raise OptionError('Not enough arguments') vdi_uuid = args[0] print 'Deleting VDI %s' % vdi_uuid result = execute(server, 'VDI.destroy', (session, vdi_uuid), async = async) if async: print 'Task started: %s' % result else: print 'Done.' def xapi_vdi_rename(args, async = False): server, session = connect() if len(args) < 2: raise OptionError('Not enough arguments') vdi_uuid = execute(server, 'VDI.get_by_name_label', session, args[0]) vdi_name = args[1] print 'Renaming VDI %s to %s' % (vdi_uuid[0], vdi_name) result = execute(server, 'VDI.set_name_label', (session, vdi_uuid[0], vdi_name), async = async) if async: print 'Task started: %s' % result else: print 'Done.' def xapi_pif_list(args, async = False): server, session = connect() pif_uuids = execute(server, 'PIF.get_all', (session,)) for pif_uuid in pif_uuids: pif = execute(server, 'PIF.get_record', (session, pif_uuid)) print pif def xapi_debug_wait(args, async = False): secs = 10 if len(args) > 0: secs = int(args[0]) server, session = connect() task_uuid = execute(server, 'debug.wait', (session, secs), async=async) print 'Task UUID: %s' % task_uuid def xapi_vm_stat(args, async = False): domname = args[0] server, session = connect() vm_uuid = resolve_vm(server, session, domname) vif_uuids = execute(server, 'VM.get_VIFs', (session, vm_uuid)) vbd_uuids = execute(server, 'VM.get_VBDs', (session, vm_uuid)) vcpus_utils = execute(server, 'VM.get_VCPUs_utilisation', (session, vm_uuid)) for vcpu_num in sorted(vcpus_utils.keys()): print 'CPU %s : %5.2f%%' % (vcpu_num, vcpus_utils[vcpu_num] * 100) for vif_uuid in vif_uuids: vif = execute(server, 'VIF.get_record', (session, vif_uuid)) print '%(device)s: rx: %(io_read_kbs)10.2f tx: %(io_write_kbs)10.2f' \ % vif for vbd_uuid in vbd_uuids: vbd = execute(server, 'VBD.get_record', (session, vbd_uuid)) print '%(device)s: rd: %(io_read_kbs)10.2f wr: %(io_write_kbs)10.2f' \ % vbd # # Command Line Utils # import cmd import shlex class XenAPICmd(cmd.Cmd): def __init__(self, server, session): cmd.Cmd.__init__(self) self.server = server self.session = session self.prompt = ">>> " def default(self, line): words = shlex.split(line) if len(words) > 0: cmd_name = words[0].replace('-', '_') is_async = 'async' in cmd_name if is_async: cmd_name = re.sub('async_', '', cmd_name) func_name = 'xapi_%s' % cmd_name func = globals().get(func_name) if func: try: args = tuple(words[1:]) func(args, async = is_async) return True except SystemExit: return False except OptionError, e: print 'Error:', str(e) return False except Exception, e: import traceback traceback.print_exc() return False print '*** Unknown command: %s' % words[0] return False def do_EOF(self, line): print sys.exit(0) def do_help(self, line): usage(print_usage = False) def emptyline(self): pass def postcmd(self, stop, line): return False def precmd(self, line): words = shlex.split(line) if len(words) > 0: words0 = words[0].replace('-', '_') return ' '.join([words0] + words[1:]) else: return line def shell(): server, session = connect() x = XenAPICmd(server, session) x.cmdloop('Xen API Prompt. Type "help" for a list of functions') def usage(command = None, print_usage = True): if not command: if print_usage: print 'Usage: xapi [options] [args]' print print 'Subcommands:' print for func in sorted(globals().keys()): if func.startswith('xapi_'): command = func[5:].replace('_', '-') args, description = COMMANDS.get(command, ('', '')) print '%-16s %-40s' % (command, description) print else: parse_args(command, ['-h']) def main(args): # poor man's optparse that doesn't abort on unrecognised opts options = {} remaining = [] arg_n = 0 while args: arg = args.pop(0) if arg in ('--help', '-h'): options['help'] = True elif arg in ('--server', '-s') and args: options['server'] = args.pop(0) elif arg in ('--user', '-u') and args: options['user'] = args.pop(0) elif arg in ('--password', '-p') and args: options['password'] = args.pop(0) else: remaining.append(arg) # abort here if these conditions are true if options.get('help') and not remaining: usage() sys.exit(1) if options.get('help') and remaining: usage(remaining[0]) sys.exit(1) if not remaining: usage() sys.exit(1) if options.get('server'): # it is ugly to use a global, but it is simple global SERVER_URI SERVER_URI = options['server'] if options.get('user'): global SERVER_USER SERVER_USER = options['user'] if options.get('password'): global SERVER_PASS SERVER_PASS = options['password'] subcmd = remaining[0].replace('-', '_') is_async = 'async' in subcmd if is_async: subcmd = re.sub('async_', '', subcmd) subcmd_func_name = 'xapi_' + subcmd subcmd_func = globals().get(subcmd_func_name, None) if subcmd == 'shell': shell() elif not subcmd_func or not callable(subcmd_func): print 'Error: Unable to find subcommand \'%s\'' % subcmd usage() sys.exit(1) try: subcmd_func(remaining[1:], async = is_async) except XenAPIError, e: print 'Error: %s' % str(e.args[0]) sys.exit(2) except OptionError, e: print 'Error: %s' % e sys.exit(0) if __name__ == "__main__": import sys main(sys.argv[1:])