#!/usr/bin/python3.13 -tt
#
# Script to set up a Xen guest and kick off an install
#
# Copyright 2006 Novell, Inc.
# Author: Charles Coffing <ccoffing@novell.com>
#
# This software may be freely redistributed under the terms of the GNU
# general public license.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 

from __future__ import print_function
import optparse
import os
import sys

#os.environ['PYCHECKER'] = '--stdlib'
#import pychecker.checker

#import vminstall.caps
import vmdisks.disks
import vminstall.VMDefaults
import vminstall.xml
import vminstall.nics
import vminstall.passthrough
import vminstall.cpuid
import vminstall.options
import vminstall.job
import vminstall.log
import vminstall.graphics_adapter
from vminstall.exceptions import *

def showOsTypesCB(option, opt_str, value, parser):
    keys = list(vminstall.VMDefaults.VMDefaults.keys())
    keys.sort()
    for os_type in keys:
        print(os_type, file=sys.stdout)
    sys.exit(0)

def setVmSettings(option, opt_str, value, parser):
    vminstall.xml.merge(value, parser.values)

def setDisk(option, opt_str, value, parser):
    blocks=None
    if options.os_type is not None:
        blocks = vminstall.VMDefaults.VMDefaults[options.os_type]._disk_size() * 1024*1024*2
    disk = vmdisks.disks.parse_string(value, False, blocks)
    if parser.values.disks:
        parser.values.disks.extend([disk])
    else:
        parser.values.disks = [disk]

def setNic(option, opt_str, value, parser):
    nic = vminstall.nics.parse_string(value)
    if parser.values.nics:
        parser.values.nics.extend([nic])
    else:
        parser.values.nics = [nic]

def setHostDevice(option, opt_str, value, parser):
    parser.values.passthrough = True
    hd = vminstall.passthrough.parse_string(value)
    if parser.values.host_devices:
        parser.values.host_devices.extend(hd)
    else:
        parser.values.host_devices = hd

def setCPUID(option, opt_str, value, parser):
    cpuid = vminstall.cpuid.parse_string(value)
    for c in cpuid:
        if parser.values.cpuid:
            parser.values.cpuid.extend([c])
        else:
            parser.values.cpuid = [c]

def setCPUCHECK(option, opt_str, value, parser):
    cpu_check = vminstall.cpuid.parse_string(value)
    for c in cpuid:
        if parser.values.cpu_check:
            parser.values.cpu_check.extend([c])
        else:
            parser.values.cpu_check = [c]

def setAdvanced(option, opt_str, value, parser):
    vminstall.advanced.parse_string(parser.values, value)

def setConnection(option, opt_str, value, parser):
    connection = vminstall.libvirt_hypervisor.parse_string(value)
    if connection:
        parser.values.connect = connection

def setPXE(option, opt_str, value, parser):
    parser.values.pxe_boot = True
    if len(parser.rargs) and not parser.rargs[0].startswith('-'):
        address = parser.rargs[0]
        parts = address.split(".")
        if len(parts) == 4:
            for item in parts:
                try:
                    if not 0 <= int(item) <= 255:
                        return
                except ValueError as e:
                    print('%s: %s' % (vminstall.msg.error, str(e)), file=sys.stderr)
                    sys.exit(-1)
            parser.values.pxe_addr = address

def populate_options(options):
    parser = optparse.OptionParser(description="""Defines a Xen virtual machine and installs its operating system.  Consult the man page for complete usage and examples.""")

    # Check for an os_type first so we can default to correct disk settings
    found = False
    for o in sys.argv:
        if found:
            if o in vminstall.VMDefaults.VMDefaults:
                options.os_type = o
            break
        if o == '-o' or o == '--os-type':
            found = True

    parser.add_option('-c', '--vcpus', type='int', dest='vcpus',
                      help="number of virtual CPUs")
    parser.add_option('', '--cpuid', type='string', action='callback', callback=setCPUID,
                      help="present the fully virtualized VM with cpuid information for " \
                      "processor compatibility: CPUID:[REG=],[REG=],...")
    parser.add_option('', '--cpu-check', type='string', action='callback', callback=setCPUCHECK,
                      help="set cpuid consistency checks before allowing the host to run a fully " \
                      "virtualized VM: CPUID:[REG=],[REG=],...")
    parser.add_option('-d', '--disk', type='string', action='callback', callback=setDisk,
                      help="defines a virtual disk: PDEV,VDEV[,TYPE[,MODE[,MB[,OPTIONS]]]]")
    parser.add_option('-g', '--graphics', type='string',
                      help="graphics hardware; one of 'cirrus', 'none', 'para', 'vesa'")
    parser.add_option('', '--graphics-viewer', type='string',
                      help="graphics viewer; one of 'text', 'sdl', 'vnc', or 'virt-viewer'")
    parser.add_option('', '--sound', type='string',
                      help="sound hardware; one of 'ac97', 'all', 'es1370', 'ich6', 'none', 'sb16'")
    parser.add_option('-m', '--memory', type='int', dest='memoryMB', metavar='MEGABYTES',
                      help="initial MB of RAM for VM")
    parser.add_option('-M', '--max-memory', type='int', dest='max_memoryMB', metavar='MEGABYTES',
                      help="maximum MB of RAM for VM")
    parser.add_option('-n', '--name', type='string', dest='name',
                      help="name of the VM")
    parser.add_option('', '--nic', type='string', action='callback', callback=setNic,
                      help="defines a virtual network adapter: bridge=BRIDGE,mac=MAC,model=MODEL")
    parser.add_option('', '--passthrough', action='callback', callback=setHostDevice,
                      help="allows direct access from the VM to a host device: " \
                      "pci=[DOMAIN:BUS:SLOT.FUNC[:MANAGED]],usb=[BUS:DEVICE:VENDOR_ID:PRODUCT_ID[:MANAGED]]")
    parser.add_option('-u', '--uuid', type='string', dest='uuid',
                      help="universally unique identifer of VM")
    parser.add_option('-v', '--para-virt', action='store_false', dest='full_virt',
                      help="paravirtualize the VM")
    parser.add_option('-V', '--full-virt', action='store_true', dest='full_virt',
                      help="fully virtualize the VM")
    parser.add_option('', '--vnc-password', type='string', dest='vnc_password',
                      help="password to protect VNC access (default none)")
    parser.add_option('', '--vnc-port', type='int', dest='vncport',
                      help="port VNC server should listen on")
    parser.add_option('', '--keymap', type='string', dest='keymap',
                      help="keyboard translation map file; See /usr/share/xen/qemu/keymaps")
    parser.add_option('', '--vm-settings', type='string',
                      action='callback', callback=setVmSettings, help="XML file with VM settings")

    parser.add_option('-o', '--os-type', type='string', dest='os_type',
                      help="type of OS")
    parser.add_option('', '--os-settings', type='string', dest='os_settings',
                      help="OS-specific automated install file")
    parser.add_option('-x', '--extra-args', type='string', dest='extra_args',
                      help="additional arguments for the paravirtualized kernel")
    parser.add_option('-s', '--source', type='string', dest='source',
                      help="URL of installation source for paravirtualized VM")
    parser.add_option('-p', '--pxe-boot', action='callback', callback=setPXE,
                      help="Specify PXE booting for the VM: [PXE Server IP]")
    parser.add_option('', '--config-dir', type='string',
                      help="Specify the directory to store VM configuration files")
    parser.add_option('', '--upgrade', action="store_true", dest="upgrade",
                      help="Specify upgrading the paravirtualized VM")
    #parser.add_option('', '--connect', type='string', action='callback', callback=setConnection,
    #                  help="Specify a Uniform Resource Locator to connect to a remote host: --connect xen:///")

    parser.add_option('', '--background', action='store_true', dest='background',
                      help="run as background job; do not prompt")
    parser.add_option('', '--no-autoconsole', action='store_true', dest='noautoconsole',
                      help="don't automatically connect to VM console; user must connect manually")
    parser.add_option('', '--ui', type='string',
                      help="user interface; one of 'text', 'gtk', or 'yast'")
    parser.add_option('-O', '--os-types', action='callback', callback=showOsTypesCB,
                      help="list valid OS types, then quit")
    parser.add_option('', '--advanced', type='string', action='callback', callback=setAdvanced,
                      help="Set an advanced option: option=value,[option=value]...")

    parser.add_option('', '--no-restart', action='store_true', dest='norestart',
                      help="don't restart VM after installation finishes")
    parser.add_option('-e', '--existing-os', action='store_true', dest='existing_os',
                      help="Generate a configuration file based on an existing disk or " \
                      "disk image with an installed operating system")
    parser.add_option('', '--no-install', action='store_true', dest='no_install',
                      help="Generate an installation configuration file; do not perform the install")
    parser.add_option('', '--verify-disk-bootable', action='store_true', dest='verify_disk_bootable',
                      help="Verify the first paravirtualized disk is bootable but don't extract the " \
                            "kernel/initrd. Use grub.xen to boot the paravirtualized VM")

    parser.add_option('', '--debug', action='store_true', dest='debug',
                      help="print debugging information")
    parser.add_option('', '--preserve-on-error', action='store_true', dest='preserve_on_error',
                      help="On error, do not delete temporary, configuration and image files")
    parser.add_option('', '--use-xl', action='store_true', dest='use_xl',
                      help="Xen specific option. Use the xl command to start the VM; " \
                      "Only vncviewer is supported")

    # Manually copy option keys/values into optparse's values, rather
    # than relying on optparse.parser.add_option, so my options class
    # can be de-coupled from optparse.
    values = optparse.Values()
    for item in list(options.items()):
        values.ensure_value(item[0], item[1])
    (parsed_options, args) = parser.parse_args(values=values)
    vminstall.options.shallow_copy(parsed_options, options)

if __name__ == "__main__":
    # OPTIONS are things that have the user has decided; DEFAULTS can change
    # over time to reflect the changing OPTIONS.  When finally accepted,
    # DEFAULTS are used.  As a rule of thumb, write to OPTIONS but read from
    # DEFAULTS.
    #
    # In other words:
    #
    # OPTIONS = input(XML, command line)
    # while not done:
    #     DEFAULTS = OPTIONS + calculations
    #     user is presented with DEFAULTS
    #     OPTIONS += input(user)
    # vm = VM(DEFAULTS)
    #
    # The basic flow is the same, regardless of CLI vs. GUI.

    # vm-install doesn't work for non-root users currently
    # (see bnc#574603)
    #
    if os.getuid() != 0:
        print('%s: %s' % (vminstall.msg.error, vminstall.msg.must_be_root), file=sys.stderr)
        sys.exit(-1)
    #
    # Hypervisor Check
    #
    # Load appropriate paths based on the type of hypervisor
    #
    try:
        if not vminstall.caps.is_xen() and not vminstall.caps.is_kvm() and not vminstall.caps.is_qemu():
            raise HypervisorError(HypervisorError.E_NO_HYPERVISOR)
    except Error as e:
        print('%s: %s' % (vminstall.msg.error, str(e)), file=sys.stderr)
        sys.exit(-1)

    #
    # Declare any global data that is hypervisor-based.
    #
    vminstall.graphics_adapter.graphics_adapter = vminstall.graphics_adapter.KVMGraphicsAdapter()

    #
    # FIXME:
    # GUI may have other threads going.  Tear down.
    # VM may still be installing; tear down.

    # Load up the default options
    options = vminstall.options.Options()
    defaults = vminstall.options.Options()
    try:
        populate_options(options)               # Fill in command line options.
        if vminstall.caps.is_xen():
            vminstall.paths.load_xen_paths(options)
        elif vminstall.caps.is_kvm():
            vminstall.paths.load_kvm_paths()
        else:
            vminstall.paths.load_qemu_paths()
        defaults.connect = vminstall.options.calculate(options, defaults)
        job = vminstall.job.Job(background=defaults.background, upgrade=defaults.upgrade, debug=defaults.debug)

        # Create a console instance based on the type of hypervisor running.
        if vminstall.hypervisor.connection.use_libvirt is False:
            if options.use_xl:
                from vminstall.xl_console import XlConsole
                vminstall.console.console = XlConsole()
            else:
                from vminstall.xen_console import XenConsole
                vminstall.console.console = XenConsole()
        else:
            from vminstall.libvirt_console import LibVirtConsole
            vminstall.console.console = LibVirtConsole()

        interface = None
        if defaults.ui == 'yast':
            if defaults.background:
                raise
            # FIXME
        elif defaults.ui == 'gtk':
            if defaults.background or 'DISPLAY' not in os.environ:
                raise
            try:
                vminstall.log.debug("Selecting GTK as the interface")
                import vminstall.gtk as interface
            except Exception as e:
                vminstall.log.error("Install package 'python-gtk' for a graphical UI.")
        elif defaults.background or defaults.ui == 'text':
            import vminstall.text as interface
        if interface is None:
            if 'DISPLAY' in os.environ and os.environ['DISPLAY']:
                try:
                    import vminstall.gtk as interface
                except Exception as e:
                    pass
        if interface is None:
            import vminstall.text as interface

        interface.run(job, options, defaults)
        r = 0
    except KeyboardInterrupt:
        print('%s\n' % vminstall.msg.aborted, file=sys.stderr)
        r = 1
    except Error as e:
        print('%s: %s' % (vminstall.msg.error, str(e)), file=sys.stderr)
        r = e.exit_code()
    except Exception as e:
        print('%s: %s' % (vminstall.msg.error, str(e)), file=sys.stderr)
        r = 1

    # FIXME:
    # GUI may have other threads going.  Tear down.
    # VM may still be installing; tear down.

    sys.exit(r)
