Implement VM rename functionality

Closes #125
This commit is contained in:
Joshua Boniface 2021-05-23 16:41:42 -04:00
parent 9de14c46fb
commit f46c2e7f6a
6 changed files with 204 additions and 0 deletions

View File

@ -1804,6 +1804,45 @@ class API_VM_Console(Resource):
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
##########################################################

View File

@ -601,6 +601,37 @@ def vm_modify(name, restart, xml):
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):
"""
Undefine a VM from the PVC cluster.

View File

@ -130,6 +130,27 @@ def vm_modify(config, vm, xml, restart):
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):
"""
Modify PVC metadata of a VM

View File

@ -790,6 +790,36 @@ def vm_modify(domain, cfgfile, editor, restart, confirm_flag):
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)
###############################################################################
# pvc vm undefine
###############################################################################
@ -4395,6 +4425,7 @@ vm_volume.add_command(vm_volume_remove)
cli_vm.add_command(vm_define)
cli_vm.add_command(vm_meta)
cli_vm.add_command(vm_modify)
cli_vm.add_command(vm_rename)
cli_vm.add_command(vm_undefine)
cli_vm.add_command(vm_remove)
cli_vm.add_command(vm_dump)

View File

@ -22,6 +22,7 @@
import time
import re
import lxml.objectify
import lxml.etree
import daemon_lib.zkhandler as zkhandler
import daemon_lib.common as common
@ -299,6 +300,55 @@ def dump_vm(zk_conn, domain):
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):
# Validate that VM exists in cluster
dom_uuid = getDomainUUID(zk_conn, domain)

View File

@ -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": {
"get": {
"description": "",