Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
0c75a127b2 | |||
f46c2e7f6a | |||
9de14c46fb | |||
1b8b101b64 | |||
fe15bdb854 | |||
b851a6209c | |||
5ceb57e540 | |||
62c84664fc | |||
66f1ac35ab | |||
529f99841d | |||
6246b8dfb3 | |||
669338c22b | |||
629cf62385 | |||
dfa3432601 | |||
62213fab99 |
22
README.md
22
README.md
@ -20,6 +20,28 @@ To get started with PVC, please see the [About](https://parallelvirtualcluster.r
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
#### v0.9.18
|
||||||
|
|
||||||
|
* Adds VM rename functionality to API and CLI client
|
||||||
|
|
||||||
|
#### v0.9.17
|
||||||
|
|
||||||
|
* [CLI] Fixes bugs in log follow output
|
||||||
|
|
||||||
|
#### v0.9.16
|
||||||
|
|
||||||
|
* Improves some CLI help messages
|
||||||
|
* Skips empty local cluster in CLI
|
||||||
|
* Adjusts how confirmations happen during VM modify restarts
|
||||||
|
* Fixes bug around corrupted VM log files
|
||||||
|
* Fixes bug around subprocess pipe exceptions
|
||||||
|
|
||||||
|
#### v0.9.15
|
||||||
|
|
||||||
|
* [CLI] Adds additional verification (--yes) to several VM management commands
|
||||||
|
* [CLI] Adds a method to override --yes/confirmation requirements via envvar (PVC_UNSAFE)
|
||||||
|
* [CLI] Adds description fields to PVC clusters in CLI
|
||||||
|
|
||||||
#### v0.9.14
|
#### v0.9.14
|
||||||
|
|
||||||
* Fixes bugs around cloned volume provisioning
|
* Fixes bugs around cloned volume provisioning
|
||||||
|
@ -26,7 +26,7 @@ import pvcapid.flaskapi as pvc_api
|
|||||||
##########################################################
|
##########################################################
|
||||||
|
|
||||||
# Version string for startup output
|
# Version string for startup output
|
||||||
version = '0.9.14'
|
version = '0.9.18'
|
||||||
|
|
||||||
if pvc_api.config['ssl_enabled']:
|
if pvc_api.config['ssl_enabled']:
|
||||||
context = (pvc_api.config['ssl_cert_file'], pvc_api.config['ssl_key_file'])
|
context = (pvc_api.config['ssl_cert_file'], pvc_api.config['ssl_key_file'])
|
||||||
|
@ -1804,6 +1804,45 @@ class API_VM_Console(Resource):
|
|||||||
api.add_resource(API_VM_Console, '/vm/<vm>/console')
|
api.add_resource(API_VM_Console, '/vm/<vm>/console')
|
||||||
|
|
||||||
|
|
||||||
|
# /vm/<vm>/rename
|
||||||
|
class API_VM_Rename(Resource):
|
||||||
|
@RequestParser([
|
||||||
|
{'name': 'new_name'}
|
||||||
|
])
|
||||||
|
@Authenticator
|
||||||
|
def post(self, vm, reqargs):
|
||||||
|
"""
|
||||||
|
Rename VM {vm}, and all connected disk volumes which include this name, to {new_name}
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- vm
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: new_name
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The new name of the VM
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
id: Message
|
||||||
|
400:
|
||||||
|
description: Bad request
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
id: Message
|
||||||
|
"""
|
||||||
|
return api_helper.vm_rename(
|
||||||
|
vm,
|
||||||
|
reqargs.get('new_name', None)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(API_VM_Rename, '/vm/<vm>/rename')
|
||||||
|
|
||||||
|
|
||||||
##########################################################
|
##########################################################
|
||||||
# Client API - Network
|
# Client API - Network
|
||||||
##########################################################
|
##########################################################
|
||||||
|
@ -601,6 +601,37 @@ def vm_modify(name, restart, xml):
|
|||||||
return output, retcode
|
return output, retcode
|
||||||
|
|
||||||
|
|
||||||
|
def vm_rename(name, new_name):
|
||||||
|
"""
|
||||||
|
Rename a VM in the PVC cluster.
|
||||||
|
"""
|
||||||
|
if new_name is None:
|
||||||
|
output = {
|
||||||
|
'message': 'A new VM name must be specified'
|
||||||
|
}
|
||||||
|
return 400, output
|
||||||
|
|
||||||
|
zk_conn = pvc_common.startZKConnection(config['coordinators'])
|
||||||
|
if pvc_vm.searchClusterByName(zk_conn, new_name) is not None:
|
||||||
|
output = {
|
||||||
|
'message': 'A VM named \'{}\' is already present in the cluster'.format(new_name)
|
||||||
|
}
|
||||||
|
return 400, output
|
||||||
|
|
||||||
|
retflag, retdata = pvc_vm.rename_vm(zk_conn, name, new_name)
|
||||||
|
pvc_common.stopZKConnection(zk_conn)
|
||||||
|
|
||||||
|
if retflag:
|
||||||
|
retcode = 200
|
||||||
|
else:
|
||||||
|
retcode = 400
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'message': retdata.replace('\"', '\'')
|
||||||
|
}
|
||||||
|
return output, retcode
|
||||||
|
|
||||||
|
|
||||||
def vm_undefine(name):
|
def vm_undefine(name):
|
||||||
"""
|
"""
|
||||||
Undefine a VM from the PVC cluster.
|
Undefine a VM from the PVC cluster.
|
||||||
|
@ -130,6 +130,27 @@ def vm_modify(config, vm, xml, restart):
|
|||||||
return retstatus, response.json().get('message', '')
|
return retstatus, response.json().get('message', '')
|
||||||
|
|
||||||
|
|
||||||
|
def vm_rename(config, vm, new_name):
|
||||||
|
"""
|
||||||
|
Rename VM to new name
|
||||||
|
|
||||||
|
API endpoint: POST /vm/{vm}/rename
|
||||||
|
API arguments: new_name={new_name}
|
||||||
|
API schema: {"message":"{data}"}
|
||||||
|
"""
|
||||||
|
params = {
|
||||||
|
'new_name': new_name
|
||||||
|
}
|
||||||
|
response = call_api(config, 'post', '/vm/{vm}/rename'.format(vm=vm), params=params)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
retstatus = True
|
||||||
|
else:
|
||||||
|
retstatus = False
|
||||||
|
|
||||||
|
return retstatus, response.json().get('message', '')
|
||||||
|
|
||||||
|
|
||||||
def vm_metadata(config, vm, node_limit, node_selector, node_autostart, migration_method, provisioner_profile):
|
def vm_metadata(config, vm, node_limit, node_selector, node_autostart, migration_method, provisioner_profile):
|
||||||
"""
|
"""
|
||||||
Modify PVC metadata of a VM
|
Modify PVC metadata of a VM
|
||||||
@ -1002,8 +1023,9 @@ def follow_console_log(config, vm, lines=10):
|
|||||||
API arguments: lines={lines}
|
API arguments: lines={lines}
|
||||||
API schema: {"name":"{vmname}","data":"{console_log}"}
|
API schema: {"name":"{vmname}","data":"{console_log}"}
|
||||||
"""
|
"""
|
||||||
|
# We always grab 500 to match the follow call, but only _show_ `lines` number
|
||||||
params = {
|
params = {
|
||||||
'lines': lines
|
'lines': 500
|
||||||
}
|
}
|
||||||
response = call_api(config, 'get', '/vm/{vm}/console'.format(vm=vm), params=params)
|
response = call_api(config, 'get', '/vm/{vm}/console'.format(vm=vm), params=params)
|
||||||
|
|
||||||
@ -1012,7 +1034,7 @@ def follow_console_log(config, vm, lines=10):
|
|||||||
|
|
||||||
# Shrink the log buffer to length lines
|
# Shrink the log buffer to length lines
|
||||||
console_log = response.json()['data']
|
console_log = response.json()['data']
|
||||||
shrunk_log = console_log.split('\n')[-lines:]
|
shrunk_log = console_log.split('\n')[-int(lines):]
|
||||||
loglines = '\n'.join(shrunk_log)
|
loglines = '\n'.join(shrunk_log)
|
||||||
|
|
||||||
# Print the initial data and begin following
|
# Print the initial data and begin following
|
||||||
|
@ -46,7 +46,7 @@ myhostname = socket.gethostname().split('.')[0]
|
|||||||
zk_host = ''
|
zk_host = ''
|
||||||
|
|
||||||
default_store_data = {
|
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']
|
api_key = api_config['pvc']['api']['authentication']['tokens'][0]['token']
|
||||||
else:
|
else:
|
||||||
api_key = 'N/A'
|
api_key = 'N/A'
|
||||||
return host, port, scheme, api_key
|
return cfgfile, host, port, scheme, api_key
|
||||||
|
|
||||||
|
|
||||||
def get_config(store_data, cluster=None):
|
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
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
cfgfile = cluster_details.get('cfgfile')
|
cfgfile = cluster_details.get('cfgfile')
|
||||||
if os.path.isfile(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:
|
else:
|
||||||
return {'badcfg': True}
|
return {'badcfg': True}
|
||||||
# Handle an all-wildcard address
|
# Handle an all-wildcard address
|
||||||
@ -92,6 +92,7 @@ def get_config(store_data, cluster=None):
|
|||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
else:
|
else:
|
||||||
# This is a static configuration, get the raw details
|
# This is a static configuration, get the raw details
|
||||||
|
description = cluster_details['description']
|
||||||
host = cluster_details['host']
|
host = cluster_details['host']
|
||||||
port = cluster_details['port']
|
port = cluster_details['port']
|
||||||
scheme = cluster_details['scheme']
|
scheme = cluster_details['scheme']
|
||||||
@ -100,6 +101,7 @@ def get_config(store_data, cluster=None):
|
|||||||
config = dict()
|
config = dict()
|
||||||
config['debug'] = False
|
config['debug'] = False
|
||||||
config['cluster'] = cluster
|
config['cluster'] = cluster
|
||||||
|
config['desctription'] = description
|
||||||
config['api_host'] = '{}:{}'.format(host, port)
|
config['api_host'] = '{}:{}'.format(host, port)
|
||||||
config['api_scheme'] = scheme
|
config['api_scheme'] = scheme
|
||||||
config['api_key'] = api_key
|
config['api_key'] = api_key
|
||||||
@ -175,6 +177,10 @@ def cli_cluster():
|
|||||||
# pvc cluster add
|
# pvc cluster add
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@click.command(name='add', short_help='Add a new cluster to the client.')
|
@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(
|
@click.option(
|
||||||
'-a', '--address', 'address', required=True,
|
'-a', '--address', 'address', required=True,
|
||||||
help='The IP address or hostname of the cluster API client.'
|
help='The IP address or hostname of the cluster API client.'
|
||||||
@ -194,7 +200,7 @@ def cli_cluster():
|
|||||||
@click.argument(
|
@click.argument(
|
||||||
'name'
|
'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.
|
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)
|
existing_config = get_store(store_path)
|
||||||
# Append our new entry to the end
|
# Append our new entry to the end
|
||||||
existing_config[name] = {
|
existing_config[name] = {
|
||||||
|
'description': description,
|
||||||
'host': address,
|
'host': address,
|
||||||
'port': port,
|
'port': port,
|
||||||
'scheme': scheme,
|
'scheme': scheme,
|
||||||
@ -252,10 +259,11 @@ def cluster_list():
|
|||||||
clusters = get_store(store_path)
|
clusters = get_store(store_path)
|
||||||
# Find the lengths of each column
|
# Find the lengths of each column
|
||||||
name_length = 5
|
name_length = 5
|
||||||
|
description_length = 12
|
||||||
address_length = 10
|
address_length = 10
|
||||||
port_length = 5
|
port_length = 5
|
||||||
scheme_length = 7
|
scheme_length = 7
|
||||||
api_key_length = 8
|
api_key_length = 32
|
||||||
|
|
||||||
for cluster in clusters:
|
for cluster in clusters:
|
||||||
cluster_details = clusters[cluster]
|
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
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
cfgfile = cluster_details.get('cfgfile')
|
cfgfile = cluster_details.get('cfgfile')
|
||||||
if os.path.isfile(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:
|
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:
|
else:
|
||||||
|
description = cluster_details.get('description', '')
|
||||||
address = cluster_details.get('host', 'N/A')
|
address = cluster_details.get('host', 'N/A')
|
||||||
port = cluster_details.get('port', 'N/A')
|
port = cluster_details.get('port', 'N/A')
|
||||||
scheme = cluster_details.get('scheme', 'N/A')
|
scheme = cluster_details.get('scheme', 'N/A')
|
||||||
@ -278,6 +287,9 @@ def cluster_list():
|
|||||||
if _name_length > name_length:
|
if _name_length > name_length:
|
||||||
name_length = _name_length
|
name_length = _name_length
|
||||||
_address_length = len(address) + 1
|
_address_length = len(address) + 1
|
||||||
|
_description_length = len(description) + 1
|
||||||
|
if _description_length > description_length:
|
||||||
|
description_length = _description_length
|
||||||
if _address_length > address_length:
|
if _address_length > address_length:
|
||||||
address_length = _address_length
|
address_length = _address_length
|
||||||
_port_length = len(str(port)) + 1
|
_port_length = len(str(port)) + 1
|
||||||
@ -294,11 +306,13 @@ def cluster_list():
|
|||||||
click.echo("Available clusters:")
|
click.echo("Available clusters:")
|
||||||
click.echo()
|
click.echo()
|
||||||
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(),
|
bold=ansiprint.bold(),
|
||||||
end_bold=ansiprint.end(),
|
end_bold=ansiprint.end(),
|
||||||
name="Name",
|
name="Name",
|
||||||
name_length=name_length,
|
name_length=name_length,
|
||||||
|
description="Description",
|
||||||
|
description_length=description_length,
|
||||||
address="Address",
|
address="Address",
|
||||||
address_length=address_length,
|
address_length=address_length,
|
||||||
port="Port",
|
port="Port",
|
||||||
@ -315,14 +329,12 @@ def cluster_list():
|
|||||||
if cluster_details.get('cfgfile', None):
|
if cluster_details.get('cfgfile', None):
|
||||||
# This is a reference to an API configuration; grab the details from its listen address
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
if os.path.isfile(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:
|
else:
|
||||||
address = 'N/A'
|
continue
|
||||||
port = 'N/A'
|
|
||||||
scheme = 'N/A'
|
|
||||||
api_key = 'N/A'
|
|
||||||
else:
|
else:
|
||||||
address = cluster_details.get('host', 'N/A')
|
address = cluster_details.get('host', 'N/A')
|
||||||
|
description = cluster_details.get('description', 'N/A')
|
||||||
port = cluster_details.get('port', 'N/A')
|
port = cluster_details.get('port', 'N/A')
|
||||||
scheme = cluster_details.get('scheme', 'N/A')
|
scheme = cluster_details.get('scheme', 'N/A')
|
||||||
api_key = cluster_details.get('api_key', 'N/A')
|
api_key = cluster_details.get('api_key', 'N/A')
|
||||||
@ -330,11 +342,13 @@ def cluster_list():
|
|||||||
api_key = 'N/A'
|
api_key = 'N/A'
|
||||||
|
|
||||||
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='',
|
bold='',
|
||||||
end_bold='',
|
end_bold='',
|
||||||
name=cluster,
|
name=cluster,
|
||||||
name_length=name_length,
|
name_length=name_length,
|
||||||
|
description=description,
|
||||||
|
description_length=description_length,
|
||||||
address=address,
|
address=address,
|
||||||
address_length=address_length,
|
address_length=address_length,
|
||||||
port=port,
|
port=port,
|
||||||
@ -694,13 +708,18 @@ def vm_meta(domain, node_limit, node_selector, node_autostart, migration_method,
|
|||||||
'-r', '--restart', 'restart', is_flag=True,
|
'-r', '--restart', 'restart', is_flag=True,
|
||||||
help='Immediately restart VM to apply new config.'
|
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(
|
@click.argument(
|
||||||
'domain'
|
'domain'
|
||||||
)
|
)
|
||||||
@click.argument(
|
@click.argument(
|
||||||
'cfgfile', type=click.File(), default=None, required=False
|
'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.
|
Modify existing virtual machine DOMAIN, either in-editor or with replacement CONFIG. DOMAIN may be a UUID or name.
|
||||||
"""
|
"""
|
||||||
@ -727,38 +746,28 @@ def vm_modify(domain, cfgfile, editor, restart):
|
|||||||
else:
|
else:
|
||||||
new_vm_cfgfile = new_vm_cfgfile.strip()
|
new_vm_cfgfile = new_vm_cfgfile.strip()
|
||||||
|
|
||||||
# Show a diff and confirm
|
|
||||||
click.echo('Pending modifications:')
|
|
||||||
click.echo('')
|
|
||||||
diff = list(difflib.unified_diff(current_vm_cfgfile.split('\n'), new_vm_cfgfile.split('\n'), fromfile='current', tofile='modified', fromfiledate='', tofiledate='', n=3, lineterm=''))
|
|
||||||
for line in diff:
|
|
||||||
if re.match(r'^\+', line) is not None:
|
|
||||||
click.echo(colorama.Fore.GREEN + line + colorama.Fore.RESET)
|
|
||||||
elif re.match(r'^\-', line) is not None:
|
|
||||||
click.echo(colorama.Fore.RED + line + colorama.Fore.RESET)
|
|
||||||
elif re.match(r'^\^', line) is not None:
|
|
||||||
click.echo(colorama.Fore.BLUE + line + colorama.Fore.RESET)
|
|
||||||
else:
|
|
||||||
click.echo(line)
|
|
||||||
click.echo('')
|
|
||||||
|
|
||||||
click.confirm('Write modifications to cluster?', abort=True)
|
|
||||||
|
|
||||||
if restart:
|
|
||||||
click.echo('Writing modified configuration of VM "{}" and restarting.'.format(dom_name))
|
|
||||||
else:
|
|
||||||
click.echo('Writing modified configuration of VM "{}".'.format(dom_name))
|
|
||||||
|
|
||||||
# We're operating in replace mode
|
# We're operating in replace mode
|
||||||
else:
|
else:
|
||||||
# Open the XML file
|
# Open the XML file
|
||||||
new_vm_cfgfile = cfgfile.read()
|
new_vm_cfgfile = cfgfile.read()
|
||||||
cfgfile.close()
|
cfgfile.close()
|
||||||
|
|
||||||
if restart:
|
click.echo('Replacing configuration of VM "{}" with file "{}".'.format(dom_name, cfgfile.name))
|
||||||
click.echo('Replacing configuration of VM "{}" with file "{}" and restarting.'.format(dom_name, cfgfile.name))
|
|
||||||
|
# Show a diff and confirm
|
||||||
|
click.echo('Pending modifications:')
|
||||||
|
click.echo('')
|
||||||
|
diff = list(difflib.unified_diff(current_vm_cfgfile.split('\n'), new_vm_cfgfile.split('\n'), fromfile='current', tofile='modified', fromfiledate='', tofiledate='', n=3, lineterm=''))
|
||||||
|
for line in diff:
|
||||||
|
if re.match(r'^\+', line) is not None:
|
||||||
|
click.echo(colorama.Fore.GREEN + line + colorama.Fore.RESET)
|
||||||
|
elif re.match(r'^\-', line) is not None:
|
||||||
|
click.echo(colorama.Fore.RED + line + colorama.Fore.RESET)
|
||||||
|
elif re.match(r'^\^', line) is not None:
|
||||||
|
click.echo(colorama.Fore.BLUE + line + colorama.Fore.RESET)
|
||||||
else:
|
else:
|
||||||
click.echo('Replacing configuration of VM "{}" with file "{}".'.format(dom_name, cfgfile.name))
|
click.echo(line)
|
||||||
|
click.echo('')
|
||||||
|
|
||||||
# Verify our XML is sensible
|
# Verify our XML is sensible
|
||||||
try:
|
try:
|
||||||
@ -767,7 +776,47 @@ def vm_modify(domain, cfgfile, editor, restart):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
cleanup(False, 'Error: XML is malformed or invalid: {}'.format(e))
|
cleanup(False, 'Error: XML is malformed or invalid: {}'.format(e))
|
||||||
|
|
||||||
|
click.confirm('Write modifications to cluster?', abort=True)
|
||||||
|
|
||||||
|
if restart and not confirm_flag and not config['unsafe']:
|
||||||
|
try:
|
||||||
|
click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_modify(config, domain, new_cfg, restart)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc vm rename
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='rename', short_help='Rename a virtual machine.')
|
||||||
|
@click.argument(
|
||||||
|
'domain'
|
||||||
|
)
|
||||||
|
@click.argument(
|
||||||
|
'new_name'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-y', '--yes', 'confirm_flag',
|
||||||
|
is_flag=True, default=False,
|
||||||
|
help='Confirm the rename'
|
||||||
|
)
|
||||||
|
@cluster_req
|
||||||
|
def vm_rename(domain, new_name, confirm_flag):
|
||||||
|
"""
|
||||||
|
Rename virtual machine DOMAIN, and all its connected disk volumes, to NEW_NAME. DOMAIN may be a UUID or name.
|
||||||
|
"""
|
||||||
|
if not confirm_flag and not config['unsafe']:
|
||||||
|
try:
|
||||||
|
click.confirm('Rename VM {} to {}'.format(domain, new_name), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
retcode, retmsg = pvc_vm.vm_rename(config, domain, new_name)
|
||||||
cleanup(retcode, retmsg)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -788,7 +837,7 @@ def vm_undefine(domain, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Stop virtual machine DOMAIN and remove it database, preserving disks. DOMAIN may be a UUID or name.
|
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:
|
try:
|
||||||
click.confirm('Undefine VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
click.confirm('Undefine VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -815,7 +864,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.
|
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:
|
try:
|
||||||
click.confirm('Undefine VM {} and remove all disks'.format(domain), prompt_suffix='? ', abort=True)
|
click.confirm('Undefine VM {} and remove all disks'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -853,11 +902,21 @@ def vm_start(domain):
|
|||||||
'-w', '--wait', 'wait', is_flag=True, default=False,
|
'-w', '--wait', 'wait', is_flag=True, default=False,
|
||||||
help='Wait for restart to complete before returning.'
|
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
|
@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.
|
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)
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'restart', wait=wait)
|
||||||
cleanup(retcode, retmsg)
|
cleanup(retcode, retmsg)
|
||||||
@ -874,11 +933,21 @@ def vm_restart(domain, wait):
|
|||||||
'-w', '--wait', 'wait', is_flag=True, default=False,
|
'-w', '--wait', 'wait', is_flag=True, default=False,
|
||||||
help='Wait for shutdown to complete before returning.'
|
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
|
@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.
|
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)
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'shutdown', wait=wait)
|
||||||
cleanup(retcode, retmsg)
|
cleanup(retcode, retmsg)
|
||||||
@ -891,11 +960,21 @@ def vm_shutdown(domain, wait):
|
|||||||
@click.argument(
|
@click.argument(
|
||||||
'domain'
|
'domain'
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
'-y', '--yes', 'confirm_flag',
|
||||||
|
is_flag=True, default=False,
|
||||||
|
help='Confirm the stop'
|
||||||
|
)
|
||||||
@cluster_req
|
@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.
|
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')
|
retcode, retmsg = pvc_vm.vm_state(config, domain, 'stop')
|
||||||
cleanup(retcode, retmsg)
|
cleanup(retcode, retmsg)
|
||||||
@ -1078,26 +1157,38 @@ def vm_vcpu_get(domain, raw):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
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.
|
By default, the topology of the vCPus is 1 socket, VCPUS cores per socket, 1 thread per core.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if topology is not None:
|
if topology is not None:
|
||||||
try:
|
try:
|
||||||
sockets, cores, threads = topology.split(',')
|
sockets, cores, threads = topology.split(',')
|
||||||
if sockets * cores * threads != vcpus:
|
if sockets * cores * threads != vcpus:
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
cleanup(False, "The topology specified is not valid.")
|
cleanup(False, "The specified topology is not valid.")
|
||||||
topology = (sockets, cores, threads)
|
topology = (sockets, cores, threads)
|
||||||
else:
|
else:
|
||||||
topology = (1, vcpus, 1)
|
topology = (1, vcpus, 1)
|
||||||
|
|
||||||
|
if restart and not confirm_flag and not config['unsafe']:
|
||||||
|
try:
|
||||||
|
click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_vcpus_set(config, domain, vcpus, topology, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1149,13 +1240,25 @@ def vm_memory_get(domain, raw):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
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 {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_memory_set(config, domain, memory, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1222,13 +1325,25 @@ def vm_network_get(domain, raw):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
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 {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_networks_add(config, domain, vni, macaddr, model, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1246,13 +1361,25 @@ def vm_network_add(domain, vni, macaddr, model, restart):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
Remove the network VNI to the virtual machine DOMAIN.
|
||||||
"""
|
"""
|
||||||
|
if restart and not confirm_flag and not config['unsafe']:
|
||||||
|
try:
|
||||||
|
click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_networks_remove(config, domain, vni, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1325,15 +1452,27 @@ def vm_volume_get(domain, raw):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
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.
|
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 {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_volumes_add(config, domain, volume, disk_id, bus, disk_type, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1351,13 +1490,25 @@ def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart):
|
|||||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
help='Immediately restart VM to apply new config.'
|
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
|
@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.
|
Remove the volume VNI to the virtual machine DOMAIN.
|
||||||
"""
|
"""
|
||||||
|
if restart and not confirm_flag and not config['unsafe']:
|
||||||
|
try:
|
||||||
|
click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
|
||||||
|
except Exception:
|
||||||
|
restart = False
|
||||||
|
|
||||||
retcode, retmsg = pvc_vm.vm_volumes_remove(config, domain, vni, restart)
|
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)
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
@ -1642,6 +1793,7 @@ def net_modify(vni, description, domain, name_servers, ip6_network, ip6_gateway,
|
|||||||
Modify details of virtual network VNI. All fields optional; only specified fields will be updated.
|
Modify details of virtual network VNI. All fields optional; only specified fields will be updated.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
pvc network modify 1001 --gateway 10.1.1.1 --dhcp
|
pvc network modify 1001 --gateway 10.1.1.1 --dhcp
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -1669,7 +1821,7 @@ def net_remove(net, confirm_flag):
|
|||||||
WARNING: PVC does not verify whether clients are still present in this network. Before removing, ensure
|
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.
|
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:
|
try:
|
||||||
click.confirm('Remove network {}'.format(net), prompt_suffix='? ', abort=True)
|
click.confirm('Remove network {}'.format(net), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -1778,7 +1930,7 @@ def net_dhcp_remove(net, macaddr, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove a DHCP lease for MACADDR from virtual network NET; NET must be a VNI.
|
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:
|
try:
|
||||||
click.confirm('Remove DHCP lease for {} in network {}'.format(macaddr, net), prompt_suffix='? ', abort=True)
|
click.confirm('Remove DHCP lease for {} in network {}'.format(macaddr, net), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -1897,7 +2049,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.
|
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:
|
try:
|
||||||
click.confirm('Remove ACL {} in network {}'.format(rule, net), prompt_suffix='? ', abort=True)
|
click.confirm('Remove ACL {} in network {}'.format(rule, net), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2096,7 +2248,7 @@ def ceph_osd_add(node, device, weight, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Add a new Ceph OSD on node NODE with block device DEVICE.
|
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:
|
try:
|
||||||
click.confirm('Destroy all data and create a new OSD on {}:{}'.format(node, device), prompt_suffix='? ', abort=True)
|
click.confirm('Destroy all data and create a new OSD on {}:{}'.format(node, device), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2125,7 +2277,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.
|
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:
|
try:
|
||||||
click.confirm('Remove OSD {}'.format(osdid), prompt_suffix='? ', abort=True)
|
click.confirm('Remove OSD {}'.format(osdid), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2256,14 +2408,16 @@ def ceph_pool():
|
|||||||
default='copies=3,mincopies=2', show_default=True, required=False,
|
default='copies=3,mincopies=2', show_default=True, required=False,
|
||||||
help="""
|
help="""
|
||||||
The replication configuration, specifying both a "copies" and "mincopies" value, separated by a
|
The replication configuration, specifying both a "copies" and "mincopies" value, separated by a
|
||||||
comma, e.g. "copies=3,mincopies=2". The "copies" value specifies the total number of replicas and should not exceed the total number of nodes; the "mincopies" value specifies the minimum number of available copies to allow writes. For additional details please see the Cluster Architecture documentation.
|
comma, e.g. "copies=3,mincopies=2". The "copies" value specifies the total number of replicas
|
||||||
|
and should not exceed the total number of nodes; the "mincopies" value specifies the minimum
|
||||||
|
number of available copies to allow writes. For additional details please see the Cluster
|
||||||
|
Architecture documentation.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@cluster_req
|
@cluster_req
|
||||||
def ceph_pool_add(name, pgs, replcfg):
|
def ceph_pool_add(name, pgs, replcfg):
|
||||||
"""
|
"""
|
||||||
Add a new Ceph RBD pool with name NAME and PGS placement groups.
|
Add a new Ceph RBD pool with name NAME and PGS placement groups.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
retcode, retmsg = pvc_ceph.ceph_pool_add(config, name, pgs, replcfg)
|
retcode, retmsg = pvc_ceph.ceph_pool_add(config, name, pgs, replcfg)
|
||||||
@ -2289,7 +2443,7 @@ def ceph_pool_remove(name, confirm_flag):
|
|||||||
|
|
||||||
DANGER: This will completely remove the pool and all volumes contained in it from the cluster.
|
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:
|
try:
|
||||||
click.confirm('Remove RBD pool {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove RBD pool {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2410,7 +2564,7 @@ def ceph_volume_remove(pool, name, confirm_flag):
|
|||||||
|
|
||||||
DANGER: This will completely remove the volume and all data contained in it.
|
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:
|
try:
|
||||||
click.confirm('Remove volume {}/{}'.format(pool, name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove volume {}/{}'.format(pool, name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2594,7 +2748,7 @@ def ceph_volume_snapshot_remove(pool, volume, name, confirm_flag):
|
|||||||
|
|
||||||
DANGER: This will completely remove the snapshot.
|
DANGER: This will completely remove the snapshot.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove snapshot {} for volume {}/{}'.format(name, pool, volume), prompt_suffix='? ', abort=True)
|
click.confirm('Remove snapshot {} for volume {}/{}'.format(name, pool, volume), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2870,7 +3024,7 @@ def provisioner_template_system_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove system template NAME from the PVC cluster provisioner.
|
Remove system template NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove system template {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove system template {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -2977,7 +3131,7 @@ def provisioner_template_network_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove network template MAME from the PVC cluster provisioner.
|
Remove network template MAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove network template {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove network template {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3041,7 +3195,7 @@ def provisioner_template_network_vni_remove(name, vni, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove network VNI from network template NAME.
|
Remove network VNI from network template NAME.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove VNI {} from network template {}'.format(vni, name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove VNI {} from network template {}'.format(vni, name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3116,7 +3270,7 @@ def provisioner_template_storage_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove storage template NAME from the PVC cluster provisioner.
|
Remove storage template NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove storage template {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove storage template {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3235,7 +3389,7 @@ def provisioner_template_storage_disk_remove(name, disk, confirm_flag):
|
|||||||
|
|
||||||
DISK must be a Linux-style disk identifier such as "sda" or "vdb".
|
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:
|
try:
|
||||||
click.confirm('Remove disk {} from storage template {}'.format(disk, name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove disk {} from storage template {}'.format(disk, name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3425,7 +3579,7 @@ def provisioner_userdata_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove userdata document NAME from the PVC cluster provisioner.
|
Remove userdata document NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove userdata document {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove userdata document {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3604,7 +3758,7 @@ def provisioner_script_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove script NAME from the PVC cluster provisioner.
|
Remove script NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove provisioning script {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove provisioning script {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3700,7 +3854,7 @@ def provisioner_ova_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove OVA image NAME from the PVC cluster provisioner.
|
Remove OVA image NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove OVA image {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove OVA image {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -3885,7 +4039,7 @@ def provisioner_profile_remove(name, confirm_flag):
|
|||||||
"""
|
"""
|
||||||
Remove profile NAME from the PVC cluster provisioner.
|
Remove profile NAME from the PVC cluster provisioner.
|
||||||
"""
|
"""
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove profile {}'.format(name), prompt_suffix='? ', abort=True)
|
click.confirm('Remove profile {}'.format(name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -4065,9 +4219,13 @@ def status_cluster(oformat):
|
|||||||
Show basic information and health for the active PVC cluster.
|
Show basic information and health for the active PVC cluster.
|
||||||
|
|
||||||
Output formats:
|
Output formats:
|
||||||
|
|
||||||
plain: Full text, full colour output for human-readability.
|
plain: Full text, full colour output for human-readability.
|
||||||
|
|
||||||
short: Health-only, full colour output for human-readability.
|
short: Health-only, full colour output for human-readability.
|
||||||
|
|
||||||
json: Compact JSON representation for machine parsing.
|
json: Compact JSON representation for machine parsing.
|
||||||
|
|
||||||
json-pretty: Pretty-printed JSON representation for machine parsing or human-readability.
|
json-pretty: Pretty-printed JSON representation for machine parsing or human-readability.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -4131,7 +4289,7 @@ def task_restore(filename, confirm_flag):
|
|||||||
Restore the JSON backup data from a file to the cluster.
|
Restore the JSON backup data from a file to the cluster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Replace all existing cluster data from coordinators with backup file "{}"'.format(filename.name), prompt_suffix='? ', abort=True)
|
click.confirm('Replace all existing cluster data from coordinators with backup file "{}"'.format(filename.name), prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -4157,7 +4315,7 @@ def task_init(confirm_flag):
|
|||||||
Perform initialization of a new PVC cluster.
|
Perform initialization of a new PVC cluster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not confirm_flag:
|
if not confirm_flag and not config['unsafe']:
|
||||||
try:
|
try:
|
||||||
click.confirm('Remove all existing cluster data from coordinators and initialize a new cluster', prompt_suffix='? ', abort=True)
|
click.confirm('Remove all existing cluster data from coordinators and initialize a new cluster', prompt_suffix='? ', abort=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -4186,7 +4344,11 @@ def task_init(confirm_flag):
|
|||||||
'-q', '--quiet', '_quiet', envvar='PVC_QUIET', is_flag=True, default=False,
|
'-q', '--quiet', '_quiet', envvar='PVC_QUIET', is_flag=True, default=False,
|
||||||
help='Suppress cluster connection information.'
|
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
|
Parallel Virtual Cluster CLI management tool
|
||||||
|
|
||||||
@ -4194,6 +4356,12 @@ def cli(_cluster, _debug, _quiet):
|
|||||||
|
|
||||||
"PVC_CLUSTER": Set the cluster to access instead of using --cluster/-c
|
"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
|
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.
|
for an API configuration in "/etc/pvc/pvcapid.yaml". If this is also not found, abort.
|
||||||
"""
|
"""
|
||||||
@ -4203,6 +4371,7 @@ def cli(_cluster, _debug, _quiet):
|
|||||||
config = get_config(store_data, _cluster)
|
config = get_config(store_data, _cluster)
|
||||||
if not config.get('badcfg', None):
|
if not config.get('badcfg', None):
|
||||||
config['debug'] = _debug
|
config['debug'] = _debug
|
||||||
|
config['unsafe'] = _unsafe
|
||||||
|
|
||||||
if not _quiet:
|
if not _quiet:
|
||||||
if config['api_scheme'] == 'https' and not config['verify_ssl']:
|
if config['api_scheme'] == 'https' and not config['verify_ssl']:
|
||||||
@ -4256,6 +4425,7 @@ vm_volume.add_command(vm_volume_remove)
|
|||||||
cli_vm.add_command(vm_define)
|
cli_vm.add_command(vm_define)
|
||||||
cli_vm.add_command(vm_meta)
|
cli_vm.add_command(vm_meta)
|
||||||
cli_vm.add_command(vm_modify)
|
cli_vm.add_command(vm_modify)
|
||||||
|
cli_vm.add_command(vm_rename)
|
||||||
cli_vm.add_command(vm_undefine)
|
cli_vm.add_command(vm_undefine)
|
||||||
cli_vm.add_command(vm_remove)
|
cli_vm.add_command(vm_remove)
|
||||||
cli_vm.add_command(vm_dump)
|
cli_vm.add_command(vm_dump)
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import lxml.objectify
|
import lxml.objectify
|
||||||
|
import lxml.etree
|
||||||
|
|
||||||
import daemon_lib.zkhandler as zkhandler
|
import daemon_lib.zkhandler as zkhandler
|
||||||
import daemon_lib.common as common
|
import daemon_lib.common as common
|
||||||
@ -299,6 +300,55 @@ def dump_vm(zk_conn, domain):
|
|||||||
return True, vm_xml
|
return True, vm_xml
|
||||||
|
|
||||||
|
|
||||||
|
def rename_vm(zk_conn, domain, new_domain):
|
||||||
|
dom_uuid = getDomainUUID(zk_conn, domain)
|
||||||
|
if not dom_uuid:
|
||||||
|
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
|
||||||
|
|
||||||
|
# Verify that the VM is in a stopped state; renaming is not supported otherwise
|
||||||
|
state = zkhandler.readdata(zk_conn, '/domains/{}/state'.format(dom_uuid))
|
||||||
|
if state != 'stop':
|
||||||
|
return False, 'ERROR: VM "{}" is not in stopped state; VMs cannot be renamed while running.'.format(domain)
|
||||||
|
|
||||||
|
# Parse and valiate the XML
|
||||||
|
vm_config = common.getDomainXML(zk_conn, dom_uuid)
|
||||||
|
|
||||||
|
# Obtain the RBD disk list using the common functions
|
||||||
|
ddisks = common.getDomainDisks(vm_config, {})
|
||||||
|
pool_list = []
|
||||||
|
rbd_list = []
|
||||||
|
for disk in ddisks:
|
||||||
|
if disk['type'] == 'rbd':
|
||||||
|
pool_list.append(disk['name'].split('/')[0])
|
||||||
|
rbd_list.append(disk['name'].split('/')[1])
|
||||||
|
|
||||||
|
# Rename each volume in turn
|
||||||
|
for idx, rbd in enumerate(rbd_list):
|
||||||
|
rbd_new = re.sub(r"{}".format(domain), new_domain, rbd)
|
||||||
|
# Skip renaming if nothing changed
|
||||||
|
if rbd_new == rbd:
|
||||||
|
continue
|
||||||
|
ceph.rename_volume(zk_conn, pool_list[idx], rbd, rbd_new)
|
||||||
|
|
||||||
|
# Replace the name in the config
|
||||||
|
vm_config_new = lxml.etree.tostring(vm_config, encoding='ascii', method='xml').decode().replace(domain, new_domain)
|
||||||
|
|
||||||
|
# Get VM information
|
||||||
|
_b, dom_info = get_info(zk_conn, dom_uuid)
|
||||||
|
|
||||||
|
# Undefine the old VM
|
||||||
|
undefine_vm(zk_conn, dom_uuid)
|
||||||
|
|
||||||
|
# Define the new VM
|
||||||
|
define_vm(zk_conn, vm_config_new, dom_info['node'], dom_info['node_limit'], dom_info['node_selector'], dom_info['node_autostart'], migration_method=dom_info['migration_method'], profile=dom_info['profile'], initial_state='stop')
|
||||||
|
|
||||||
|
# If the VM is migrated, store that
|
||||||
|
if dom_info['migrated'] != 'no':
|
||||||
|
zkhandler.writedata(zk_conn, {'/domains/{}/lastnode'.format(dom_uuid): dom_info['last_node']})
|
||||||
|
|
||||||
|
return True, 'Successfully renamed VM "{}" to "{}".'.format(domain, new_domain)
|
||||||
|
|
||||||
|
|
||||||
def undefine_vm(zk_conn, domain):
|
def undefine_vm(zk_conn, domain):
|
||||||
# Validate that VM exists in cluster
|
# Validate that VM exists in cluster
|
||||||
dom_uuid = getDomainUUID(zk_conn, domain)
|
dom_uuid = getDomainUUID(zk_conn, domain)
|
||||||
|
30
debian/changelog
vendored
30
debian/changelog
vendored
@ -1,3 +1,33 @@
|
|||||||
|
pvc (0.9.18-0) unstable; urgency=high
|
||||||
|
|
||||||
|
* Adds VM rename functionality to API and CLI client
|
||||||
|
|
||||||
|
-- Joshua M. Boniface <joshua@boniface.me> Sun, 23 May 2021 17:23:10 -0400
|
||||||
|
|
||||||
|
pvc (0.9.17-0) unstable; urgency=high
|
||||||
|
|
||||||
|
* [CLI] Fixes bugs in log follow output
|
||||||
|
|
||||||
|
-- Joshua M. Boniface <joshua@boniface.me> Wed, 19 May 2021 17:06:29 -0400
|
||||||
|
|
||||||
|
pvc (0.9.16-0) unstable; urgency=high
|
||||||
|
|
||||||
|
* Improves some CLI help messages
|
||||||
|
* Skips empty local cluster in CLI
|
||||||
|
* Adjusts how confirmations happen during VM modify restarts
|
||||||
|
* Fixes bug around corrupted VM log files
|
||||||
|
* Fixes bug around subprocess pipe exceptions
|
||||||
|
|
||||||
|
-- Joshua M. Boniface <joshua@boniface.me> Mon, 10 May 2021 01:13:21 -0400
|
||||||
|
|
||||||
|
pvc (0.9.15-0) unstable; urgency=high
|
||||||
|
|
||||||
|
* [CLI] Adds additional verification (--yes) to several VM management commands
|
||||||
|
* [CLI] Adds a method to override --yes/confirmation requirements via envvar (PVC_UNSAFE)
|
||||||
|
* [CLI] Adds description fields to PVC clusters in CLI
|
||||||
|
|
||||||
|
-- Joshua M. Boniface <joshua@boniface.me> Thu, 08 Apr 2021 13:37:47 -0400
|
||||||
|
|
||||||
pvc (0.9.14-0) unstable; urgency=high
|
pvc (0.9.14-0) unstable; urgency=high
|
||||||
|
|
||||||
* Fixes bugs around cloned volume provisioning
|
* Fixes bugs around cloned volume provisioning
|
||||||
|
@ -18,6 +18,28 @@ To get started with PVC, please see the [About](https://parallelvirtualcluster.r
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
#### v0.9.18
|
||||||
|
|
||||||
|
* Adds VM rename functionality to API and CLI client
|
||||||
|
|
||||||
|
#### v0.9.17
|
||||||
|
|
||||||
|
* [CLI] Fixes bugs in log follow output
|
||||||
|
|
||||||
|
#### v0.9.16
|
||||||
|
|
||||||
|
* Improves some CLI help messages
|
||||||
|
* Skips empty local cluster in CLI
|
||||||
|
* Adjusts how confirmations happen during VM modify restarts
|
||||||
|
* Fixes bug around corrupted VM log files
|
||||||
|
* Fixes bug around subprocess pipe exceptions
|
||||||
|
|
||||||
|
#### v0.9.15
|
||||||
|
|
||||||
|
* [CLI] Adds additional verification (--yes) to several VM management commands
|
||||||
|
* [CLI] Adds a method to override --yes/confirmation requirements via envvar (PVC_UNSAFE)
|
||||||
|
* [CLI] Adds description fields to PVC clusters in CLI
|
||||||
|
|
||||||
#### v0.9.14
|
#### v0.9.14
|
||||||
|
|
||||||
* Fixes bugs around cloned volume provisioning
|
* Fixes bugs around cloned volume provisioning
|
||||||
|
@ -6035,6 +6035,38 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/vm/{vm}/rename": {
|
||||||
|
"post": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "The new name of the VM",
|
||||||
|
"in": "query",
|
||||||
|
"name": "new_name",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Message"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Rename VM {vm}, and all connected disk volumes which include this name, to {new_name}",
|
||||||
|
"tags": [
|
||||||
|
"vm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/vm/{vm}/state": {
|
"/api/v1/vm/{vm}/state": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "",
|
"description": "",
|
||||||
|
@ -53,7 +53,7 @@ import pvcnoded.CephInstance as CephInstance
|
|||||||
import pvcnoded.MetadataAPIInstance as MetadataAPIInstance
|
import pvcnoded.MetadataAPIInstance as MetadataAPIInstance
|
||||||
|
|
||||||
# Version string for startup output
|
# Version string for startup output
|
||||||
version = '0.9.14'
|
version = '0.9.18'
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# PVCD - node daemon startup program
|
# PVCD - node daemon startup program
|
||||||
|
@ -44,7 +44,14 @@ class VMConsoleWatcherInstance(object):
|
|||||||
open(self.logfile, 'a').close()
|
open(self.logfile, 'a').close()
|
||||||
os.chmod(self.logfile, 0o600)
|
os.chmod(self.logfile, 0o600)
|
||||||
|
|
||||||
self.logdeque = deque(open(self.logfile), self.console_log_lines)
|
try:
|
||||||
|
self.logdeque = deque(open(self.logfile), self.console_log_lines)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# There is corruption in the log file; overwrite it
|
||||||
|
self.logger.out('Failed to decode console log file; clearing existing file', state='w', prefix='Domain {}'.format(self.domuuid))
|
||||||
|
with open(self.logfile, 'w') as lfh:
|
||||||
|
lfh.write('\n')
|
||||||
|
self.logdeque = deque(open(self.logfile), self.console_log_lines)
|
||||||
|
|
||||||
self.stamp = None
|
self.stamp = None
|
||||||
self.cached_stamp = None
|
self.cached_stamp = None
|
||||||
|
@ -91,6 +91,8 @@ def run_os_command(command_string, background=False, environment=None, timeout=N
|
|||||||
retcode = command_output.returncode
|
retcode = command_output.returncode
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
retcode = 128
|
retcode = 128
|
||||||
|
except Exception:
|
||||||
|
retcode = 255
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout = command_output.stdout.decode('ascii')
|
stdout = command_output.stdout.decode('ascii')
|
||||||
|
Reference in New Issue
Block a user