Oneshot: cleanup CLI, initial networking support
This commit is contained in:
parent
618fecb73f
commit
3e69fb275f
2 changed files with 119 additions and 48 deletions
|
@ -1,65 +1,123 @@
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from uncloud.vmm import VMM
|
from uncloud.vmm import VMM
|
||||||
|
from uncloud.host.virtualmachine import update_radvd_conf, create_vxlan_br_tap
|
||||||
|
|
||||||
from . import virtualmachine, logger
|
from . import virtualmachine, logger
|
||||||
|
|
||||||
|
###
|
||||||
|
# Argument parser loaded by scripts/uncloud.
|
||||||
arg_parser = argparse.ArgumentParser('oneshot', add_help=False)
|
arg_parser = argparse.ArgumentParser('oneshot', add_help=False)
|
||||||
arg_parser.add_argument('--workdir', default=Path.home())
|
|
||||||
arg_parser.add_argument('--list-vms', action='store_true')
|
|
||||||
arg_parser.add_argument('--start-vm', action='store_true')
|
|
||||||
arg_parser.add_argument('--stop-vm', action='store_true')
|
|
||||||
arg_parser.add_argument('--name')
|
|
||||||
arg_parser.add_argument('--image')
|
|
||||||
arg_parser.add_argument('--uuid')
|
|
||||||
arg_parser.add_argument('--mac')
|
|
||||||
arg_parser.add_argument('--get_vm_status', action='store_true')
|
|
||||||
arg_parser.add_argument('--setup-network')
|
|
||||||
|
|
||||||
def setup_network():
|
# Actions.
|
||||||
print("Not implemented yet.")
|
arg_parser.add_argument('--list', action='store_true',
|
||||||
exit(1)
|
help='list UUID and name of running VMs')
|
||||||
|
arg_parser.add_argument('--start', nargs=3,
|
||||||
|
metavar=('IMAGE', 'UPSTREAM_INTERFACE', 'NETWORK'),
|
||||||
|
help='start a VM using the OS IMAGE (full path), configuring networking on NETWORK IPv6 prefix')
|
||||||
|
arg_parser.add_argument('--stop', metavar='UUID',
|
||||||
|
help='stop a VM')
|
||||||
|
arg_parser.add_argument('--get-status', metavar='UUID',
|
||||||
|
help='return the status of the VM')
|
||||||
|
arg_parser.add_argument('--get-vnc', metavar='UUID',
|
||||||
|
help='return the path of the VNC socket of the VM')
|
||||||
|
arg_parser.add_argument('--reconfigure-radvd', metavar='NETWORK',
|
||||||
|
help='regenerate and reload RADVD configuration for NETWORK IPv6 prefix')
|
||||||
|
|
||||||
def require_with(arguments, required, mode):
|
# Arguments.
|
||||||
if not arguments[required]:
|
arg_parser.add_argument('--workdir', default=Path.home(),
|
||||||
print("--{} is required with the {} flag. Exiting.".format(required, mode))
|
help='Working directory, defaulting to $HOME')
|
||||||
exit(1)
|
arg_parser.add_argument('--mac',
|
||||||
|
help='MAC address of the VM to create (--start)')
|
||||||
|
arg_parser.add_argument('--memory', type=int,
|
||||||
|
help='Memory (MB) to allocate (--start)')
|
||||||
|
arg_parser.add_argument('--cores', type=int,
|
||||||
|
help='Number of cores to allocate (--start)')
|
||||||
|
arg_parser.add_argument('--threads', type=int,
|
||||||
|
help='Number of threads to allocate (--start)')
|
||||||
|
arg_parser.add_argument('--image-format', choices=['raw', 'qcow2'],
|
||||||
|
help='Format of OS image (--start)')
|
||||||
|
arg_parser.add_argument('--accel', choices=['kvm', 'tcg'], default='tcg',
|
||||||
|
help='QEMU acceleration to use (--start)')
|
||||||
|
arg_parser.add_argument('--upstream-interface', default='eth0',
|
||||||
|
help='Name of upstream interface (--start)')
|
||||||
|
|
||||||
|
###
|
||||||
|
# Helpers.
|
||||||
|
|
||||||
|
# XXX: check if it is possible to use the type returned by ETCD queries.
|
||||||
|
class UncloudEntryWrapper:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def value(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def status_line(vm):
|
||||||
|
return "VM: {} {} {}".format(vm.get_uuid(), vm.get_name(), vm.get_status())
|
||||||
|
|
||||||
|
###
|
||||||
|
# Entrypoint.
|
||||||
|
|
||||||
def main(arguments):
|
def main(arguments):
|
||||||
# Initialize VMM
|
# Initialize VMM.
|
||||||
workdir = arguments['workdir']
|
workdir = arguments['workdir']
|
||||||
vmm = VMM(vmm_backend=workdir)
|
vmm = VMM(vmm_backend=workdir)
|
||||||
|
|
||||||
# Initialize workdir directory.
|
# Harcoded debug values.
|
||||||
# TODO: copy ifup, ifdown.
|
net_id = 0
|
||||||
|
|
||||||
# Build VM configuration.
|
# Build VM configuration.
|
||||||
vm_config = {}
|
vm_config = {}
|
||||||
for spec in ['uuid', 'memory', 'cores', 'threads', 'image', 'image_format', 'name']:
|
vm_options = [
|
||||||
if arguments.get(spec):
|
'mac', 'memory', 'cores', 'threads', 'image', 'image_format',
|
||||||
vm_config[spec] = arguments[spec]
|
'--upstream_interface', 'upstream_interface', 'network'
|
||||||
|
]
|
||||||
|
for option in vm_options:
|
||||||
|
if arguments.get(option):
|
||||||
|
vm_config[option] = arguments[option]
|
||||||
|
|
||||||
|
vm_config['net_id'] = net_id
|
||||||
|
|
||||||
# Execute requested VM action.
|
# Execute requested VM action.
|
||||||
|
if arguments['reconfigure_radvd']:
|
||||||
|
# TODO: check that RADVD is available.
|
||||||
|
prefix = arguments['reconfigure_radvd']
|
||||||
|
network = UncloudEntryWrapper({
|
||||||
|
'id': net_id,
|
||||||
|
'ipv6': prefix
|
||||||
|
})
|
||||||
|
|
||||||
|
# Make use of uncloud.host.virtualmachine for network configuration.
|
||||||
|
update_radvd_conf([network])
|
||||||
|
elif arguments['start']:
|
||||||
|
# Extract from --start positional arguments. Quite fragile.
|
||||||
|
vm_config['image'] = arguments['start'][0]
|
||||||
|
vm_config['network'] = arguments['start'][1]
|
||||||
|
vm_config['upstream_interface'] = arguments['start'][2]
|
||||||
|
|
||||||
|
vm_config['tap_interface'] = "uc{}".format(len(vmm.discover()))
|
||||||
vm = virtualmachine.VM(vmm, vm_config)
|
vm = virtualmachine.VM(vmm, vm_config)
|
||||||
if arguments['setup_network']:
|
|
||||||
setup_network()
|
|
||||||
elif arguments['start_vm']:
|
|
||||||
require_with(arguments, 'image', 'start_vm')
|
|
||||||
vm.start()
|
vm.start()
|
||||||
logger.info("Created VM {}".format(vm.get_uuid))
|
elif arguments['stop']:
|
||||||
elif arguments['get_vm_status']:
|
vm = virtualmachine.VM(vmm, {'uuid': arguments['stop']})
|
||||||
require_with(arguments, 'uuid', 'get_vm_status')
|
vm = virtualmachine.VM(vmm, vm_config)
|
||||||
print("VM: {} {} {}".format(vm.get_uuid(), vm.get_name(), vm.get_status()))
|
|
||||||
elif arguments['stop_vm']:
|
|
||||||
require_with(arguments, 'uuid', 'stop_vm')
|
|
||||||
vm.stop()
|
vm.stop()
|
||||||
elif arguments['list_vms']:
|
elif arguments['get_status']:
|
||||||
discovered = vmm.discover()
|
vm = virtualmachine.VM(vmm, {'uuid': arguments['get_status']})
|
||||||
print("Found {} VMs.".format(len(discovered)))
|
print(status_line(vm))
|
||||||
for uuid in vmm.discover():
|
elif arguments['get_vnc']:
|
||||||
vmi = virtualmachine.VM(vmm, {'uuid': uuid})
|
vm = virtualmachine.VM(vmm, {'uuid': arguments['get_vnc']})
|
||||||
print("VM: {} {} {}".format(vmi.get_uuid, vmi.get_name, vmi.get_status))
|
print(vm.get_vnc_addr())
|
||||||
|
elif arguments['list']:
|
||||||
|
vms = vmm.discover()
|
||||||
|
print("Found {} VMs.".format(len(vms)))
|
||||||
|
for uuid in vms:
|
||||||
|
vm = virtualmachine.VM(vmm, {'uuid': uuid})
|
||||||
|
print(status_line(vm))
|
||||||
else:
|
else:
|
||||||
print('No action requested. Exiting.')
|
print('Please specify an action: --start, --stop, --list,\
|
||||||
|
--get-status, --get-vnc, --reconfigure-radvd')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from uncloud.host.virtualmachine import create_vxlan_br_tap
|
||||||
from uncloud.oneshot import logger
|
from uncloud.oneshot import logger
|
||||||
|
|
||||||
class VM(object):
|
class VM(object):
|
||||||
|
@ -9,29 +10,36 @@ class VM(object):
|
||||||
self.vmm = vmm
|
self.vmm = vmm
|
||||||
|
|
||||||
# Extract VM specs/metadata from configuration.
|
# Extract VM specs/metadata from configuration.
|
||||||
self.name = config.get('name')
|
self.name = config.get('name', 'no-name')
|
||||||
self.memory = config.get('memory', 1024)
|
self.memory = config.get('memory', 1024)
|
||||||
self.cores = config.get('cores', 1)
|
self.cores = config.get('cores', 1)
|
||||||
self.threads = config.get('threads', 1)
|
self.threads = config.get('threads', 1)
|
||||||
self.image_format = config.get('image_format', 'qcow2')
|
self.image_format = config.get('image_format', 'qcow2')
|
||||||
self.image = config.get('image')
|
self.image = config.get('image')
|
||||||
self.uuid = config.get('uuid', uuid.uuid4())
|
self.uuid = config.get('uuid', str(uuid.uuid4()))
|
||||||
self.mac = config.get('mac', 'spuik')
|
self.mac = config.get('mac')
|
||||||
|
|
||||||
|
self.net_id = config.get('net_id', 0)
|
||||||
|
self.upstream_interface = config.get('upstream_interface', 'eth0')
|
||||||
|
self.tap_interface = config.get('tap_interface', 'uc0')
|
||||||
|
self.network = config.get('network')
|
||||||
|
|
||||||
# Harcoded & generated values.
|
# Harcoded & generated values.
|
||||||
self.image_format='qcow2'
|
|
||||||
self.accel = 'kvm'
|
self.accel = 'kvm'
|
||||||
|
|
||||||
def get_qemu_args(self):
|
def get_qemu_args(self):
|
||||||
command = (
|
command = (
|
||||||
"-uuid {uuid} -name {name}"
|
"-uuid {uuid} -name {name} -machine pc,accel={accel}"
|
||||||
" -drive file={image},format={image_format},if=virtio"
|
" -drive file={image},format={image_format},if=virtio"
|
||||||
" -device virtio-rng-pci"
|
" -device virtio-rng-pci"
|
||||||
" -m {memory} -smp cores={cores},threads={threads}"
|
" -m {memory} -smp cores={cores},threads={threads}"
|
||||||
|
" -netdev tap,id=vmnet{net_id},ifname={tap},script=no,downscript=no"
|
||||||
|
" -device virtio-net-pci,netdev=vmnet{net_id},mac={mac}"
|
||||||
).format(
|
).format(
|
||||||
uuid=self.uuid, name=self.name,
|
uuid=self.uuid, name=self.name, accel=self.accel,
|
||||||
image=self.image, image_format=self.image_format,
|
image=self.image, image_format=self.image_format,
|
||||||
memory=self.memory, cores=self.cores, threads=self.threads,
|
memory=self.memory, cores=self.cores, threads=self.threads,
|
||||||
|
net_id=self.net_id, tap=self.tap_interface, mac=self.mac
|
||||||
)
|
)
|
||||||
|
|
||||||
return command.split(" ")
|
return command.split(" ")
|
||||||
|
@ -41,9 +49,14 @@ class VM(object):
|
||||||
if not os.path.isfile(self.image):
|
if not os.path.isfile(self.image):
|
||||||
logger.error("Image {} does not exist. Aborting.".format(self.image))
|
logger.error("Image {} does not exist. Aborting.".format(self.image))
|
||||||
|
|
||||||
|
# Create Bridge, VXLAN and tap interface for VM.
|
||||||
|
create_vxlan_br_tap(
|
||||||
|
self.net_id, self.upstream_interface, self.tap_interface, self.network
|
||||||
|
)
|
||||||
|
|
||||||
# Generate config for and run QEMU.
|
# Generate config for and run QEMU.
|
||||||
qemu_args = self.get_qemu_args()
|
qemu_args = self.get_qemu_args()
|
||||||
logger.warning("QEMU args for VM {}: {}".format(self.uuid, qemu_args))
|
logger.debug("QEMU args for VM {}: {}".format(self.uuid, qemu_args))
|
||||||
self.vmm.start(
|
self.vmm.start(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
migration=False,
|
migration=False,
|
||||||
|
|
Loading…
Reference in a new issue