various improvements
This commit is contained in:
0
compute/cli/__init__.py
Normal file
0
compute/cli/__init__.py
Normal file
26
compute/cli/_create.py
Normal file
26
compute/cli/_create.py
Normal file
@ -0,0 +1,26 @@
|
||||
import argparse
|
||||
|
||||
from compute import Session
|
||||
from compute.utils import identifiers
|
||||
|
||||
|
||||
def _create_instance(session: Session, args: argparse.Namespace) -> None:
|
||||
"""
|
||||
Умолчания (достать информацию из либвирта):
|
||||
- arch
|
||||
- machine
|
||||
- emulator
|
||||
- CPU
|
||||
- cpu_vendor
|
||||
- cpu_model
|
||||
- фичи
|
||||
- max_memory
|
||||
- max_vcpus
|
||||
|
||||
(сегнерировать):
|
||||
- MAC адрес
|
||||
- boot_order = ('cdrom', 'hd')
|
||||
- title = ''
|
||||
- name = uuid.uuid4().hex
|
||||
"""
|
||||
print(args)
|
319
compute/cli/control.py
Normal file
319
compute/cli/control.py
Normal file
@ -0,0 +1,319 @@
|
||||
"""Command line interface."""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
import libvirt
|
||||
|
||||
from compute import __version__
|
||||
from compute.exceptions import (
|
||||
ComputeServiceError,
|
||||
GuestAgentTimeoutExceededError,
|
||||
)
|
||||
from compute.instance import GuestAgent
|
||||
from compute.session import Session
|
||||
|
||||
from ._create import _create_instance
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log_levels = logging.getLevelNamesMapping()
|
||||
|
||||
env_log_level = os.getenv('CMP_LOG')
|
||||
|
||||
libvirt.registerErrorHandler(
|
||||
lambda userdata, err: None, # noqa: ARG005
|
||||
ctx=None,
|
||||
)
|
||||
|
||||
|
||||
class Table:
|
||||
"""Minimalistic text table constructor."""
|
||||
|
||||
def __init__(self, whitespace: str | None = None):
|
||||
"""Initialise Table."""
|
||||
self.whitespace = whitespace or '\t'
|
||||
self.header = []
|
||||
self._rows = []
|
||||
self._table = ''
|
||||
|
||||
def row(self, row: list) -> None:
|
||||
"""Add table row."""
|
||||
self._rows.append([str(col) for col in row])
|
||||
|
||||
def rows(self, rows: list[list]) -> None:
|
||||
"""Add multiple rows."""
|
||||
for row in rows:
|
||||
self.row(row)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Build table and return."""
|
||||
widths = [max(map(len, col)) for col in zip(*self._rows, strict=True)]
|
||||
self._rows.insert(0, [str(h).upper() for h in self.header])
|
||||
for row in self._rows:
|
||||
self._table += self.whitespace.join(
|
||||
(
|
||||
val.ljust(width)
|
||||
for val, width in zip(row, widths, strict=True)
|
||||
)
|
||||
)
|
||||
self._table += '\n'
|
||||
return self._table.strip()
|
||||
|
||||
|
||||
def _list_instances(session: Session) -> None:
|
||||
table = Table()
|
||||
table.header = ['NAME', 'STATE']
|
||||
for instance in session.list_instances():
|
||||
table.row(
|
||||
[
|
||||
instance.name,
|
||||
instance.status,
|
||||
]
|
||||
)
|
||||
print(table)
|
||||
sys.exit()
|
||||
|
||||
|
||||
def _exec_guest_agent_command(
|
||||
session: Session, args: argparse.Namespace
|
||||
) -> None:
|
||||
instance = session.get_instance(args.instance)
|
||||
ga = GuestAgent(instance.domain, timeout=args.timeout)
|
||||
arguments = args.arguments.copy()
|
||||
if len(arguments) > 1:
|
||||
arguments = [shlex.join(arguments)]
|
||||
if not args.no_cmd_string:
|
||||
arguments.insert(0, '-c')
|
||||
stdin = None
|
||||
if not sys.stdin.isatty():
|
||||
stdin = sys.stdin.read()
|
||||
try:
|
||||
output = ga.guest_exec(
|
||||
path=args.shell,
|
||||
args=arguments,
|
||||
env=args.env,
|
||||
stdin=stdin,
|
||||
capture_output=True,
|
||||
decode_output=True,
|
||||
poll=True,
|
||||
)
|
||||
except GuestAgentTimeoutExceededError as e:
|
||||
sys.exit(
|
||||
f'{e}. NOTE: command may still running in guest, '
|
||||
f'PID={ga.last_pid}'
|
||||
)
|
||||
if output.stderr:
|
||||
print(output.stderr.strip(), file=sys.stderr)
|
||||
if output.stdout:
|
||||
print(output.stdout.strip(), file=sys.stdout)
|
||||
sys.exit(output.exitcode)
|
||||
|
||||
|
||||
def main(session: Session, args: argparse.Namespace) -> None:
|
||||
"""Perform actions."""
|
||||
match args.command:
|
||||
case 'create':
|
||||
_create_instance(session, args)
|
||||
case 'exec':
|
||||
_exec_guest_agent_command(session, args)
|
||||
case 'ls':
|
||||
_list_instances(session)
|
||||
case 'start':
|
||||
instance = session.get_instance(args.instance)
|
||||
instance.start()
|
||||
case 'shutdown':
|
||||
instance = session.get_instance(args.instance)
|
||||
instance.shutdown(args.method)
|
||||
case 'reboot':
|
||||
instance = session.get_instance(args.instance)
|
||||
instance.reboot()
|
||||
case 'reset':
|
||||
instance = session.get_instance(args.instance)
|
||||
instance.reset()
|
||||
case 'status':
|
||||
instance = session.get_instance(args.instance)
|
||||
print(instance.status)
|
||||
case 'setvcpus':
|
||||
instance = session.get_instance(args.instance)
|
||||
instance.set_vcpus(args.nvcpus, live=True)
|
||||
|
||||
|
||||
def cli() -> None: # noqa: PLR0915
|
||||
"""Parse command line arguments."""
|
||||
root = argparse.ArgumentParser(
|
||||
prog='compute',
|
||||
description='manage compute instances and storage volumes.',
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
root.add_argument(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='enable verbose mode',
|
||||
)
|
||||
root.add_argument(
|
||||
'-c',
|
||||
'--connect',
|
||||
metavar='URI',
|
||||
default='qemu:///system',
|
||||
help='libvirt connection URI',
|
||||
)
|
||||
root.add_argument(
|
||||
'-l',
|
||||
'--log-level',
|
||||
metavar='LEVEL',
|
||||
choices=log_levels,
|
||||
help='log level [envvar: CMP_LOG]',
|
||||
)
|
||||
root.add_argument(
|
||||
'-V',
|
||||
'--version',
|
||||
action='version',
|
||||
version=__version__,
|
||||
)
|
||||
subparsers = root.add_subparsers(dest='command', metavar='COMMAND')
|
||||
|
||||
# create command
|
||||
create = subparsers.add_parser('create', help='create compute instance')
|
||||
create.add_argument('image', nargs='?')
|
||||
create.add_argument('--name', help='instance name, used as ID')
|
||||
create.add_argument('--title', help='human-understandable instance title')
|
||||
create.add_argument('--desc', default='', help='instance description')
|
||||
create.add_argument('--memory', type=int, help='memory in MiB')
|
||||
create.add_argument('--max-memory', type=int, help='max memory in MiB')
|
||||
create.add_argument('--vcpus', type=int)
|
||||
create.add_argument('--max-vcpus', type=int)
|
||||
create.add_argument('--cpu-vendor')
|
||||
create.add_argument('--cpu-model')
|
||||
create.add_argument(
|
||||
'--cpu-emulation-mode',
|
||||
choices=['host-passthrough', 'host-model', 'custom'],
|
||||
default='host-passthrough',
|
||||
)
|
||||
create.add_argument('--cpu-features')
|
||||
create.add_argument('--cpu-topology')
|
||||
create.add_argument('--mahine')
|
||||
create.add_argument('--emulator')
|
||||
create.add_argument('--arch')
|
||||
create.add_argument('--boot-order')
|
||||
create.add_argument('--volume')
|
||||
create.add_argument('-f', '--file', help='create instance from YAML')
|
||||
|
||||
# exec subcommand
|
||||
execute = subparsers.add_parser(
|
||||
'exec',
|
||||
help='execute command in guest via guest agent',
|
||||
description=(
|
||||
'NOTE: any argument after instance name will be passed into '
|
||||
'guest as shell command.'
|
||||
),
|
||||
)
|
||||
execute.add_argument('instance')
|
||||
execute.add_argument('arguments', nargs=argparse.REMAINDER)
|
||||
execute.add_argument(
|
||||
'-t',
|
||||
'--timeout',
|
||||
type=int,
|
||||
default=60,
|
||||
help=(
|
||||
'waiting time in seconds for a command to be executed '
|
||||
'in guest, 60 sec by default'
|
||||
),
|
||||
)
|
||||
execute.add_argument(
|
||||
'-s',
|
||||
'--shell',
|
||||
default='/bin/sh',
|
||||
help='path to executable in guest, /bin/sh by default',
|
||||
)
|
||||
execute.add_argument(
|
||||
'-e',
|
||||
'--env',
|
||||
type=str,
|
||||
nargs='?',
|
||||
action='append',
|
||||
help='environment variables to pass to executable in guest',
|
||||
)
|
||||
execute.add_argument(
|
||||
'-n',
|
||||
'--no-cmd-string',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
"do not append '-c' option to arguments list, suitable "
|
||||
'for non-shell executables and other specific cases.'
|
||||
),
|
||||
)
|
||||
|
||||
# ls subcommand
|
||||
listall = subparsers.add_parser('ls', help='list instances')
|
||||
listall.add_argument(
|
||||
'-a',
|
||||
'--all',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='list all instances including inactive',
|
||||
)
|
||||
|
||||
# start subcommand
|
||||
start = subparsers.add_parser('start', help='start instance')
|
||||
start.add_argument('instance')
|
||||
|
||||
# shutdown subcommand
|
||||
shutdown = subparsers.add_parser('shutdown', help='shutdown instance')
|
||||
shutdown.add_argument('instance')
|
||||
shutdown.add_argument(
|
||||
'-m',
|
||||
'--method',
|
||||
choices=['soft', 'normal', 'hard', 'unsafe'],
|
||||
default='normal',
|
||||
help='use shutdown method',
|
||||
)
|
||||
|
||||
# reboot subcommand
|
||||
reboot = subparsers.add_parser('reboot', help='reboot instance')
|
||||
reboot.add_argument('instance')
|
||||
|
||||
# reset subcommand
|
||||
reset = subparsers.add_parser('reset', help='reset instance')
|
||||
reset.add_argument('instance')
|
||||
|
||||
# status subcommand
|
||||
status = subparsers.add_parser('status', help='display instance status')
|
||||
status.add_argument('instance')
|
||||
|
||||
# setvcpus subcommand
|
||||
setvcpus = subparsers.add_parser('setvcpus', help='set vCPU number')
|
||||
setvcpus.add_argument('instance')
|
||||
setvcpus.add_argument('nvcpus', type=int)
|
||||
|
||||
# Run parser
|
||||
args = root.parse_args()
|
||||
if args.command is None:
|
||||
root.print_help()
|
||||
sys.exit()
|
||||
|
||||
# Set logging level
|
||||
log_level = args.log_level or env_log_level
|
||||
if log_level in log_levels:
|
||||
logging.basicConfig(level=log_levels[log_level])
|
||||
|
||||
# Perform actions
|
||||
try:
|
||||
with Session(args.connect) as session:
|
||||
main(session, args)
|
||||
except ComputeServiceError as e:
|
||||
sys.exit(f'error: {e}')
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
sys.exit()
|
||||
except Exception as e: # noqa: BLE001
|
||||
sys.exit(f'unexpected error {type(e)}: {e}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
Reference in New Issue
Block a user