|
|
|
@ -46,7 +46,7 @@ myhostname = socket.gethostname().split('.')[0]
|
|
|
|
|
zk_host = ''
|
|
|
|
|
|
|
|
|
|
default_store_data = {
|
|
|
|
|
'cfgfile': '/etc/pvc/pvcapid.yaml' # pvc/api/listen_address, pvc/api/listen_port
|
|
|
|
|
'cfgfile': '/etc/pvc/pvcapid.yaml'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -67,7 +67,7 @@ def read_from_yaml(cfgfile):
|
|
|
|
|
api_key = api_config['pvc']['api']['authentication']['tokens'][0]['token']
|
|
|
|
|
else:
|
|
|
|
|
api_key = 'N/A'
|
|
|
|
|
return host, port, scheme, api_key
|
|
|
|
|
return cfgfile, host, port, scheme, api_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_config(store_data, cluster=None):
|
|
|
|
@ -84,7 +84,7 @@ def get_config(store_data, cluster=None):
|
|
|
|
|
# This is a reference to an API configuration; grab the details from its listen address
|
|
|
|
|
cfgfile = cluster_details.get('cfgfile')
|
|
|
|
|
if os.path.isfile(cfgfile):
|
|
|
|
|
host, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
description, host, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
else:
|
|
|
|
|
return {'badcfg': True}
|
|
|
|
|
# Handle an all-wildcard address
|
|
|
|
@ -92,6 +92,7 @@ def get_config(store_data, cluster=None):
|
|
|
|
|
host = '127.0.0.1'
|
|
|
|
|
else:
|
|
|
|
|
# This is a static configuration, get the raw details
|
|
|
|
|
description = cluster_details['description']
|
|
|
|
|
host = cluster_details['host']
|
|
|
|
|
port = cluster_details['port']
|
|
|
|
|
scheme = cluster_details['scheme']
|
|
|
|
@ -100,6 +101,7 @@ def get_config(store_data, cluster=None):
|
|
|
|
|
config = dict()
|
|
|
|
|
config['debug'] = False
|
|
|
|
|
config['cluster'] = cluster
|
|
|
|
|
config['desctription'] = description
|
|
|
|
|
config['api_host'] = '{}:{}'.format(host, port)
|
|
|
|
|
config['api_scheme'] = scheme
|
|
|
|
|
config['api_key'] = api_key
|
|
|
|
@ -175,6 +177,10 @@ def cli_cluster():
|
|
|
|
|
# pvc cluster add
|
|
|
|
|
###############################################################################
|
|
|
|
|
@click.command(name='add', short_help='Add a new cluster to the client.')
|
|
|
|
|
@click.option(
|
|
|
|
|
'-d', '--description', 'description', required=False, default="N/A",
|
|
|
|
|
help='A text description of the cluster.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-a', '--address', 'address', required=True,
|
|
|
|
|
help='The IP address or hostname of the cluster API client.'
|
|
|
|
@ -194,7 +200,7 @@ def cli_cluster():
|
|
|
|
|
@click.argument(
|
|
|
|
|
'name'
|
|
|
|
|
)
|
|
|
|
|
def cluster_add(address, port, ssl, name, api_key):
|
|
|
|
|
def cluster_add(description, address, port, ssl, name, api_key):
|
|
|
|
|
"""
|
|
|
|
|
Add a new PVC cluster NAME, via its API connection details, to the configuration of the local CLI client. Replaces any existing cluster with this name.
|
|
|
|
|
"""
|
|
|
|
@ -207,6 +213,7 @@ def cluster_add(address, port, ssl, name, api_key):
|
|
|
|
|
existing_config = get_store(store_path)
|
|
|
|
|
# Append our new entry to the end
|
|
|
|
|
existing_config[name] = {
|
|
|
|
|
'description': description,
|
|
|
|
|
'host': address,
|
|
|
|
|
'port': port,
|
|
|
|
|
'scheme': scheme,
|
|
|
|
@ -252,10 +259,11 @@ def cluster_list():
|
|
|
|
|
clusters = get_store(store_path)
|
|
|
|
|
# Find the lengths of each column
|
|
|
|
|
name_length = 5
|
|
|
|
|
description_length = 12
|
|
|
|
|
address_length = 10
|
|
|
|
|
port_length = 5
|
|
|
|
|
scheme_length = 7
|
|
|
|
|
api_key_length = 8
|
|
|
|
|
api_key_length = 32
|
|
|
|
|
|
|
|
|
|
for cluster in clusters:
|
|
|
|
|
cluster_details = clusters[cluster]
|
|
|
|
@ -263,10 +271,11 @@ def cluster_list():
|
|
|
|
|
# This is a reference to an API configuration; grab the details from its listen address
|
|
|
|
|
cfgfile = cluster_details.get('cfgfile')
|
|
|
|
|
if os.path.isfile(cfgfile):
|
|
|
|
|
address, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
description, address, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
else:
|
|
|
|
|
address, port, scheme, api_key = 'N/A', 'N/A', 'N/A', 'N/A'
|
|
|
|
|
description, address, port, scheme, api_key = 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'
|
|
|
|
|
else:
|
|
|
|
|
description = cluster_details.get('description', '')
|
|
|
|
|
address = cluster_details.get('host', 'N/A')
|
|
|
|
|
port = cluster_details.get('port', 'N/A')
|
|
|
|
|
scheme = cluster_details.get('scheme', 'N/A')
|
|
|
|
@ -278,6 +287,9 @@ def cluster_list():
|
|
|
|
|
if _name_length > name_length:
|
|
|
|
|
name_length = _name_length
|
|
|
|
|
_address_length = len(address) + 1
|
|
|
|
|
_description_length = len(description) + 1
|
|
|
|
|
if _description_length > description_length:
|
|
|
|
|
description_length = _description_length
|
|
|
|
|
if _address_length > address_length:
|
|
|
|
|
address_length = _address_length
|
|
|
|
|
_port_length = len(str(port)) + 1
|
|
|
|
@ -294,11 +306,13 @@ def cluster_list():
|
|
|
|
|
click.echo("Available clusters:")
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo(
|
|
|
|
|
'{bold}{name: <{name_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}} {api_key: <{api_key_length}}{end_bold}'.format(
|
|
|
|
|
'{bold}{name: <{name_length}} {description: <{description_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}} {api_key: <{api_key_length}}{end_bold}'.format(
|
|
|
|
|
bold=ansiprint.bold(),
|
|
|
|
|
end_bold=ansiprint.end(),
|
|
|
|
|
name="Name",
|
|
|
|
|
name_length=name_length,
|
|
|
|
|
description="Description",
|
|
|
|
|
description_length=description_length,
|
|
|
|
|
address="Address",
|
|
|
|
|
address_length=address_length,
|
|
|
|
|
port="Port",
|
|
|
|
@ -315,14 +329,16 @@ def cluster_list():
|
|
|
|
|
if cluster_details.get('cfgfile', None):
|
|
|
|
|
# This is a reference to an API configuration; grab the details from its listen address
|
|
|
|
|
if os.path.isfile(cfgfile):
|
|
|
|
|
address, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
description, address, port, scheme, api_key = read_from_yaml(cfgfile)
|
|
|
|
|
else:
|
|
|
|
|
description = 'N/A'
|
|
|
|
|
address = 'N/A'
|
|
|
|
|
port = 'N/A'
|
|
|
|
|
scheme = 'N/A'
|
|
|
|
|
api_key = 'N/A'
|
|
|
|
|
else:
|
|
|
|
|
address = cluster_details.get('host', 'N/A')
|
|
|
|
|
description = cluster_details.get('description', 'N/A')
|
|
|
|
|
port = cluster_details.get('port', 'N/A')
|
|
|
|
|
scheme = cluster_details.get('scheme', 'N/A')
|
|
|
|
|
api_key = cluster_details.get('api_key', 'N/A')
|
|
|
|
@ -330,11 +346,13 @@ def cluster_list():
|
|
|
|
|
api_key = 'N/A'
|
|
|
|
|
|
|
|
|
|
click.echo(
|
|
|
|
|
'{bold}{name: <{name_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}} {api_key: <{api_key_length}}{end_bold}'.format(
|
|
|
|
|
'{bold}{name: <{name_length}} {description: <{description_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}} {api_key: <{api_key_length}}{end_bold}'.format(
|
|
|
|
|
bold='',
|
|
|
|
|
end_bold='',
|
|
|
|
|
name=cluster,
|
|
|
|
|
name_length=name_length,
|
|
|
|
|
description=description,
|
|
|
|
|
description_length=description_length,
|
|
|
|
|
address=address,
|
|
|
|
|
address_length=address_length,
|
|
|
|
|
port=port,
|
|
|
|
@ -694,13 +712,18 @@ def vm_meta(domain, node_limit, node_selector, node_autostart, migration_method,
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@click.argument(
|
|
|
|
|
'domain'
|
|
|
|
|
)
|
|
|
|
|
@click.argument(
|
|
|
|
|
'cfgfile', type=click.File(), default=None, required=False
|
|
|
|
|
)
|
|
|
|
|
def vm_modify(domain, cfgfile, editor, restart):
|
|
|
|
|
def vm_modify(domain, cfgfile, editor, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Modify existing virtual machine DOMAIN, either in-editor or with replacement CONFIG. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
@ -712,6 +735,12 @@ def vm_modify(domain, cfgfile, editor, restart):
|
|
|
|
|
if not retcode and not vm_information.get('name', None):
|
|
|
|
|
cleanup(False, 'ERROR: Could not find VM "{}"!'.format(domain))
|
|
|
|
|
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
dom_name = vm_information.get('name')
|
|
|
|
|
|
|
|
|
|
if editor is True:
|
|
|
|
@ -768,6 +797,8 @@ def vm_modify(domain, cfgfile, editor, restart):
|
|
|
|
|
cleanup(False, 'Error: XML is malformed or invalid: {}'.format(e))
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_modify(config, domain, new_cfg, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -788,7 +819,7 @@ def vm_undefine(domain, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Stop virtual machine DOMAIN and remove it database, preserving disks. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Undefine VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -815,7 +846,7 @@ def vm_remove(domain, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Stop virtual machine DOMAIN and remove it, along with all disks,. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Undefine VM {} and remove all disks'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -853,11 +884,21 @@ def vm_start(domain):
|
|
|
|
|
'-w', '--wait', 'wait', is_flag=True, default=False,
|
|
|
|
|
help='Wait for restart to complete before returning.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_restart(domain, wait):
|
|
|
|
|
def vm_restart(domain, wait, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Restart running virtual machine DOMAIN. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'restart', wait=wait)
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
@ -874,11 +915,21 @@ def vm_restart(domain, wait):
|
|
|
|
|
'-w', '--wait', 'wait', is_flag=True, default=False,
|
|
|
|
|
help='Wait for shutdown to complete before returning.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the shutdown'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_shutdown(domain, wait):
|
|
|
|
|
def vm_shutdown(domain, wait, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Gracefully shut down virtual machine DOMAIN. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Shut down VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'shutdown', wait=wait)
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
@ -891,11 +942,21 @@ def vm_shutdown(domain, wait):
|
|
|
|
|
@click.argument(
|
|
|
|
|
'domain'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the stop'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_stop(domain):
|
|
|
|
|
def vm_stop(domain, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Forcibly halt (destroy) running virtual machine DOMAIN. DOMAIN may be a UUID or name.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Forcibly stop VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'stop')
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
@ -1078,13 +1139,23 @@ def vm_vcpu_get(domain, raw):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_vcpu_set(domain, vcpus, topology, restart):
|
|
|
|
|
def vm_vcpu_set(domain, vcpus, topology, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Set the vCPU count of the virtual machine DOMAIN to VCPUS.
|
|
|
|
|
|
|
|
|
|
By default, the topology of the vCPus is 1 socket, VCPUS cores per socket, 1 thread per core.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
if topology is not None:
|
|
|
|
|
try:
|
|
|
|
@ -1098,6 +1169,8 @@ def vm_vcpu_set(domain, vcpus, topology, restart):
|
|
|
|
|
topology = (1, vcpus, 1)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_vcpus_set(config, domain, vcpus, topology, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1149,13 +1222,25 @@ def vm_memory_get(domain, raw):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_memory_set(domain, memory, restart):
|
|
|
|
|
def vm_memory_set(domain, memory, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Set the provisioned memory of the virtual machine DOMAIN to MEMORY; MEMORY must be an integer in MB.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_memory_set(config, domain, memory, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1222,13 +1307,25 @@ def vm_network_get(domain, raw):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_network_add(domain, vni, macaddr, model, restart):
|
|
|
|
|
def vm_network_add(domain, vni, macaddr, model, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Add the network VNI to the virtual machine DOMAIN. Networks are always addded to the end of the current list of networks in the virtual machine.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_networks_add(config, domain, vni, macaddr, model, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1246,13 +1343,25 @@ def vm_network_add(domain, vni, macaddr, model, restart):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_network_remove(domain, vni, restart):
|
|
|
|
|
def vm_network_remove(domain, vni, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove the network VNI to the virtual machine DOMAIN.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_networks_remove(config, domain, vni, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1325,15 +1434,27 @@ def vm_volume_get(domain, raw):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart):
|
|
|
|
|
def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Add the volume VOLUME to the virtual machine DOMAIN.
|
|
|
|
|
|
|
|
|
|
VOLUME may be either an absolute file path (for type 'file') or an RBD volume in the form "pool/volume" (for type 'rbd'). RBD volumes are verified against the cluster before adding and must exist.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_volumes_add(config, domain, volume, disk_id, bus, disk_type, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1351,13 +1472,25 @@ def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart):
|
|
|
|
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
|
|
|
|
help='Immediately restart VM to apply new config.'
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
'-y', '--yes', 'confirm_flag',
|
|
|
|
|
is_flag=True, default=False,
|
|
|
|
|
help='Confirm the restart'
|
|
|
|
|
)
|
|
|
|
|
@cluster_req
|
|
|
|
|
def vm_volume_remove(domain, vni, restart):
|
|
|
|
|
def vm_volume_remove(domain, vni, restart, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove the volume VNI to the virtual machine DOMAIN.
|
|
|
|
|
"""
|
|
|
|
|
if restart and not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Restart VM {} after applying change'.format(domain), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
retcode, retmsg = pvc_vm.vm_volumes_remove(config, domain, vni, restart)
|
|
|
|
|
if retcode and not restart:
|
|
|
|
|
retmsg = retmsg + " Changes will be applied on next VM start/restart."
|
|
|
|
|
cleanup(retcode, retmsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1669,7 +1802,7 @@ def net_remove(net, confirm_flag):
|
|
|
|
|
WARNING: PVC does not verify whether clients are still present in this network. Before removing, ensure
|
|
|
|
|
that all client VMs have been removed from the network or undefined behaviour may occur.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove network {}'.format(net), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -1778,7 +1911,7 @@ def net_dhcp_remove(net, macaddr, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove a DHCP lease for MACADDR from virtual network NET; NET must be a VNI.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove DHCP lease for {} in network {}'.format(macaddr, net), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -1897,7 +2030,7 @@ def net_acl_remove(net, rule, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove an NFT firewall rule RULE from network NET; RULE must be a description; NET must be a VNI.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove ACL {} in network {}'.format(rule, net), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2096,7 +2229,7 @@ def ceph_osd_add(node, device, weight, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Add a new Ceph OSD on node NODE with block device DEVICE.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Destroy all data and create a new OSD on {}:{}'.format(node, device), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2125,7 +2258,7 @@ def ceph_osd_remove(osdid, confirm_flag):
|
|
|
|
|
|
|
|
|
|
DANGER: This will completely remove the OSD from the cluster. OSDs will rebalance which may negatively affect performance or available space.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove OSD {}'.format(osdid), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2289,7 +2422,7 @@ def ceph_pool_remove(name, confirm_flag):
|
|
|
|
|
|
|
|
|
|
DANGER: This will completely remove the pool and all volumes contained in it from the cluster.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove RBD pool {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2410,7 +2543,7 @@ def ceph_volume_remove(pool, name, confirm_flag):
|
|
|
|
|
|
|
|
|
|
DANGER: This will completely remove the volume and all data contained in it.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove volume {}/{}'.format(pool, name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2594,7 +2727,7 @@ def ceph_volume_snapshot_remove(pool, volume, name, confirm_flag):
|
|
|
|
|
|
|
|
|
|
DANGER: This will completely remove the snapshot.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove snapshot {} for volume {}/{}'.format(name, pool, volume), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2870,7 +3003,7 @@ def provisioner_template_system_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove system template NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove system template {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -2977,7 +3110,7 @@ def provisioner_template_network_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove network template MAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove network template {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3041,7 +3174,7 @@ def provisioner_template_network_vni_remove(name, vni, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove network VNI from network template NAME.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove VNI {} from network template {}'.format(vni, name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3116,7 +3249,7 @@ def provisioner_template_storage_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove storage template NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove storage template {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3235,7 +3368,7 @@ def provisioner_template_storage_disk_remove(name, disk, confirm_flag):
|
|
|
|
|
|
|
|
|
|
DISK must be a Linux-style disk identifier such as "sda" or "vdb".
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove disk {} from storage template {}'.format(disk, name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3425,7 +3558,7 @@ def provisioner_userdata_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove userdata document NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove userdata document {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3604,7 +3737,7 @@ def provisioner_script_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove script NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove provisioning script {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3700,7 +3833,7 @@ def provisioner_ova_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove OVA image NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove OVA image {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -3885,7 +4018,7 @@ def provisioner_profile_remove(name, confirm_flag):
|
|
|
|
|
"""
|
|
|
|
|
Remove profile NAME from the PVC cluster provisioner.
|
|
|
|
|
"""
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove profile {}'.format(name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -4131,7 +4264,7 @@ def task_restore(filename, confirm_flag):
|
|
|
|
|
Restore the JSON backup data from a file to the cluster.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Replace all existing cluster data from coordinators with backup file "{}"'.format(filename.name), prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -4157,7 +4290,7 @@ def task_init(confirm_flag):
|
|
|
|
|
Perform initialization of a new PVC cluster.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not confirm_flag:
|
|
|
|
|
if not confirm_flag and not config['unsafe']:
|
|
|
|
|
try:
|
|
|
|
|
click.confirm('Remove all existing cluster data from coordinators and initialize a new cluster', prompt_suffix='? ', abort=True)
|
|
|
|
|
except Exception:
|
|
|
|
@ -4186,7 +4319,11 @@ def task_init(confirm_flag):
|
|
|
|
|
'-q', '--quiet', '_quiet', envvar='PVC_QUIET', is_flag=True, default=False,
|
|
|
|
|
help='Suppress cluster connection information.'
|
|
|
|
|
)
|
|
|
|
|
def cli(_cluster, _debug, _quiet):
|
|
|
|
|
@click.option(
|
|
|
|
|
'-u', '--unsafe', '_unsafe', envvar='PVC_UNSAFE', is_flag=True, default=False,
|
|
|
|
|
help='Allow unsafe operations without confirmation/"--yes" argument.'
|
|
|
|
|
)
|
|
|
|
|
def cli(_cluster, _debug, _quiet, _unsafe):
|
|
|
|
|
"""
|
|
|
|
|
Parallel Virtual Cluster CLI management tool
|
|
|
|
|
|
|
|
|
@ -4194,6 +4331,12 @@ def cli(_cluster, _debug, _quiet):
|
|
|
|
|
|
|
|
|
|
"PVC_CLUSTER": Set the cluster to access instead of using --cluster/-c
|
|
|
|
|
|
|
|
|
|
"PVC_DEBUG": Enable additional debugging details instead of using --debug/-v
|
|
|
|
|
|
|
|
|
|
"PVC_QUIET": Suppress stderr connection output from client instead of using --quiet/-q
|
|
|
|
|
|
|
|
|
|
"PVC_UNSAFE": Suppress confirmation requirements instead of using --unsafe/-u or --yes/-y; USE WITH EXTREME CARE
|
|
|
|
|
|
|
|
|
|
If no PVC_CLUSTER/--cluster is specified, attempts first to load the "local" cluster, checking
|
|
|
|
|
for an API configuration in "/etc/pvc/pvcapid.yaml". If this is also not found, abort.
|
|
|
|
|
"""
|
|
|
|
@ -4203,6 +4346,7 @@ def cli(_cluster, _debug, _quiet):
|
|
|
|
|
config = get_config(store_data, _cluster)
|
|
|
|
|
if not config.get('badcfg', None):
|
|
|
|
|
config['debug'] = _debug
|
|
|
|
|
config['unsafe'] = _unsafe
|
|
|
|
|
|
|
|
|
|
if not _quiet:
|
|
|
|
|
if config['api_scheme'] == 'https' and not config['verify_ssl']:
|
|
|
|
|