#! /usr/bin/env python

# import modules {{{
import sys
import os
import getopt
import libvirt
import tempfile
import re

from lxml import etree # for comments see http://codespeak.net/lxml.
# }}}
# global constants {{{
MKVE_DIR = "/usr/share/mkve"
MKVE_CACHE_DIR = "/var/lib/mkve"
MKVE_MACHINES_DIR = MKVE_CACHE_DIR+"/machines"
MKVE_CONF_DIR = MKVE_DIR+"/conf"
MKVE_TOOLS_DIR = MKVE_DIR+"/tools"
# }}}
# global functions {{{
# echo {{{
def echo(message): sys.stdout.write("%s\n" % message)
# }}}
# warn {{{
def warn(mess): sys.stderr.write("%s: %s\n" % (sys.argv[0], mess))
# }}}
# die {{{
def die(howl): raise Exception(howl)
# }}}
# option {{{
def option(name, noargs=False):
    if noargs: return opts.has_key(name)
    if opts.has_key(name): return opts[name]
    return None
# }}}
# setLibvirtConnection {{{
def setLibvirtConnection(hyper="default"):
    """
    Set a libvirt connection to given (or default) hypervisor
    """
    urls = {
            "default":  "",
            "kvm":     "qemu:///system", # libvirt rules: kvm => qemu
            "openvz":   "openvz:///system"
            }
    if not urls.has_key(hyper):
        die("setLibvirtConnection get unsupported hypervisor %s" %( hyper ))

    conn = libvirt.open(urls[hyper])
    if not conn:
        die("failed to open connection to the hypervisor %s" %(hyper))

    return conn
# }}}
# names_list {{{
def names_list():
    return os.access(MKVE_MACHINES_DIR, os.F_OK) \
            and os.listdir(MKVE_MACHINES_DIR) \
            or []
# }}}
# system {{{
def system(cmd, *args, **dict):
    for arg in args:
        cmd += ' %s' %(arg)
    if os.system(cmd):
        warnmsg = dict.has_key('warnmsg') and dict['warnmsg']
        errmsg = dict.has_key('errmsg') and dict['errmsg'] or "os.system: failed to run '%s'" %(cmd)
        if warnmsg: warn(warnmsg)
        else: die(errmsg)
# }}}

# }}}

# XML description {{{
class Element: # {{{
    def __init__(self, name, args):
        self.args = args
        self.root = etree.Element(name)

    def append(self, subelement): # {{{
        try:
            self.root.append(subelement.root)
        except:
            try:
                self.root.append(subelement)
            except:
                self.error('can not append element %s' %subelement)
    # }}}
    def set(self, name, value): # {{{
        self.root.set(name, value)
    # }}}
    def sub_element(self, name, **args): # {{{
        return etree.SubElement(self.root, name, args)
    # }}}
    def error(self, message): # {{{
        e = '%s: %s' %(self.root.tag, message)
        raise Exception(e)
    # }}}
    def __str__(self): # {{{
        return etree.tostring(self.root, pretty_print=True)
    # }}}
    def arg(self, name): # {{{
        """\
        Return argument named 'name'
        """
        try:
            return self.args[name]
        except KeyError:
            return None
    # }}}
    def check_arg(self, argname, arg, *possible_values): # {{{
        if not arg:
            self.error('argument %s must be non-empty' %(argname))
        if possible_values and arg not in possible_values:
            self.error('wrong value of argument %s: %s; must be one of %s' \
                    %(argname, arg, possible_values))
    # }}}
# }}}

# classes osxxx {{{

class osxen(Element): # {{{
    def __init__(self, **args):
        Element.__init__(self, 'os', args)

        self.__ostypexen()
        self.__osbootkernel()

    def __ostypexen(self): # {{{
        type_text = self.arg('type')
        self.check_arg('type', type_text, 'xen', 'linux')
        type = self.sub_element('type')
        type.text = type_text

        arch = self.arg('arch')
        if arch:
            self.check_arg('arch', arch, 'i686', 'x86_64', 'ia64')
            type.set('arch', arch)

        machine = self.arg('machine')
        if machine:
            self.check_arg('machine', machine, 'xenpv', 'xenner')
            type.set('machine', machine)
    # }}}
    def __osbootkernel(self): # {{{
        kernel  = self.arg('kernel')
        if kernel: self.sub_element('kernel').text = kernel

        initrd  = self.arg('initrd')
        if initrd: self.sub_element('initrd').text = initrd

        root    = self.arg('root')
        if root: self.sub_element('root').text = root

        cmdline = self.arg('cmdline')
        if cmdline: self.sub_element('cmdline').text = cmdline
    # }}}
# }}}

class oshvm(Element): # {{{
    def __init__(self, **args):
        Element.__init__(self, 'os', args)

        self.__ostypehvm()
        self.__osbootkernel()
        self.__osbootdev()
        self.__loader()

    def __ostypehvm(self): # {{{
        type = self.sub_element('type')
        type.text = 'hvm'

        arch = self.arg('arch')
        self.check_arg('arch', arch, 'i686', 'x86_64', 'mips', 'sparc', 'ppc')
        type.set('arch',  arch)

        machine = self.arg('machine')
        self.check_arg('machine', machine, 'xenfv', 'pc', 'isapc', 'mips', 'sun4m', 'g3bw', 'mac99', 'prep')
        type.set('machine',  machine)
    # }}}
    def __osbootkernel(self): # {{{
        kernel  = self.arg('kernel')
        if kernel: self.sub_element('kernel').text = kernel

        initrd  = self.arg('initrd')
        if initrd: self.sub_element('initrd').text = initrd

        root    = self.arg('root')
        if root: self.sub_element('root').text = root

        cmdline = self.arg('cmdline')
        if cmdline: self.sub_element('cmdline').text = cmdline
    # }}}
    def __osbootdev(self): # {{{
        boot_dev = self.arg('boot_dev')
        if boot_dev:
            self.check_arg('boot_dev', boot_dev, 'hd', 'fd', 'cdrom', 'network')
            self.sub_element('boot', dev=boot_dev)
    # }}}
    def __loader(self): # {{{
        loader = self.arg('loader')
        if loader:
            self.sub_element('loader').text = loader
    # }}}
# }}}

class osexe(Element): # {{{
    def __init__(self, **args):
        Element.__init__(self, 'os', args)

        self.sub_element('type').text = 'exe'

        init = self.arg('init')
        if init:
            self.sub_element('init').text = init
# }}}

# }}}
# class features {{{
class features(Element):
    def __init__(self, args):
        Element.__init__(self, 'features', args)

        ftrs = self.arg('features')
        if ftrs:
            ftrs = ftrs.split(';')
            for fu in ftrs:
                self.check_args('features', fu, 'pae', 'apic', 'acpi')
                self.sub_element(fu)
# }}}
# class disk {{{
class disk(Element):
    """\
    Any device that looks like a disk, be it a floppy, harddisk, CDROM, or
    paravirtualized driver is specified via the 'disk' element.
    """
    def __init__(self, **args):
        Element.__init__(self, 'disk', args)

        self.__fill_disk()
        self.__fill_source()
        self.__fill_driver()
        self.__fill_target()

        if self.arg('readonly'): self.sub_element('readonly')
        if self.arg('shareable'): self.sub_element('shareable')

    def __fill_disk(self): # {{{
        """\
        The 'disk' element is the main container for describing disks. The
        'type' attribute is either "file" or "block" and refers to the
        underlying source for the disk. The optional 'device' attribute
        indicates how the disk is to be exposed to the guest OS. Possible
        values for this attribute are "floppy", "disk" and "cdrom", defaulting
        to "disk".
        """
        device = self.arg('device')
        if device:
            self.check_arg('device', device, 'floppy', 'disk', 'cdrom')
            self.set('device', device)

        type = self.arg('type')
        if type:
            self.check_arg('type', type, 'file', 'block')
            self.set('type', type)
    # }}}
    def __fill_source(self): # {{{
        """\
        If the disk type is "file", then the 'file' attribute specifies the
        fully-qualified path to the file holding the disk. If the disk type is
        "block", then the 'dev' attribute specifies the path to the host device
        to serve as the disk.
        """
        source = self.arg('source')
        type = self.arg('type')
        if type=='file':
            self.sub_element('source', file=source)
        elif type=='block':
            self.sub_element('source', dev=source)
    # }}}
    def __fill_target(self): # {{{
        """\
        The 'target' element controls the bus/device under which the disk is
        exposed to the guest OS. The 'dev' attribute indicates the "logical"
        device name. The actual device name specified is not guarenteed to map
        to the device name in the guest OS. Treat it as a device ordering hint.

        The optional 'bus' attribute specifies the type of disk device to
        emulate; possible values are driver specific, with typical values being
        "ide", "scsi", "virtio", "xen". If omitted, the bus type is inferred
        from the style of the device name. eg, a device named "sda" will
        typically be exported using a SCSI bus.
        """
        dev = self.arg('target_dev')
        self.check_arg('target_dev', dev)
        target = self.sub_element('target', dev=dev)

        bus = self.arg('target_bus')
        if bus:
            self.check_arg('target_bus', bus, 'ide', 'virtio', 'fdc', 'xen', 'usb')
            target.set('bus', bus)
    # }}}
    def __fill_driver(self): # {{{
        """\
        If the hypervisor supports multiple backend drivers, then the optional
        'driver' element allows them to be selected. The 'name' attribute is
        the primary backend driver name, while the optional 'type' attribute
        provides the sub-type.
        """
        name = self.arg('driver_name')
        if name:
            driver = self.sub_element('driver', name=name)

            type = self.arg('driver_type')
            if type: driver.set('type', type)
    # }}}
# }}}
# class interface {{{
class interface(Element):
    def __init__(self, **args):
        Element.__init__(self, 'interface', args)

        type = self.arg('type')
        self.check_arg('type', type, 'bridge', 'ethernet', 'network', 'user')
        self.set('type', type)

        self.__set_source()

        self.__set_target()
        self.__set_mac()
        self.__set_script()

    def __type2source_attr(self): # {{{
        type = self.arg('type')

        if type == 'bridge':
            return 'bridge'
        elif type == 'ethernet':
            return 'dev'
        elif type == 'network':
            return 'network'
        else:
            self.error('unknown interface type, (%s)' % self)
    # }}}
    def __set_source(self): # {{{
        source_attr_value = self.arg('source')
        if source_attr_value:
            source = self.sub_element('source')
            source_attr = self.__type2source_attr()
            source.set(source_attr, source_attr_value)
    # }}}
    def __set_target(self): # {{{
        target_dev = self.arg('target_dev')
        if target_dev:
            self.sub_element('target', dev=target_dev)
    # }}}
    def __set_mac(self): # {{{
        mac = self.arg('mac')
        if mac:
            self.sub_element('mac', address=mac)
    # }}}
    def __set_script(self): # {{{
        script = self.arg('script')
        if script:
            self.sub_element('script', path=script)
    # }}}
    def __set_ip(self): # {{{
        ip = self.arg('ip')
        if ip:
            self.sub_element('ip', address=ip)
    # }}}
    def __set_model(self): # {{{
        model = self.arg('model')
        if model:
            self.sub_element('model', type=model)
    # }}}

# }}}
# class input {{{
class input(Element):
    """\
    Input devices allow interaction with the graphical framebuffer in the guest
    virtual machine. When enabling the framebuffer, an input device is
    automatically provided. It may be possible to add additional devices
    explicitly, for example, to provide a graphics tablet for absolute cursor
    movement.
    """
    def __init__(self, args):
        """\
        The 'input' element has one madatory attribute, the 'type' whose value
        can be either "mouse" or "tablet". The latter provides absolute cursor
        movement, while the former uses relative movement. The optional 'bus'
        attribute can be used to refine the exact device type. It takes values
        "xen" (paravirtualized), "ps2" and "usb".
        """
        Element.__init__(self, 'input', args)

        type = self.arg('type')
        self.check_arg('type', type, 'tablet', 'mouse')
        self.set('type', type)

        bus = self.arg('bus')
        if bus:
            self.check_arg('bus', bus, 'ps2', 'usb', 'xen')
            self.set('bus', bus)
# }}}
# class kvmcdev {{{
# class source {{{
class source(Element):
    def __init__(self, args):
        Element.__init__(self, 'source', args)

        mode = self.arg('mode')
        if mode: self.set('mode', mode)

        path = self.arg('path')
        if path: self.set('path', path)

        host = self.arg('host')
        if host: self.set('host', host)

        service = self.arg('service')
        if service: self.set('service', service)

        service = self.arg('wiremode')
        if wiremode: self.set('wiremode', wiremode)
# }}}

class kvmcdev(Element):
    def __init__(self, name, args):
        self.__name = name
        Element.__init__(self, name, args)

        tty = self.arg('tty')
        if tty:
            self.set('tty', tty)
        else:
            self.__set_type()
            self.__set_source()
            self.__set_protocol()
            self.__set_target()

    def __set_type(self): # {{{
        type = self.arg('type')
        self.check_arg('type', type, 'dev', 'file', 'pipe', 'unix', 'tcp', 'udp', 'null', 'stdio', 'vc', 'pty')
        self.set('type', type)
    # }}}
    def __set_source(self): # {{{
        sources = self.arg('sources')
        if sources:
            for s in sources:
                self.append( s )
    # }}}
    def __set_protocol(self): # {{{
        type = self.arg('protocol_type')
        if type:
            self.sub_element('protocol', type=type)
    # }}}
    def __set_target(self): # {{{
        port = self.arg('target_port')
        if port:
            self.sub_element('target', port=port)
    # }}}

# class parallel {{{
class parallel(kvmcdev):
    def __init__(self, args):
        kvmcdev.__init__(self, 'parallel', args)
# }}}
# class serial {{{
class serial(kvmcdev):
    def __init__(self, args):
        kvmcdev.__init__(self, 'serial', args)
# }}}
# class console {{{
class console(kvmcdev):
    def __init__(self, args):
        kvmcdev.__init__(self, 'console', args)
# }}}
# }}}
# class hostdev {{{
class hostdev(Element):
    """\
    """
    def __init__(self, args):
        """\
        """
        Element.__init__(self, 'hostdev', args)

        mode = self.arg('mode')
        type = self.arg('type')
        if mode and type:
            self.check_args('mode', mode, 'subsystem', 'capabilities')
            self.check_args('type', type, 'usb', 'pci')
            self.set('mode', mode)
            self.set('type', type)

        source = self.sub_element('source')
        vendor = self.arg('vendor')
        product = self.arg('product')
        bus = self.arg('bus')
        device = self.arg('device')
        if vendor and product:
            etree.SubElement(source, 'vendor', id=vendor)
            etree.SubElement(source, 'product', id=product)
        elif bus and device:
            address = etree.SubElement(source, 'address')
            address.set('bus', bus)
            address.set('device', device)
        else:
            self.error('hostdev: you must specify (vendor and product) or (bus and device)')
# }}}
# class sound {{{
class sound(Element):
    def __init__(self, args):
        Element.__init__(self, 'sound', args)

        model = self.arg('model')
        self.check_arg('model', model, 'sb16', 'es1370', 'pcspk')
        self.set('model', model)
# }}}
# class filesystem {{{
class filesystem(Element):
    def __init__(self, **args):
        Element.__init__(self, 'filesystem', args)

        type = self.arg('type')
        self.check_arg('type', type, 'file', 'block', 'mount', 'template')
        self.set('type', type)

        self.__set_source()
        self.__set_target()

    def __type2source(self):
        return {
                'file': 'file', 'block': 'dev', 'mount': 'dir', 'template': 'name'
                }[self.arg('type')]

    def __set_source(self):
        source = self.sub_element('source')
        source_attr = self.__type2source()
        source.set(source_attr, self.arg('source'))

    def __set_target(self):
        dir = self.arg('target_dir')
        self.check_arg('target_dir', dir)
        self.sub_element('target', dir=dir)

# }}}
# class devices {{{

class devices(Element):
    def __init__(self, args):
        Element.__init__(self, 'devices', args)

        self.__set_emulator()
        self.__set_graphics()
        self.__set_disks()
        self.__set_filesystems()
        self.__set_interfaces()
        self.__set_consoles()
        self.__set_sounds()
        self.__set_parallels()
        self.__set_serials()
        self.__set_inputs()
        self.__set_hostdevs()

    def __set_emulator(self): # {{{
        """\
        The contents of the emulator element specify the fully qualified path
        to the device model emulator binary. The capabilities XML specifies the
        recommended default emulator to use for each particular domain
        type/architecture combination.
        """
        emulator = self.arg('emulator')
        if emulator:
            self.sub_element('emulator').text = emulator
    # }}}
    def __set_graphics(self): # {{{
        type = self.arg('graphics_type')
        if type:
            self.check_arg('type', type, 'sdl', 'vnc')
            grphcs = self.sub_element('graphics', type=type)

        if type == 'vnc':
            port = self.arg('graphics_port')
            if port: grphcs.set('port', port)

            autoport = self.arg('graphics_autoport')
            if autoport:
                self.check_arg('graphics_autoport', autoport, 'yes', 'no')
                grphcs.set('autoport', autoport)

            listen = self.arg('graphics_listen')
            if listen: grphcs.set('listen', listen)

            passwd = self.arg('graphics_passwd')
            if passwd: grphcs.set('passwd', passwd)

            keymap = self.arg('graphics_keymap')
            if keymap: grphcs.set('keymap', keymap)

    # }}}
    def __set_disks(self): # {{{
        disks = self.arg('disks')
        if disks:
            for disk in disks:
                self.append( disk )
    # }}}
    def __set_filesystems(self): # {{{
        filesystems = self.arg('filesystems')
        if filesystems:
            for fs in filesystems:
                self.append( fs )
    # }}}
    def __set_interfaces(self): # {{{
        interfaces = self.arg('interfaces')
        if interfaces:
            for interface in interfaces:
                self.append( interface )
    # }}}
    def __set_consoles(self): # {{{
        consoles = self.arg('consoles')
        if consoles:
            for c in consoles:
                self.append( c )
    # }}}
    def __set_sounds(self): # {{{
        sounds = self.arg('sounds')
        if sounds:
            for s in sounds:
                self.append( s )
    # }}}
    def __set_parallels(self): # {{{
        parallels = self.arg('parallels')
        if parallels:
            for p in parallels:
                self.append( p )
    # }}}
    def __set_serials(self): # {{{
        serials = self.arg('serials')
        if serials:
            for s in serials:
                self.append( s )
    # }}}
    def __set_inputs(self): # {{{
        inputs = self.arg('inputs')
        if inputs:
            for input in inputs:
                self.append( input )
    # }}}
    def __set_hostdevs(self): # {{{
        hostdevs = self.arg('hostdevs')
        if hostdevs:
            for h in hostdevs:
                self.append( h )
    # }}}
# }}}
# class domainXML {{{
class domainXML(Element):
    """\
    Dummy class that does only one thing: get arguments and returns (via
    __str__) XML description of domain.
    """
    def __init__(self, **args):
        """\
        The root element required for all virtual machines is named 'domain'.
        It has two attributes, the 'type' specifies the hypervisor used for
        running the domain. The allowed values are driver specific, but include
        "xen", "kvm", "qemu", "lxc" and "kqemu". The second attribute is 'id'
        which is a unique integer identifier for the running guest machine.
        Inactive machines have no 'id' value.

        We are don't specifying the 'id' element, as it corresponds only to
        running machine.
        """
        Element.__init__(self, 'domain', args)

        type = self.arg('type')
        self.check_arg('type', type, 'xen', 'kvm', 'kqemu', 'qemu', 'lxc', 'openvz', 'test')
        self.set('type', type)

        self.__set_name()
        self.__set_uuid()
        self.__set_memory()
        self.__set_currentMemory()
        self.__set_vcpu()
        self.__set_os()
        self.__set_lifecycle_control()
        self.__set_features()
        self.__set_clock()
        self.__set_devices()

    def __set_name(self): # {{{
        """\
        The content of the 'name' element provides a short name for the virtual
        machine. This name should consist only of alpha-numeric characters and
        is required to be unique within the scope of a single host. It is often
        used to form the filename for storing the persistent configuration file.
        """
        name = self.arg('name')
        self.check_arg('name', name)
        self.sub_element('name').text = name
    # }}}
    def __set_uuid(self): # {{{
        """\
        The content of the 'uuid' element provides a globally unique identifier
        for the virtual machine. The format must be RFC 4122 compliant, eg
        3e3fce45-4f53-4fa7-bb32-11f34168b82b. If omitted when defining/creating
        a new machine, a random UUID is generated.
        """
        uuid = self.arg('uuid')
        if uuid:
            self.sub_element('uuid').text = uuid
    # }}}
    def __set_memory(self): # {{{
        """\
        The maximum allocation of memory for the guest at boot time. The units
        for this value are bytes (??? kilobytes)
        """
        memory = self.arg('memory')
        self.check_arg('memory', memory)
        self.sub_element('memory').text = self.__str_to_memory(memory)
    # }}}
    def __set_currentMemory(self): # {{{
        """\
        The actual allocation of memory for the guest. This value be less than
        the maximum allocation, to allow for ballooning up the guests memory on
        the fly. If this is omitted, it defaults to the same value as the
        memory element
        """
        currentMemory = self.arg('currentMemory')
        if currentMemory:
            self.sub_element('currentMemory').text = self.__str_to_memory(currentMemory)
    # }}}
    def __set_vcpu(self): # {{{
        """\
        The content of this element defines the number of virtual CPUs
        allocated for the guest OS.
        """
        vcpu_text = self.arg('vcpu')
        if vcpu_text:
            vcpu = self.sub_element('vcpu')
            vcpu.text = vcpu_text

            vcpu_cpuset = self.arg('vcpu_cpuset')
            if vcpu_cpuset:
                vcpu.set('cpuset', vcpu_cpuset)
    # }}}
    def __set_os(self): # {{{
        """\
        There are a number of different ways to boot virtual machines each with
        their own pros and cons.
        """
        bootloader = self.arg('bootloader')
        bootloader_args = self.arg('bootloader_args')
        if bootloader:
            self.sub_element('bootloader').text = bootloader
            if bootloader_args:
                self.sub_element('bootloader_args').text = bootloader_args

        os = self.arg('os')
        if os: self.append(os)
    # }}}
    def __set_lifecycle_control(self): # {{{
        """\
        It is sometimes neccessary to override the default actions taken when a
        guest OS triggers a lifecycle operation. The following collections of
        elements allow the actions to be specified. A common use case is to
        force a reboot to be treated as a poweroff when doing the initial OS
        installation. This allows the VM to be re-configured for the first
        post-install bootup.

        on_poweroff
            The content of this element specifies the action to take when the
            guest requests a poweroff.

        on_reboot
            The content of this element specifies the action to take when the
            guest requests a reboot.

        on_crash
            The content of this element specifies the action to take when the
            guest crashes.

        Each of these states allow for the same four possible actions.

        destroy
            The domain will be terminated completely and all resources released

        restart
            The domain will be terminated, and then restarted with the same
            configuration

        preserve
            The domain will be terminated, and its resource preserved to allow
            analysis.

        rename-restart
            The domain will be terminated, and then restarted with a new name
        """
        on_poweroff = self.arg('on_poweroff')
        if on_poweroff:
            self.check_arg('on_poweroff', on_poweroff, 'destroy', 'restart', 'preserve', 'rename-restart')
            self.sub_element('on_poweroff').text = on_poweroff

        on_reboot = self.arg('on_reboot')
        if on_reboot:
            self.check_arg('on_reboot', on_reboot, 'destroy', 'restart', 'preserve', 'rename-restart')
            self.sub_element('on_reboot').text = on_reboot

        on_crash = self.arg('on_crash')
        if on_crash:
            self.check_arg('on_crash', on_crash, 'destroy', 'restart', 'preserve', 'rename-restart')
            self.sub_element('on_crash').text = on_crash
# }}}
    def __set_features(self): # {{{
        if self.arg('features'):
            self.append(features(self.args))
    # }}}
    def __set_clock(self): # {{{
        """\
        The guest clock is typically initialized from the host clock. Most
        operating systems expect the hardware clock to be kept in UTC, and this
        is the default. Windows, however, expects it to be in so called
        'localtime'.

        The 'sync' attribute takes either "utc" or "localtime" to specify how
        the guest clock is initialized in relation to the host OS.
        """
        offset = self.arg('clock_offset')
        if offset:
            self.check_arg('clock_offset', offset, 'localtime', 'utc')
            self.sub_element('clock', offset=offset)
    # }}}
    def __set_devices(self): # {{{
        """\
        The final set of XML elements are all used to descibe devices provided
        to the guest domain. All devices occur as children of the main
        'devices' element.
        """
        self.append(devices(self.args))
    # }}}

    def __str_to_memory(self, s):
        mul = 1024
        if s[-1] == 'K':
            mul = 1
            s = s[:-1]
        elif s[-1] == 'M':
            s = s[:-1]
        elif s[-1] == 'G':
            mul *= 1024
            s = s[:-1]
        return str(mul * int(s))
# }}}

# class networkXML {{{
class networkXML(Element):
    def __init__(self, **args):
        Element.__init__(self, 'network', args)

        self.__set_name()
        self.__set_uuid()
        self.__set_bridge()
        self.__set_ip()
        self.__set_forward()

    # __set_name {{{
    def __set_name(self):
        name = self.arg('name')
        self.check_arg('name', name)
        self.sub_element('name').text = name
    # }}}
    # __set_uuid {{{
    def __set_uuid(self):
        uuid = self.arg('uuid')
        if uuid:
            self.sub_element('uuid').text = uuid
    # }}}
    # __set_bridge {{{
    def __set_bridge(self):
        """\
        Bridge device, to which this network will be connected
        """
        bridge_name = self.arg('bridge')
        if not bridge_name: return

        bridge_stp = self.arg('bridge_stp')
        bridge_delay = self.arg('bridge_delay')

        bridge = self.sub_element('bridge')
        bridge.set('name', bridge_name)

        if bridge_stp:
            self.check_arg('bridge_stp', bridge_stp, 'on', 'off')
            bridge.set('stp', bridge_stp)

        if bridge_delay:
            bridge.set('delay', bridge_delay)
    # }}}
    # __set_ip {{{
    def __set_ip(self):
        address = self.arg('address')
        if not address: return

        netmask = self.arg('netmask')
        self.check_arg('netmask', netmask)
        ip = self.sub_element('ip', address=address, netmask=netmask)

        dhcp_ranges = self.arg('dhcp_ranges')
        if dhcp_ranges:
            dhcp = etree.SubElement(ip, 'dhcp')
            for b,e in dhcp_ranges:
                etree.SubElement(dhcp, 'range', start=b, end=e)
    # }}}
    # __set_forward {{{
    def __set_forward(self):
        dev = self.arg('forward_dev')
        mode = self.arg('forward_mode')

        if dev or mode:
            forward = self.sub_element('forward')

            if dev: forward.set('dev', dev)
            if mode:
                self.check_arg('forward_mode', mode, 'nat', 'routed')
                forward.set('mode', mode)
    # }}}
# }}}
# }}}

# class Network {{{
class Network:
    def __init__(self):
        pass

    # create {{{
    # __parse_address {{{
    def __dot_decimal_notation(self, mask):
        mask = (2**32 - 1) - (2**(32-mask) - 1)
        a = (mask & int('ff000000', 16)) >> 24
        b = (mask & int('00ff0000', 16)) >> 16
        c = (mask & int('0000ff00', 16)) >> 8
        d = (mask & int('000000ff', 16))
        return '%s.%s.%s.%s' %(a,b,c,d)

    def __parse_address(self):
        address = option('address')
        address, netmask = address.split('/')
        netmask = self.__dot_decimal_notation(int(netmask))
        return address, netmask
    # }}}
    # __parse_dhcp_ranges {{{
    def __parse_dhcp_ranges(self):
        ranges = option('dhcp-ranges')
        if not ranges: return []
        try:
            ranges = ranges.strip(';').split(';')
            return [range.split('-') for range in ranges]
        except Exception, e:
            die("can't parse dhcp ranges: %s" %(e))
    # }}}

    def create(self):
        name = option('name')
        bridge = option('bridge')
        address, netmask = self.__parse_address()
        dhcp_ranges = self.__parse_dhcp_ranges()

        xml = networkXML(
            name=name,
            bridge=bridge,
            address=address,
            netmask=netmask,
            dhcp_ranges=dhcp_ranges
            )
        conn = setLibvirtConnection()
        me = conn.networkDefineXML(str(xml))
        me.create()
        me.setAutostart(1)

    # }}}
    # destroy {{{
    def destroy(self):
        name = option('name')
        conn = setLibvirtConnection()
        me = conn.networkLookupByName(name)
        me.destroy()
        me.undefine()
    # }}}
    # list {{{
    def list(self):
        conn = setLibvirtConnection()
        active = option('active', noargs=True)
        inactive = option('inactive', noargs=True)

        networks = []
        if active and not inactive:
            networks = conn.listNetworks()
        if not active and inactive:
            networks = conn.listDefinedNetworks()
        else:
            networks = conn.listNetworks()
            networks += conn.listDefinedNetworks()
            networks.sort()

        for net in networks: echo(net)

    # }}}
    # info {{{
    def info(self):
        pass
    # }}}

# }}}

# class, that represents a config file for machine {{{
class InfoFile:

    class Error(Exception): pass

    def __init__(self, name):
        self.__name = name
        self.__sections = {}

        try:
            if os.access(name, os.F_OK): self.__read()
        except IOError, e:
            raise InfoFile.Error("can't open source file %s: %s" %(name, e))

    def get(self, section, key):
        if not self.__sections.has_key(section):
            raise InfoFile.Error("no such section: %s" %section)
        if not self.__sections[section].has_key(key):
            raise InfoFile.Error("section '%s' isn't containing key '%s'" %(section, key))
        return self.__sections[section][key]

    def set(self, section, key, value):
        if not self.__sections.has_key(section):
            self.__sections[section] = {}
        self.__sections[section][key] = value

    def sync(self, name=''):
        name = name or self.__name
        s = ''
        for sect_name in self.__sections.keys():
            s += "[%s]\n" %(sect_name)

            sect = self.__sections[sect_name]
            for k in sect.keys():
                s += '%s=%s\n' %(k, sect[k])
        try:
            f = file(name, "w")
            f.write(s)
            f.close()
        except IOError, e:
            raise InfoFile.Error("can't write file: %s" %e)
        except Exception, e:
            raise InfoFile.Error("self.sync failed: %s" %e)

    def __read(self):
        "try to parse a config file if it exists"

        f = file(self.__name)
        lines = [l.strip() for l in f.readlines()]
        lines = [l for l in lines if l and l[0] != '#']
        f.close()

        for l in lines:
            if l[0]=='[' and l[-1]==']':
                new_section = l[1:-1]
                if self.__sections.has_key(new_section):
                    raise InfoFile.Error("section %s already defined" %new_section)
                self.__sections[new_section] = {}
                self.__current_section = self.__sections[new_section]
            elif l.count('='):
                key, value = l.split('=', 1)
                self.__current_section[key] = value

# }}}

# class Machine {{{
class Machine:

    class Error(Exception): pass

    def __init__(self, name=""):
        self.name = name or option("name")

    # __logfile {{{
    def __logfile(self):
        path = "%s/%s/log" % (MKVE_MACHINES_DIR, option('name'))
        return path
    # }}}
    # __log {{{
    def __log(self, message):
        logfile = self.__logfile()
        if not os.access(logfile, os.F_OK):
            file(logfile, 'w').close()
        message = "%s: Machine: %s\n" % (sys.argv[0], message)
        file(logfile, 'a').write(message)
    # }}}
    # __fatal_cleanup {{{

    # __save_log {{{
    def __save_log(self):
        logfile = self.__logfile()
        if os.access(logfile, os.F_OK):
            system('cp', logfile, '/tmp/mkve.log') # XXX: don't use /tmp
    # }}}
    # __hypervisor_fatal_cleanup {{{

    # __openvz_fatal_cleanup {{{
    def __openvz_fatal_cleanup(self):
        # check, that we can access machine directory
        if self.name not in names_list(): return

        try:
            id = self.get('main', 'id')

            # check if this id references to existent machine
            if id:
                cmd = "vzctl status %s | cut -d ' ' -f 3" % (id)
                if os.popen(cmd).read().strip() == 'exist':
                    system('vzctl', 'destroy', id)
        except Exception, e:
            raise Machine.Error("__openvz_fatal_cleanup: %s" %e)
    # }}}

    def __hypervisor_fatal_cleanup(self):
        {
            "kvm":     (lambda:0),
            "openvz":   self.__openvz_fatal_cleanup
        } [ self.get('main', 'hypervisor') ] ()
    # }}}

    def __fatal_cleanup(self):
        """\
        When creating of machine is failed, we need to make cleanup, e.g.,
        remove lock.
        """

        self.__hypervisor_fatal_cleanup()
        self.__save_log()
        self.__remove_dir()

    # }}}
    # __dir {{{
    def __dir(self):
        """return machine directory"""
        return MKVE_MACHINES_DIR + "/" + self.name
    # }}}
    # __remove_dir {{{
    def __remove_dir(self): system('rm', '-rf', self.__dir())
    # }}}
    # __domain {{{
    def __domain(self):
        hypervisor = self.get('main', 'hypervisor')
        conn = setLibvirtConnection(hypervisor)

        name = self.name
        if hypervisor == 'openvz': name = self.get('main', 'id')
        dom = conn.lookupByName(name)
        if not dom:
            die("can't find domain '%s'" %(name))
        return dom
    # }}}
    # get & set {{{

    def __init_info(self):

        path = "%s/%s/info" %(MKVE_MACHINES_DIR, self.name)
        self.__info = InfoFile(path)

    def get(self, section, key):
        try:
            return self.__info.get(section, key)
        except AttributeError:
            self.__init_info()
            return self.__info.get(section, key)

    def set(self, section, key, value):
        try:
            self.__info.set(section, key, value)
            self.__info.sync()
        except AttributeError:
            self.__init_info()
            self.__info.set(section, key, value)
            self.__info.sync()
    # }}}
    # create, adopt {{{
    # __prepare_directory {{{

    # __generate_id {{{
    def __defined_by_mkve(self, id):
        for name in names_list():
            if Machine(name=name).get('main', 'id') == id:
                return True
        return False

    def __defined_id(self, id):
        if self.__defined_by_mkve(id):
            return True

        cmd = "vzctl status %s | cut -d ' ' -f 3" % (id)
        if os.popen(cmd).read().strip() == 'exist':
            warn("there was unregistered openvz machine with cid %s" % id)
            return True

        return False

    def __generate_id(self):
        for id in xrange(100, 255):
            id = str(id)
            if self.__defined_id(id): continue
            return id
        die("id's are exhausted")
    # }}}

    # __opt_id: try to get new id from command line {{{
    def __opt_id(self):
        id = option("ovz-id")
        if not id: return id

        if not re.match("^[0-9]+$", id):
            die("ovz-id (%s) must be a valid container id" %id)

        if self.__defined_id(id):
            die("machine with same id (%s) already exists" % id)

        return id
    # }}}

    def __prepare_directory(self):

        # Check, that directory with such name doesn't exist, then
        # generate an unique identification number, then create a directory
        try:
            if self.name in names_list():
                die("machine with name %s already exists" % self.name)
            id = self.__opt_id() or self.__generate_id()
        except Exception, e:
            warn(e)
            sys.exit(1)

        os.mkdir(self.__dir())

        # now extract a bundle info into machine directory for further processing by
        # hypervisor-specific routines, that must extract other parts theirselvs
        cmd = "cd %s && tar -xf %s --occurrence info" %(self.__dir(), option('bundle'))
        errmsg = "can't extract a bundle info into %s" % self.__dir()
        system(cmd, errmsg=errmsg)
        self.set('main', 'id', id)
# }}}
    # __hypervisor_create {{{

    # __openvz_create {{{

    def __subst_ovz_conf(self, config, n, v):
        cmd = "egrep -qs '^%s=' %s && sed -i 's#^%s=.*$#%s=%s#' %s || echo '%s=%s' >> %s" \
                %(n, config, n, n, v, config, n, v, config)
        system(cmd, errmsg="__subst_ovz_conf: can't substitute config")

    def __openvz_create(self):

        # now extract a bundle into machine directory for further processing
        # we don't rewrite info, that have been changed, so use -k
        cmd = "cd %s && tar --exclude info -xf %s" %(self.__dir(), option('bundle'))
        errmsg = "can't extract a bundle into %s" % self.__dir()
        system(cmd, errmsg=errmsg)

        # create OpenVZ cache
        tar = "/var/lib/vz/template/cache/altlinux-%s.tar" % self.name
        system('rm -f', tar, errmsg="__openvz_create: can't remove old tarball")
        system('ln -sf', self.__dir()+'/files.tar', tar, errmsg="__openvz_create: can't link")

        # prepare a config
        # 1. copy a DEFAULT config
        # 2. substitute values from additional config if it exists
        config = "/etc/vz/conf/ve-altlinux-%s.conf-sample" % self.name
        default = MKVE_CONF_DIR+"/openvz/ve-DEFAULT.conf-sample"
        errmsg = "__openvz_create: can't copy a default config"
        system('cp', default, config)

        try:
            add_conf = self.get('openvz', 'config')
        except InfoFile.Error:
            add_conf = 0
        if add_conf:
            f = os.popen("tar -O -xf %s/%s" %(self.__dir(), add_conf))
            for line in f.readlines():
                if line.strip():
                    name, value = line.strip().split('=')
                    self.__subst_ovz_conf(config, name, value)
    # }}}

    def __hypervisor_create(self):
        self.__log("run hypervisor-specific stuff after creating a template")
        {
            "kvm":   (lambda: 0),
            "openvz": self.__openvz_create
        } [ self.get('main', 'hypervisor') ] ()


    # }}}
# __defineXML {{{
    # __openvz_create_xml {{{
    def __openvz_create_xml(self):
        name = option('name')
        id = self.get('main', 'id')
        interfaces=[]

        if option('network'):
            interfaces.append(interface(type='network', source=option('network')))

        xml = domainXML(
            type='openvz',
            name=id,
            memory='0',
            os=osexe(),
            filesystems=(
                filesystem(type="template", source="altlinux-%s" % name, target_dir="/"),
                ),
            interfaces=interfaces
            )
        return str(xml)
    # }}}
    # __kvm_create_xml {{{
    def __kvm_create_xml(self):
        image = '%s/%s' % (self.__dir(), self.get('image', 'image_path'))
        if image.split('.')[-1] == 'lzma': image = image[:-5]
        bus=self.get('image', 'image_bus') or 'scsi'
        if bus not in ('ide', 'scsi'):
            die("bad value for bus: %s" %bus)
        dev = { "ide": "hda", "scsi": "sda" } [bus]

        bridge = option('kvm-bridge')
        if not bridge:
            die('option --kvm-bridge must be non-empty')
        interfaces = [ interface(type='bridge', source=bridge) ]
        memory = option('memory')
        if memory: self.set('main', 'memory', memory)
        else: memory = self.get('main', 'memory')
        arch = self.get('main', 'arch')

        graphics_listen, graphics_port = "127.0.0.1", "-1"
        vnc_listen = option('vnc')
        if vnc_listen:
            if ':' in vnc_listen:
                graphics_listen, graphics_port = vnc_listen.split(':')
            else:
                graphics_listen = vnc_listen

        xml = domainXML(
                        type='kvm',
                        name=option('name'),
                        vcpu='1',
                        memory=memory,
                        os=oshvm(arch=arch, machine='pc', boot_dev='hd'),
                        emulator='/usr/bin/kvm',
                        graphics_type='vnc',
                        graphics_listen=graphics_listen,
                        graphics_port=graphics_port,
                        disks=( disk(device='disk', type='file', source=image, target_dev=dev, taget_bus=bus), ),
                        interfaces=interfaces
                        )
        return str(xml)
    # }}}
    def __defineXML(self):
        hypervisor = self.get('main', 'hypervisor')
        self.__log("create and define an XML description of domain")
        xml = {
                "kvm":   self.__kvm_create_xml,
                "openvz": self.__openvz_create_xml
              } [hypervisor]()
        self.__log("create with following xml:\n%s" %xml)
        conn = setLibvirtConnection(hypervisor)
        conn.defineXML(xml)
# }}}
    # __hypervisor_post_create {{{

    # __openvz_post_create {{{
    def __openvz_post_create(self):
        veid = self.get('main', 'id')

        # XXX: need to create/change config
        # XXX: config = "%s/conf/openvz/ve-DEFAULT.conf-sample" % (MKVE_DIR)
        # XXX: openvz_config = "/etc/vz/conf/ve-%s.conf-sample" %(name)

        cmd = "vzctl set %s --name %s --save >>%s 2>&1" % (veid, self.name, self.__logfile())
        errmsg = "__openvz_post_create: `vzctl set --name` failed"
        self.__log("__openvz_post_create: run '%s'" % (cmd))
        system(cmd, errmsg=errmsg)
    # }}}
    # __kvm_post_create {{{
    def __kvm_post_create(self):
        # now extract a bundle into machine directory for further processing
        # we don't rewrite info, that have been changed, so we --exclude it
        cmd = "cd %s && tar --exclude info -xf %s" %(self.__dir(), option('bundle'))
        errmsg = "can't extract a bundle into %s" % self.__dir()
        system(cmd, errmsg=errmsg)

        # check if disk image was archivated by lzma, and unlzma it if it was
        image = '%s/%s' % (self.__dir(), self.get('image', 'image_path'))
        if image.split('.')[-1] == 'lzma':
            cmd = "cd %s && unlzma %s" %(self.__dir(), image)
            errmsg = "can't unlzma an image %s" % image
            system(cmd, errmsg=errmsg)
            self.set('image', 'image_path', image[:-5])
    # }}}

    def __hypervisor_post_create(self):
        self.__log("run hypervisor-specific stuff after defining an XML")
        {
            "kvm":    self.__kvm_post_create,
            "openvz": self.__openvz_post_create
        } [ self.get('main', 'hypervisor') ] ()

    # }}}
    # __set_bundle_name(self) {{{
    def __set_bundle_name(self):
        b = os.path.abspath(opts["bundle"])
        if not os.access(b, os.R_OK):
            die("can't access file '%s'" %(b))
        opts["bundle"] = b
    # }}}

    def create(self):
        try:
            self.__set_bundle_name()
        except Exception, e:
            raise Machine.Error("can't determine real bundle name: %s" %e)

        try:
            self.__prepare_directory()
        except Exception, e:
            self.__remove_dir()
            raise Machine.Error("can't create new machine: %s" %e)

        try:
            self.__hypervisor_create()
            self.__defineXML()
            self.__hypervisor_post_create()
        except Exception, e:
            self.__fatal_cleanup()
            raise Machine.Error("can't create new machine: %s" %e)

    def __adopt_id(self):
        hypervisor = option('hypervisor')
        if hypervisor == 'openvz':
            id = option('args')
            if not id:
                die("args option isn't specified")
            if self.__defined_by_mkve(id):
                die("id %s is already defined by mkve" %id)
            system("vzlist -H %s >/dev/null 2>&1" % id, errmsg="no shuch VZ machine: %s" % id)
            return id
        else:
            die("adopt command supports only openvz hypervisor")

    def adopt(self):
        # Check, that directory with such name doesn't exist
        if self.name in names_list():
            die("machine with name %s already exists" % self.name)

        id = self.__adopt_id()
        os.mkdir(self.__dir())

        try:
            hypervisor = option('hypervisor')
            if hypervisor == 'openvz':
                self.set('main', 'id', id)
                self.set('main', 'hypervisor', 'openvz')

            cmd = "vzctl set %s --name %s --save >>%s 2>&1" %(id, self.name, self.__logfile())
            errmsg = "__openvz_post_create: `vzctl set --name` failed"
            self.__log("__openvz_post_create: run '%s'" % (cmd))
            system(cmd, errmsg=errmsg)

        except Exception, e:
            self.__remove_dir()
            raise Machine.Error("can't adopt a machine: %s" %e)
    # }}}
    # pack {{{
    def pack(self):
        hypervisor = self.get('main', 'hypervisor')
        if hypervisor not in ('kvm', 'qemu', 'openvz'):
            die("unsupported hypervisor: %s" % hypervisor)

        # stop a domain
        try:
            if self.__domain().info()[0] in (1,2,3):
                self.stop()
        except Exception, e:
            die(e)

        # choose an appropriate functionality
        if hypervisor == 'kvm' or hypervisor == 'qemu':
            arglist = ['--output', option('output'),
                       '--info', "%s/info" %self.__dir(),
                       '--image', "%s/%s" %(self.__dir(), self.get('image', 'image_path'))
                       ]
            if option('no-lzma', noargs=True): arglist.append('--no-lzma')

            cmd = MKVE_TOOLS_DIR+'/mkve-pack-kvm'
            system(cmd, *arglist)
        elif hypervisor == 'openvz':
            info = "--info %s/info" % self.__dir()
            id   = self.get('main', 'id')
            outp = option('output')

            cmd = MKVE_TOOLS_DIR+'/mkve-pack-openvz'
            system(cmd, info, id, outp)
    # }}}
    # destroy {{{
    def __hypervisor_postun(self):
        if self.get('main', 'hypervisor') == 'openvz':
            tar = "/var/lib/vz/template/cache/altlinux-%s.tar" % self.name
            os.unlink(tar)

    def destroy(self):
        try: self.stop()
        except Exception, e: warn(e)

        try: self.__domain().undefine()
        except Exception, e: warn(e)

        try: self.__hypervisor_postun()
        except Exception, e: warn(e)

        self.__remove_dir()
    # }}}
    # info {{{

    # __state {{{
    def __state(self, state):
        __states = [
                "0: no state",
                "1: the domain is running",
                "2: the domain is blocked on resource",
                "3: the domain is paused by user",
                "4: the domain is being shut down",
                "5: the domain is shut off",
                "6: the domain is crashed"
                ]
        return __states[state]
    # }}}
    # __vnc_display {{{
    def __vnc_display(self):
        listen = None
        port = None

        if self.get('main', 'hypervisor') == 'openvz':
            return 'none'

        state = self.__domain().info()[0]
        if state in (0, 4, 5, 6):
            return 'none'

        desc = self.__domain().XMLDesc(0)
        for i in etree.fromstring(desc):
            if i.tag == 'devices':
                for j in i:
                    if j.tag == 'graphics':
                        listen = j.get('listen')
                        port = j.get('port')

        ret = listen or "127.0.0.1"
        if port: ret += ":%s" %port

        return ret
    # }}}

    def info(self):
        mess = ''
        opts = {}
        true_opts = {}
        optslist = ('hypervisor', 'id', 'ip-address', 'state', 'memory', 'vcpu', 'cpu-time', 'start-on-boot', 'vnc')

        for o in optslist:
            opts[o] = option(o, noargs=True)
            if opts[o]: true_opts[o]=1
        show_all = (len(true_opts) == 0) or (len(true_opts) == len(optslist))
        prepend = len(true_opts) != 1

        # extern information {{{
        if opts['hypervisor'] or show_all:
            if prepend: mess += "hypervisor: "
            mess += "%s\n" % self.get('main', 'hypervisor')

        if opts['id'] or show_all:
            if prepend: mess += "id: "
            mess += "%s\n" % self.get('main', 'id')

        if opts['ip-address'] or show_all:
            if prepend: mess += "ip address: "
            mess += "%s\n" % ("192.0.2." + self.get('main', 'id'))
        # }}}
        # the information given by virDomain.info {{{
        if opts['state'] or opts['memory'] or opts['vcpu'] or opts['cpu-time'] or show_all:
            info = self.__domain().info()
            if opts['state'] or show_all:
                if prepend: mess += "state: "
                mess += "%s\n" % self.__state(info[0])
            if opts['memory'] or show_all:
                if prepend: mess += "memory: "
                mess += "%s\n" % info[2]
            if opts['vcpu'] or show_all:
                if prepend: mess += "vcpu: "
                mess += "%s\n" % info[3]
            if opts['cpu-time'] or show_all:
                if prepend: mess += "cpu-time: "
                mess += "%s\n" % (float(info[4])/(10**9))
        # }}}
        # start on boot {{{
        if opts['start-on-boot'] or show_all:
            if prepend: mess += "start on boot: "
            mess += "%s\n" %(self.__domain().autostart() and "yes" or "no")
        # }}}
        # vnc display {{{
        if opts['vnc'] or show_all:
            if prepend: mess += "vnc display: "
            mess += "%s\n" %(self.__vnc_display())
        # }}}

        if mess:
            # delete the last \n
            if mess[-1] == '\n': mess = mess[:-1]
            echo(mess)
        else: die('info: strange, but info message is empty')
    # }}}
    # start {{{
    def start(self):
        # try to load tun.ko if hypervisor is kvm
        if self.get('main', 'hypervisor') == "kvm":
            cmd = "egrep -q '^tun ' /proc/modules || modprobe tun"
            system(cmd, warnmsg="can't upload tun.ko")

        self.__domain().create()
    # }}}
    # stop {{{
    def stop(self, critical=None):
        if critical and self.__domain().info()[0] not in (1,2,3): # i.e., if domain is not running
            die("can't stop domain: it is not running")
        try:
            if self.__domain().info()[0] in (1,2,3): # i.e., if domain is running
                self.__domain().destroy()
        except:
            if critical:
                die("can't stop domain")
    # }}}
    # restart {{{
    def restart(self):
        if self.__domain().info()[0] not in (1,2,3): # i.e., if domain is not running
            die("can't restart domain: it is not running")
        try:
            # reboot( UNUSED flags )
            self.__domain().reboot( 0 )
        except:
            die("can't restart domain")
    # }}}
    # suspend {{{
    def suspend(self):
        self.__domain().suspend()
    # }}}
    # resume {{{
    def resume(self):
        self.__domain().resume()
    # }}}
    # autostart {{{
    def autostart(self):
        self.__domain().setAutostart(not option('disable', noargs=True))
    # }}}
# }}}

# main {{{

# commands {{{
# create {{{
def command_create():
    """\
    create: creates a new virtual machine

    @args@
    name= name of creating machine;
    bundle= path to source bundle;
    kvm-bridge=? bridge to connect to;
    ovz-id=? CTID of container;
    memory=? amount of RAM for VE;
    vnc=? "<IP>[:<port>]" to connect to;
    network=? virtual network to connect to (see net-* commands);
    """
    process_opts(command_create.__doc__)

    Machine().create()
# }}}
# adopt {{{
def command_adopt():
    """\
    adopt: adopt an orphaned (i.e., non-mkve) virtual machine

    @args@
    name= name of new machine;
    hypervisor= hypervisor;
    args= hypervisor-specific arguments;
    """
    process_opts(command_adopt.__doc__)

    Machine().adopt()
# }}}
# pack {{{
def command_pack():
    """\
    pack: pack a specified machine into bundle

    @args@
    name= name of target machine;
    output= full name of output;
    no-lzma? do not use lzma when packing;
    """
    process_opts(command_pack.__doc__)

    Machine().pack()
# }}}
# destroy {{{
def command_destroy():
    """\
    destroy: destroys a specified machine

    @args@
    name= name of destroying machine
    """
    process_opts(command_destroy.__doc__)

    Machine().destroy()
# }}}
# list {{{
def command_list():
    """\
    list: list machines, hypervisors or templates

    @args@
    hypervisor=? list machines for specified hypervisor only;
    """
    process_opts(command_list.__doc__)

    hypervisor = option('hypervisor')
    if hypervisor:
        for name in names_list():
            if Machine(name=name).get('main', 'hypervisor') == hypervisor:
                echo(name)
    else:
        for name in names_list(): echo(name)
# }}}
# info {{{
def command_info():
    """\
    info: shows information about specified machine

    If no or more than one additional options specified, command lists
    information, prepended by the option name; if an unique option specified,
    command will echo only the value of this option.

    @args@
    name= what machine to show;
    hypervisor? under what hypervisor does machine run;
    id? what identificator machine has;
    ip-address? IP address for controling machine (has meaning only for openvz);
    state? human-readable state of machine;
    memory? how much RAM does machine use;
    vcpu? how much virtual CPUs does machine use;
    cpu-time? CPU time in microseconds;
    start-on-boot? extract autostart flag;
    vnc? describe vnc display;
    """
    process_opts(command_info.__doc__)

    Machine().info()
# }}}
# start {{{
def command_start():
    """\
    start: starts specified machine

    @args@
    name= name of machine;
    """
    process_opts(command_start.__doc__)

    Machine().start()
# }}}
# stop {{{
def command_stop():
    """\
    stop: stops specified machine

    @args@
    name= name of machine;
    """
    process_opts(command_stop.__doc__)

    Machine().stop(critical="yes")
# }}}
# restart {{{
def command_restart():
    """\
    restart: restarts specified machine

    @args@
    name= name of machine;
    """
    process_opts(command_stop.__doc__)

    Machine().restart()
# }}}
# suspend {{{
def command_suspend():
    """\
    suspend: suspends specified machine (see resume command)

    @args@
    name= name of machine;
    """
    process_opts(command_suspend.__doc__)

    Machine().suspend()
# }}}
# resume {{{
def command_resume():
    """\
    resume: resumes previously suspended machine (see suspend command)

    @args@
    name= name of machine;
    """
    process_opts(command_resume.__doc__)

    Machine().resume()
# }}}
# autostart {{{
def command_autostart():
    """\
    autostart: sets or clears machine autostart flag

    @args@
    name= name of machine;
    disable? clear autostart flag;
    """
    process_opts(command_autostart.__doc__)

    Machine().autostart()
# }}}

# net-create {{{
def command_net_create():
    """\
    net-create: creates a virtual network

    @args@
    name= name of creating network;
    bridge= add network to that bridge;
    address= address of default route in this network;
    dhcp-ranges=? DHCP ranges of form BEGIN-END, separated by semicolons
    """
    process_opts(command_net_create.__doc__)

    Network().create()
# }}}
# net-destroy {{{
def command_net_destroy():
    """\
    net-destroy: destroys a network

    @args@
    name= name of network
    """
    process_opts(command_net_destroy.__doc__)

    Network().destroy()
# }}}
# net-list {{{
def command_net_list():
    """\
    net-list: lists all virtual networks

    @args@
    active? list only active networks (default is all);
    inactive? list only stopped networks
    """
    process_opts(command_net_list.__doc__)

    Network().list()
# }}}
# net-info {{{
def command_net_info():
    """\
    net-info: prints some information about network

    @args@
    name= name of network
    """
    process_opts(command_net_info.__doc__)

    Network().info()
# }}}

# help {{{
def command_help():
    """\
    help: print help
    """

    system("man mkve")
# }}}
# }}}

# parse command line {{{

# extract_opts_from_doc {{{
def extract_opts_from_doc(doc):
    ret = doc.split('@args@')[1].strip()                 # extract argument part
    ret = [x.strip() for x in ret.strip(';').split(';')] # split it to lines with arguments
    return [x.split()[0] for x in ret]                   # extract arguments names
# }}}
# check_mandatory_opts {{{
def check_mandatory_opts(mandatory):
    """\
    We are given a list of options, that must be present and non-empty.
    Each mandatory option require a non-empty argument.
    """

    for o in mandatory:
        if not option(o):
            die('option --%s is absent' %o)
        if option(o).strip() == "":
            die('option --%s is empty' %o)
# }}}

opts = {}
def process_opts(doc):
    """\
    Save all mandatory options, then run getopt, then check mandatory options
    """
    longopts = extract_opts_from_doc(str(doc))
    mandatory = [opt[:-1] for opt in longopts if not opt[-1] == '?']
    longopts = [ ((opt[-1] == '?') and opt[:-1] or opt) for opt in longopts]
    __opts, spam = getopt.gnu_getopt(sys.argv[2:], '', longopts)
    for o,v in __opts: opts[o[2:]] = v

    check_mandatory_opts(mandatory)

# }}}

# actually, main {{{
if __name__ == "__main__":
    try:
        if len(sys.argv) < 2: die("too few arguments, see mkve(1)")
        command = sys.argv[1]
        commands = {
            "create":   command_create,
            "adopt":    command_adopt,
            "pack":     command_pack,
            "destroy":  command_destroy,
            "list":     command_list,
            "info":     command_info,
            "start":    command_start,
            "stop":     command_stop,
            "restart":  command_restart,
            "suspend":  command_suspend,
            "resume":   command_resume,
            "autostart": command_autostart,
            #
            "net-create":   command_net_create,
            "net-destroy":  command_net_destroy,
            "net-list":     command_net_list,
            "net-info":     command_net_info,
            #
            "help": command_help,
            "--help": command_help
             }
        if not commands.has_key(command):
            die("unknown command: %s, see mkve(1)" %(command))

        if len(sys.argv) > 2 \
                and sys.argv[2][:2] != '--' \
                and '--name' not in sys.argv:
                    sys.argv.insert(2, '--name')

        if command not in ('help', '--help', 'list', 'pack'):
            if os.getuid() != 0:
                die("only root can do that")

        commands[command]()

    except Exception, e:
        warn( e )
        sys.exit(1)
# }}}

# }}}

# vim:fdm=marker et sw=4 ts=4
