Revamp tag handling and display
Add an additional protected class, limit manipulation to one at a time, and ensure future flexibility. Also makes display consistent with other VM elements.
This commit is contained in:
@ -78,12 +78,12 @@ def vm_list(config, limit, target_node, target_state):
|
||||
return False, response.json().get('message', '')
|
||||
|
||||
|
||||
def vm_define(config, xml, node, node_limit, node_selector, node_autostart, migration_method):
|
||||
def vm_define(config, xml, node, node_limit, node_selector, node_autostart, migration_method, user_tags, protected_tags):
|
||||
"""
|
||||
Define a new VM on the cluster
|
||||
|
||||
API endpoint: POST /vm
|
||||
API arguments: xml={xml}, node={node}, limit={node_limit}, selector={node_selector}, autostart={node_autostart}, migration_method={migration_method}
|
||||
API arguments: xml={xml}, node={node}, limit={node_limit}, selector={node_selector}, autostart={node_autostart}, migration_method={migration_method}, user_tags={user_tags}, protected_tags={protected_tags}
|
||||
API schema: {"message":"{data}"}
|
||||
"""
|
||||
params = {
|
||||
@ -91,7 +91,9 @@ def vm_define(config, xml, node, node_limit, node_selector, node_autostart, migr
|
||||
'limit': node_limit,
|
||||
'selector': node_selector,
|
||||
'autostart': node_autostart,
|
||||
'migration_method': migration_method
|
||||
'migration_method': migration_method,
|
||||
'user_tags': user_tags,
|
||||
'protected_tags': protected_tags
|
||||
}
|
||||
data = {
|
||||
'xml': xml
|
||||
@ -155,7 +157,7 @@ def vm_metadata(config, vm, node_limit, node_selector, node_autostart, migration
|
||||
"""
|
||||
Modify PVC metadata of a VM
|
||||
|
||||
API endpoint: GET /vm/{vm}/meta, POST /vm/{vm}/meta
|
||||
API endpoint: POST /vm/{vm}/meta
|
||||
API arguments: limit={node_limit}, selector={node_selector}, autostart={node_autostart}, migration_method={migration_method} profile={provisioner_profile}
|
||||
API schema: {"message":"{data}"}
|
||||
"""
|
||||
@ -188,6 +190,119 @@ def vm_metadata(config, vm, node_limit, node_selector, node_autostart, migration
|
||||
return retstatus, response.json().get('message', '')
|
||||
|
||||
|
||||
def vm_tags_get(config, vm):
|
||||
"""
|
||||
Get PVC tags of a VM
|
||||
|
||||
API endpoint: GET /vm/{vm}/tags
|
||||
API arguments:
|
||||
API schema: {{"name": "{name}", "type": "{type}"},...}
|
||||
"""
|
||||
|
||||
response = call_api(config, 'get', '/vm/{vm}/tags'.format(vm=vm))
|
||||
|
||||
if response.status_code == 200:
|
||||
retstatus = True
|
||||
retdata = response.json()
|
||||
else:
|
||||
retstatus = False
|
||||
retdata = response.json().get('message', '')
|
||||
|
||||
return retstatus, retdata
|
||||
|
||||
|
||||
def vm_tag_set(config, vm, action, tag, protected=False):
|
||||
"""
|
||||
Modify PVC tags of a VM
|
||||
|
||||
API endpoint: POST /vm/{vm}/tags
|
||||
API arguments: action={action}, tag={tag}, protected={protected}
|
||||
API schema: {"message":"{data}"}
|
||||
"""
|
||||
|
||||
params = {
|
||||
'action': action,
|
||||
'tag': tag,
|
||||
'protected': protected
|
||||
}
|
||||
|
||||
# Update the tags
|
||||
response = call_api(config, 'post', '/vm/{vm}/tags'.format(vm=vm), params=params)
|
||||
|
||||
if response.status_code == 200:
|
||||
retstatus = True
|
||||
else:
|
||||
retstatus = False
|
||||
|
||||
return retstatus, response.json().get('message', '')
|
||||
|
||||
|
||||
def format_vm_tags(config, name, tags):
|
||||
"""
|
||||
Format the output of a tags dictionary in a nice table
|
||||
"""
|
||||
if len(tags) < 1:
|
||||
return "No tags found."
|
||||
|
||||
output_list = []
|
||||
|
||||
name_length = 5
|
||||
_name_length = len(name) + 1
|
||||
if _name_length > name_length:
|
||||
name_length = _name_length
|
||||
|
||||
tags_name_length = 4
|
||||
tags_type_length = 5
|
||||
tags_protected_length = 10
|
||||
for tag in tags:
|
||||
_tags_name_length = len(tag['name']) + 1
|
||||
if _tags_name_length > tags_name_length:
|
||||
tags_name_length = _tags_name_length
|
||||
|
||||
_tags_type_length = len(tag['type']) + 1
|
||||
if _tags_type_length > tags_type_length:
|
||||
tags_type_length = _tags_type_length
|
||||
|
||||
_tags_protected_length = len(str(tag['protected'])) + 1
|
||||
if _tags_protected_length > tags_protected_length:
|
||||
tags_protected_length = _tags_protected_length
|
||||
|
||||
output_list.append(
|
||||
'{bold}{tags_name: <{tags_name_length}} \
|
||||
{tags_type: <{tags_type_length}} \
|
||||
{tags_protected: <{tags_protected_length}}{end_bold}'.format(
|
||||
name_length=name_length,
|
||||
tags_name_length=tags_name_length,
|
||||
tags_type_length=tags_type_length,
|
||||
tags_protected_length=tags_protected_length,
|
||||
bold=ansiprint.bold(),
|
||||
end_bold=ansiprint.end(),
|
||||
tags_name='Name',
|
||||
tags_type='Type',
|
||||
tags_protected='Protected'
|
||||
)
|
||||
)
|
||||
|
||||
for tag in sorted(tags, key=lambda t: t['name']):
|
||||
output_list.append(
|
||||
'{bold}{tags_name: <{tags_name_length}} \
|
||||
{tags_type: <{tags_type_length}} \
|
||||
{tags_protected: <{tags_protected_length}}{end_bold}'.format(
|
||||
name_length=name_length,
|
||||
tags_type_length=tags_type_length,
|
||||
tags_name_length=tags_name_length,
|
||||
tags_protected_length=tags_protected_length,
|
||||
bold='',
|
||||
end_bold='',
|
||||
tags_name=tag['name'],
|
||||
tags_type=tag['type'],
|
||||
tags_protected=str(tag['protected'])
|
||||
)
|
||||
)
|
||||
|
||||
return '\n'.join(output_list)
|
||||
|
||||
|
||||
def vm_remove(config, vm, delete_disks=False):
|
||||
"""
|
||||
Remove a VM
|
||||
@ -1248,6 +1363,46 @@ def format_info(config, domain_information, long_output):
|
||||
ainformation.append('{}Autostart:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_node_autostart))
|
||||
ainformation.append('{}Migration Method:{} {}'.format(ansiprint.purple(), ansiprint.end(), formatted_migration_method))
|
||||
|
||||
# Tag list
|
||||
tags_name_length = 5
|
||||
tags_type_length = 5
|
||||
tags_protected_length = 10
|
||||
for tag in domain_information['tags']:
|
||||
_tags_name_length = len(tag['name']) + 1
|
||||
if _tags_name_length > tags_name_length:
|
||||
tags_name_length = _tags_name_length
|
||||
|
||||
_tags_type_length = len(tag['type']) + 1
|
||||
if _tags_type_length > tags_type_length:
|
||||
tags_type_length = _tags_type_length
|
||||
|
||||
_tags_protected_length = len(str(tag['protected'])) + 1
|
||||
if _tags_protected_length > tags_protected_length:
|
||||
tags_protected_length = _tags_protected_length
|
||||
|
||||
ainformation.append('')
|
||||
ainformation.append('{purple}Tags:{end} {bold}{tags_name: <{tags_name_length}} {tags_type: <{tags_type_length}} {tags_protected: <{tags_protected_length}}{end}'.format(
|
||||
purple=ansiprint.purple(),
|
||||
bold=ansiprint.bold(),
|
||||
end=ansiprint.end(),
|
||||
tags_name_length=tags_name_length,
|
||||
tags_type_length=tags_type_length,
|
||||
tags_protected_length=tags_protected_length,
|
||||
tags_name='Name',
|
||||
tags_type='Type',
|
||||
tags_protected='Protected'
|
||||
))
|
||||
|
||||
for tag in sorted(domain_information['tags'], key=lambda t: t['type'] + t['name']):
|
||||
ainformation.append(' {tags_name: <{tags_name_length}} {tags_type: <{tags_type_length}} {tags_protected: <{tags_protected_length}}'.format(
|
||||
tags_name_length=tags_name_length,
|
||||
tags_type_length=tags_type_length,
|
||||
tags_protected_length=tags_protected_length,
|
||||
tags_name=tag['name'],
|
||||
tags_type=tag['type'],
|
||||
tags_protected=str(tag['protected'])
|
||||
))
|
||||
|
||||
# Network list
|
||||
net_list = []
|
||||
cluster_net_list = call_api(config, 'get', '/network').json()
|
||||
@ -1331,6 +1486,14 @@ def format_list(config, vm_list, raw):
|
||||
net_list.append(net['vni'])
|
||||
return net_list
|
||||
|
||||
# Function to get tag names and returna nicer list
|
||||
def getNiceTagName(domain_information):
|
||||
# Tag list
|
||||
tag_list = []
|
||||
for tag in sorted(domain_information['tags'], key=lambda t: t['type'] + t['name']):
|
||||
tag_list.append(tag['name'])
|
||||
return tag_list
|
||||
|
||||
# Handle raw mode since it just lists the names
|
||||
if raw:
|
||||
ainformation = list()
|
||||
@ -1344,6 +1507,7 @@ def format_list(config, vm_list, raw):
|
||||
# Dynamic columns: node_name, node, migrated
|
||||
vm_name_length = 5
|
||||
vm_state_length = 6
|
||||
vm_tags_length = 5
|
||||
vm_nets_length = 9
|
||||
vm_ram_length = 8
|
||||
vm_vcpu_length = 6
|
||||
@ -1351,6 +1515,7 @@ def format_list(config, vm_list, raw):
|
||||
vm_migrated_length = 9
|
||||
for domain_information in vm_list:
|
||||
net_list = getNiceNetID(domain_information)
|
||||
tag_list = getNiceTagName(domain_information)
|
||||
# vm_name column
|
||||
_vm_name_length = len(domain_information['name']) + 1
|
||||
if _vm_name_length > vm_name_length:
|
||||
@ -1359,6 +1524,10 @@ def format_list(config, vm_list, raw):
|
||||
_vm_state_length = len(domain_information['state']) + 1
|
||||
if _vm_state_length > vm_state_length:
|
||||
vm_state_length = _vm_state_length
|
||||
# vm_tags column
|
||||
_vm_tags_length = len(','.join(tag_list)) + 1
|
||||
if _vm_tags_length > vm_tags_length:
|
||||
vm_tags_length = _vm_tags_length
|
||||
# vm_nets column
|
||||
_vm_nets_length = len(','.join(net_list)) + 1
|
||||
if _vm_nets_length > vm_nets_length:
|
||||
@ -1375,12 +1544,12 @@ def format_list(config, vm_list, raw):
|
||||
# Format the string (header)
|
||||
vm_list_output.append(
|
||||
'{bold}{vm_header: <{vm_header_length}} {resource_header: <{resource_header_length}} {node_header: <{node_header_length}}{end_bold}'.format(
|
||||
vm_header_length=vm_name_length + vm_state_length + 1,
|
||||
vm_header_length=vm_name_length + vm_state_length + vm_tags_length + 2,
|
||||
resource_header_length=vm_nets_length + vm_ram_length + vm_vcpu_length + 2,
|
||||
node_header_length=vm_node_length + vm_migrated_length + 1,
|
||||
bold=ansiprint.bold(),
|
||||
end_bold=ansiprint.end(),
|
||||
vm_header='VMs ' + ''.join(['-' for _ in range(4, vm_name_length + vm_state_length)]),
|
||||
vm_header='VMs ' + ''.join(['-' for _ in range(4, vm_name_length + vm_state_length + vm_tags_length + 1)]),
|
||||
resource_header='Resources ' + ''.join(['-' for _ in range(10, vm_nets_length + vm_ram_length + vm_vcpu_length + 1)]),
|
||||
node_header='Node ' + ''.join(['-' for _ in range(5, vm_node_length + vm_migrated_length)])
|
||||
)
|
||||
@ -1389,12 +1558,14 @@ def format_list(config, vm_list, raw):
|
||||
vm_list_output.append(
|
||||
'{bold}{vm_name: <{vm_name_length}} \
|
||||
{vm_state_colour}{vm_state: <{vm_state_length}}{end_colour} \
|
||||
{vm_tags: <{vm_tags_length}} \
|
||||
{vm_networks: <{vm_nets_length}} \
|
||||
{vm_memory: <{vm_ram_length}} {vm_vcpu: <{vm_vcpu_length}} \
|
||||
{vm_node: <{vm_node_length}} \
|
||||
{vm_migrated: <{vm_migrated_length}}{end_bold}'.format(
|
||||
vm_name_length=vm_name_length,
|
||||
vm_state_length=vm_state_length,
|
||||
vm_tags_length=vm_tags_length,
|
||||
vm_nets_length=vm_nets_length,
|
||||
vm_ram_length=vm_ram_length,
|
||||
vm_vcpu_length=vm_vcpu_length,
|
||||
@ -1406,6 +1577,7 @@ def format_list(config, vm_list, raw):
|
||||
end_colour='',
|
||||
vm_name='Name',
|
||||
vm_state='State',
|
||||
vm_tags='Tags',
|
||||
vm_networks='Networks',
|
||||
vm_memory='RAM (M)',
|
||||
vm_vcpu='vCPUs',
|
||||
@ -1434,6 +1606,9 @@ def format_list(config, vm_list, raw):
|
||||
|
||||
# Handle colouring for an invalid network config
|
||||
net_list = getNiceNetID(domain_information)
|
||||
tag_list = getNiceTagName(domain_information)
|
||||
if len(tag_list) < 1:
|
||||
tag_list = ['N/A']
|
||||
vm_net_colour = ''
|
||||
for net_vni in net_list:
|
||||
if net_vni not in ['cluster', 'storage', 'upstream'] and not re.match(r'^macvtap:.*', net_vni) and not re.match(r'^hostdev:.*', net_vni):
|
||||
@ -1443,12 +1618,14 @@ def format_list(config, vm_list, raw):
|
||||
vm_list_output.append(
|
||||
'{bold}{vm_name: <{vm_name_length}} \
|
||||
{vm_state_colour}{vm_state: <{vm_state_length}}{end_colour} \
|
||||
{vm_tags: <{vm_tags_length}} \
|
||||
{vm_net_colour}{vm_networks: <{vm_nets_length}}{end_colour} \
|
||||
{vm_memory: <{vm_ram_length}} {vm_vcpu: <{vm_vcpu_length}} \
|
||||
{vm_node: <{vm_node_length}} \
|
||||
{vm_migrated: <{vm_migrated_length}}{end_bold}'.format(
|
||||
vm_name_length=vm_name_length,
|
||||
vm_state_length=vm_state_length,
|
||||
vm_tags_length=vm_tags_length,
|
||||
vm_nets_length=vm_nets_length,
|
||||
vm_ram_length=vm_ram_length,
|
||||
vm_vcpu_length=vm_vcpu_length,
|
||||
@ -1460,6 +1637,7 @@ def format_list(config, vm_list, raw):
|
||||
end_colour=ansiprint.end(),
|
||||
vm_name=domain_information['name'],
|
||||
vm_state=domain_information['state'],
|
||||
vm_tags=','.join(tag_list),
|
||||
vm_net_colour=vm_net_colour,
|
||||
vm_networks=','.join(net_list),
|
||||
vm_memory=domain_information['memory'],
|
||||
|
@ -638,11 +638,21 @@ def cli_vm():
|
||||
type=click.Choice(['none', 'live', 'shutdown']),
|
||||
help='The preferred migration method of the VM between nodes; saved with VM.'
|
||||
)
|
||||
@click.option(
|
||||
'-g', '--tag', 'user_tags',
|
||||
default=[], multiple=True,
|
||||
help='User tag(s) for the VM.'
|
||||
)
|
||||
@click.option(
|
||||
'-G', '--protected-tag', 'protected_tags',
|
||||
default=[], multiple=True,
|
||||
help='Protected user tag(s) for the VM.'
|
||||
)
|
||||
@click.argument(
|
||||
'vmconfig', type=click.File()
|
||||
)
|
||||
@cluster_req
|
||||
def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart, migration_method):
|
||||
def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart, migration_method, user_tags, protected_tags):
|
||||
"""
|
||||
Define a new virtual machine from Libvirt XML configuration file VMCONFIG.
|
||||
"""
|
||||
@ -658,7 +668,7 @@ def vm_define(vmconfig, target_node, node_limit, node_selector, node_autostart,
|
||||
except Exception:
|
||||
cleanup(False, 'Error: XML is malformed or invalid')
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_define(config, new_cfg, target_node, node_limit, node_selector, node_autostart, migration_method)
|
||||
retcode, retmsg = pvc_vm.vm_define(config, new_cfg, target_node, node_limit, node_selector, node_autostart, migration_method, user_tags, protected_tags)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
@ -1111,6 +1121,90 @@ def vm_flush_locks(domain):
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm tag
|
||||
###############################################################################
|
||||
@click.group(name='tag', short_help='Manage tags of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||
def vm_tags():
|
||||
"""
|
||||
Manage the tags of a virtual machine in the PVC cluster."
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm tag get
|
||||
###############################################################################
|
||||
@click.command(name='get', short_help='Get the current tags of a virtual machine.')
|
||||
@click.argument(
|
||||
'domain'
|
||||
)
|
||||
@click.option(
|
||||
'-r', '--raw', 'raw', is_flag=True, default=False,
|
||||
help='Display the raw value only without formatting.'
|
||||
)
|
||||
@cluster_req
|
||||
def vm_tags_get(domain, raw):
|
||||
"""
|
||||
Get the current tags of the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retdata = pvc_vm.vm_tags_get(config, domain)
|
||||
if retcode:
|
||||
if not raw:
|
||||
retdata = pvc_vm.format_vm_tags(config, domain, retdata['tags'])
|
||||
else:
|
||||
if len(retdata['tags']) > 0:
|
||||
retdata = '\n'.join([tag['name'] for tag in retdata['tags']])
|
||||
else:
|
||||
retdata = 'No tags found.'
|
||||
cleanup(retcode, retdata)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm tag add
|
||||
###############################################################################
|
||||
@click.command(name='add', short_help='Add new tags to a virtual machine.')
|
||||
@click.argument(
|
||||
'domain'
|
||||
)
|
||||
@click.argument(
|
||||
'tag'
|
||||
)
|
||||
@click.option(
|
||||
'-p', '--protected', 'protected', is_flag=True, required=False, default=False,
|
||||
help="Set this tag as protected; protected tags cannot be removed."
|
||||
)
|
||||
@cluster_req
|
||||
def vm_tags_add(domain, tag, protected):
|
||||
"""
|
||||
Add TAG to the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_tag_set(config, domain, 'add', tag, protected)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm tag remove
|
||||
###############################################################################
|
||||
@click.command(name='remove', short_help='Remove tags from a virtual machine.')
|
||||
@click.argument(
|
||||
'domain'
|
||||
)
|
||||
@click.argument(
|
||||
'tag'
|
||||
)
|
||||
@cluster_req
|
||||
def vm_tags_remove(domain, tag):
|
||||
"""
|
||||
Remove TAG from the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_tag_set(config, domain, 'remove', tag)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm vcpu
|
||||
###############################################################################
|
||||
@ -4612,6 +4706,10 @@ cli_node.add_command(node_unflush)
|
||||
cli_node.add_command(node_info)
|
||||
cli_node.add_command(node_list)
|
||||
|
||||
vm_tags.add_command(vm_tags_get)
|
||||
vm_tags.add_command(vm_tags_add)
|
||||
vm_tags.add_command(vm_tags_remove)
|
||||
|
||||
vm_vcpu.add_command(vm_vcpu_get)
|
||||
vm_vcpu.add_command(vm_vcpu_set)
|
||||
|
||||
@ -4642,6 +4740,7 @@ cli_vm.add_command(vm_move)
|
||||
cli_vm.add_command(vm_migrate)
|
||||
cli_vm.add_command(vm_unmigrate)
|
||||
cli_vm.add_command(vm_flush_locks)
|
||||
cli_vm.add_command(vm_tags)
|
||||
cli_vm.add_command(vm_vcpu)
|
||||
cli_vm.add_command(vm_memory)
|
||||
cli_vm.add_command(vm_network)
|
||||
|
Reference in New Issue
Block a user