diff --git a/requirements.txt b/requirements.txt index c763621..e22af4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ Jinja2>=2.8 netaddr>=0.7.18 libvirt-python>=1.3.1 ovh==0.4.7 +termcolor==1.1.0 +terminaltables==3.1.0 diff --git a/vm.py b/vm.py index 6e782e0..c5234c3 100755 --- a/vm.py +++ b/vm.py @@ -1,11 +1,14 @@ #!/usr/bin/env python """vm - simple libvirt wrapper -usage: vm [--version] [--help] [...] +usage: vm [--version] [--help] [options] [...] Available commands are: create Create new VM from cloud image ip:list List available OVH IPs ip:sync Create missing vMACs for OVH IPs + +Available options are: +-l, --libvirt=URI Use provided URI to connect to libvirt host """ from __future__ import print_function @@ -29,6 +32,7 @@ import libvirt import ovhtool import ovh import ovh.exceptions +from collections import namedtuple BASE = os.path.dirname(os.path.abspath(__file__)) @@ -73,7 +77,8 @@ class Instance(object): ovh_ips = None def __init__(self, name, cpus, memory, storage, template, - ip=None, mac=None, dns=None, password=None): + ip=None, mac=None, dns=None, password=None, + libvirt_conn=None): # General self.name = name self.cpus = cpus @@ -98,6 +103,8 @@ class Instance(object): if not self.password: self.password = gen_password() + self._libvirt_conn = libvirt_conn + def create(self): self.prepare_datasource() self.prepare_storage() @@ -133,10 +140,16 @@ class Instance(object): self.template_path, self.storage_path) def prepare_instance(self): - conn = libvirt.open(None) + conn = self.libvirt_conn dom = conn.defineXML(self.libvirt_definition) dom.create() + @property + def libvirt_conn(self): + if not self._libvirt_conn: + self._libvirt_conn = libvirt.open(None) + return self._libvirt_conn + def generate_ip(self): '''Generates IP address when none was provided by user''' if not self.ovh_ips: @@ -271,16 +284,170 @@ ey? return 0 +from xml.dom import minidom + +DomainInfo = namedtuple('DomainInfo', [ + 'state', 'max_memory', 'memory', 'vcpus', 'cpu_time' + ]) + +BlockInfo = namedtuple('BlockInfo', [ + 'capacity', 'allocation', 'physical' + ]) + +class LibvirtInterface(object): + def __init__(self, elm): + self.elm = elm + + @property + def type(self): + return self.elm.getAttribute('type') + + @property + def mac(self): + return self.elm.getElementsByTagName('mac')[0].getAttribute('address') + + @property + def alias(self): + return self.elm.getElementsByTagName('alias')[0].getAttribute('name') + + @property + def source(self): + return self.elm.getElementsByTagName('source')[0].getAttribute('bridge') + + +class LibvirtDisk(object): + def __init__(self, elm, dom): + self.elm = elm + self.domain = dom + + @property + def type(self): + return self.elm.getAttribute('type') + + @property + def source(self): + src = self.elm.getElementsByTagName('source') + + if src: + return src[0].getAttribute('dev') + + @property + def info(self): + if self.source: + return BlockInfo(*self.domain.blockInfo(self.source)) + + @property + def present(self): + return bool(self.source) + + +class LibvirtDomain(object): + _xml = None + + def __init__(self, dom, conn=None): + self.domain = dom + self.conn = conn + + @property + def xml(self): + if not self._xml: + self._xml = minidom.parseString(self.domain.XMLDesc(0)) + + return self._xml + + @property + def info(self): + return DomainInfo(*self.domain.info()) + + @property + def name(self): + return self.domain.name() + + @property + def interfaces(self): + for interface in self.xml.getElementsByTagName('interface'): + yield LibvirtInterface(interface) + + @property + def disks(self): + for disk in self.xml.getElementsByTagName('disk'): + yield LibvirtDisk(disk, self.domain) + +def cmd_list(args): + '''usage: vm list + +List existing virtual machines with a summary +''' + import terminaltables + from termcolor import colored + + global libvirt_conn + + def domains_list(): + state_colors = { + 1: 'green', + 3: 'yellow', + 5: 'red', + } + + yield ['Name', 'RAM', 'vCPUs', 'IP', 'Storage'] + + mem_sum = 0 + + for dom in libvirt_conn.listAllDomains(): + state, max_mem, mem, vcpus, cputime = dom.info() + + d = LibvirtDomain(dom) + + formatted_mem = '%dM' % (mem/1024) + + if mem != max_mem: + formatted_mem += colored(' (%dM)' % (max_mem/1024), 'grey') + + mem_sum += mem + + disk_info = [] + for disk in d.disks: + if disk.type == 'block' and disk.present: + disk_info.append( + ('%dG ' + colored('(%s)', 'grey')) % ( + disk.info.physical/(1024*1024*1024), disk.source + ) + ) + yield [ + colored(d.name, state_colors.get(state, None)), + formatted_mem, + vcpus, + '', + ', '.join(disk_info) + ] + + yield [] + yield ['', '%dM' % (mem_sum/1024), '', '', ''] + + table = terminaltables.SingleTable(list(domains_list())) + table.justify_columns={ + 0: 'right', 1: 'right', 2: 'right' + } + + print(table.table) + + commands = { 'create': cmd_create, + 'list': cmd_list, 'ip:list': cmd_iplist, 'ip:sync': cmd_ipsync, 'help': cmd_help, } +libvirt_conn = None + def main(gargv): + global libvirt_conn args = docopt(__doc__, version='vm '+__version__, options_first=True, argv=gargv) + libvirt_conn = libvirt.open(args['--libvirt']) cmd = args[''].lower() argv = [cmd] + args[''] if cmd not in commands: