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:
parent
27f1758791
commit
9ea9ac3b8a
|
@ -892,6 +892,22 @@ class API_VM_Root(Resource):
|
|||
migration_method:
|
||||
type: string
|
||||
description: The preferred migration method (live, shutdown, none)
|
||||
tags:
|
||||
type: array
|
||||
description: The tag(s) of the VM
|
||||
items:
|
||||
type: object
|
||||
id: VMTag
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of the tag
|
||||
type:
|
||||
type: string
|
||||
description: The type of the tag (user, system)
|
||||
protected:
|
||||
type: boolean
|
||||
description: Whether the tag is protected or not
|
||||
description:
|
||||
type: string
|
||||
description: The description of the VM
|
||||
|
@ -1107,7 +1123,8 @@ class API_VM_Root(Resource):
|
|||
{'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms', 'none'), 'helptext': "A valid selector must be specified"},
|
||||
{'name': 'autostart'},
|
||||
{'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified"},
|
||||
{'name': 'tags'},
|
||||
{'name': 'user_tags', 'action': 'append'},
|
||||
{'name': 'protected_tags', 'action': 'append'},
|
||||
{'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified"},
|
||||
])
|
||||
@Authenticator
|
||||
|
@ -1160,10 +1177,17 @@ class API_VM_Root(Resource):
|
|||
- shutdown
|
||||
- none
|
||||
- in: query
|
||||
name: tags
|
||||
name: user_tags
|
||||
type: array
|
||||
required: false
|
||||
description: The tag(s) of the VM
|
||||
description: The user tag(s) of the VM
|
||||
items:
|
||||
type: string
|
||||
- in: query
|
||||
name: protected_tags
|
||||
type: array
|
||||
required: false
|
||||
description: The protected user tag(s) of the VM
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
|
@ -1178,6 +1202,13 @@ class API_VM_Root(Resource):
|
|||
type: object
|
||||
id: Message
|
||||
"""
|
||||
user_tags = reqargs.get('user_tags', None)
|
||||
if user_tags is None:
|
||||
user_tags = []
|
||||
protected_tags = reqargs.get('protected_tags', None)
|
||||
if protected_tags is None:
|
||||
protected_tags = []
|
||||
|
||||
return api_helper.vm_define(
|
||||
reqargs.get('xml'),
|
||||
reqargs.get('node', None),
|
||||
|
@ -1185,7 +1216,8 @@ class API_VM_Root(Resource):
|
|||
reqargs.get('selector', 'none'),
|
||||
bool(strtobool(reqargs.get('autostart', 'false'))),
|
||||
reqargs.get('migration_method', 'none'),
|
||||
reqargs.get('tags', [])
|
||||
user_tags,
|
||||
protected_tags
|
||||
)
|
||||
|
||||
|
||||
|
@ -1220,7 +1252,8 @@ class API_VM_Element(Resource):
|
|||
{'name': 'selector', 'choices': ('mem', 'vcpus', 'load', 'vms', 'none'), 'helptext': "A valid selector must be specified"},
|
||||
{'name': 'autostart'},
|
||||
{'name': 'migration_method', 'choices': ('live', 'shutdown', 'none'), 'helptext': "A valid migration_method must be specified"},
|
||||
{'name': 'tags'},
|
||||
{'name': 'user_tags', 'action': 'append'},
|
||||
{'name': 'protected_tags', 'action': 'append'},
|
||||
{'name': 'xml', 'required': True, 'helptext': "A Libvirt XML document must be specified"},
|
||||
])
|
||||
@Authenticator
|
||||
|
@ -1276,10 +1309,17 @@ class API_VM_Element(Resource):
|
|||
- shutdown
|
||||
- none
|
||||
- in: query
|
||||
name: tags
|
||||
name: user_tags
|
||||
type: array
|
||||
required: false
|
||||
description: The tag(s) of the VM
|
||||
description: The user tag(s) of the VM
|
||||
items:
|
||||
type: string
|
||||
- in: query
|
||||
name: protected_tags
|
||||
type: array
|
||||
required: false
|
||||
description: The protected user tag(s) of the VM
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
|
@ -1294,6 +1334,13 @@ class API_VM_Element(Resource):
|
|||
type: object
|
||||
id: Message
|
||||
"""
|
||||
user_tags = reqargs.get('user_tags', None)
|
||||
if user_tags is None:
|
||||
user_tags = []
|
||||
protected_tags = reqargs.get('protected_tags', None)
|
||||
if protected_tags is None:
|
||||
protected_tags = []
|
||||
|
||||
return api_helper.vm_define(
|
||||
reqargs.get('xml'),
|
||||
reqargs.get('node', None),
|
||||
|
@ -1301,7 +1348,8 @@ class API_VM_Element(Resource):
|
|||
reqargs.get('selector', 'none'),
|
||||
bool(strtobool(reqargs.get('autostart', 'false'))),
|
||||
reqargs.get('migration_method', 'none'),
|
||||
reqargs.get('tags', [])
|
||||
user_tags,
|
||||
protected_tags
|
||||
)
|
||||
|
||||
@RequestParser([
|
||||
|
@ -1529,7 +1577,8 @@ class API_VM_Tags(Resource):
|
|||
type: array
|
||||
description: The tag(s) of the VM
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
id: VMTag
|
||||
404:
|
||||
description: VM not found
|
||||
schema:
|
||||
|
@ -1539,8 +1588,9 @@ class API_VM_Tags(Resource):
|
|||
return api_helper.get_vm_tags(vm)
|
||||
|
||||
@RequestParser([
|
||||
{'name': 'action', 'choices': ('add', 'remove', 'replace'), 'helptext': "A valid action must be specified"},
|
||||
{'name': 'tags'},
|
||||
{'name': 'action', 'choices': ('add', 'remove'), 'helptext': "A valid action must be specified"},
|
||||
{'name': 'tag'},
|
||||
{'name': 'protected'}
|
||||
])
|
||||
@Authenticator
|
||||
def post(self, vm, reqargs):
|
||||
|
@ -1554,18 +1604,21 @@ class API_VM_Tags(Resource):
|
|||
name: action
|
||||
type: string
|
||||
required: true
|
||||
description: The action to perform with the tags, either "add" to existing, "remove" from existing, or "replace" all existing
|
||||
description: The action to perform with the tag
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
- replace
|
||||
- in: query
|
||||
name: tags
|
||||
type: array
|
||||
name: tag
|
||||
type: string
|
||||
required: true
|
||||
description: The list of text tags to add/remove/replace-with
|
||||
items:
|
||||
type: string
|
||||
description: The text value of the tag
|
||||
- in: query
|
||||
name: protected
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: Set the protected state of the tag
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
|
@ -1583,10 +1636,11 @@ class API_VM_Tags(Resource):
|
|||
type: object
|
||||
id: Message
|
||||
"""
|
||||
return api_helper.update_vm_tags(
|
||||
return api_helper.update_vm_tag(
|
||||
vm,
|
||||
reqargs.get('action'),
|
||||
reqargs.get('tags')
|
||||
reqargs.get('tag'),
|
||||
reqargs.get('protected', False)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -433,7 +433,7 @@ def vm_list(zkhandler, node=None, state=None, limit=None, is_fuzzy=True):
|
|||
|
||||
|
||||
@ZKConnection(config)
|
||||
def vm_define(zkhandler, xml, node, limit, selector, autostart, migration_method, tags=[]):
|
||||
def vm_define(zkhandler, xml, node, limit, selector, autostart, migration_method, user_tags=[], protected_tags=[]):
|
||||
"""
|
||||
Define a VM from Libvirt XML in the PVC cluster.
|
||||
"""
|
||||
|
@ -444,6 +444,12 @@ def vm_define(zkhandler, xml, node, limit, selector, autostart, migration_method
|
|||
except Exception as e:
|
||||
return {'message': 'XML is malformed or incorrect: {}'.format(e)}, 400
|
||||
|
||||
tags = list()
|
||||
for tag in user_tags:
|
||||
tags.append({'name': tag, 'type': 'user', 'protected': False})
|
||||
for tag in protected_tags:
|
||||
tags.append({'name': tag, 'type': 'user', 'protected': True})
|
||||
|
||||
retflag, retdata = pvc_vm.define_vm(zkhandler, new_cfg, node, limit, selector, autostart, migration_method, profile=None, tags=tags)
|
||||
|
||||
if retflag:
|
||||
|
@ -530,18 +536,18 @@ def get_vm_tags(zkhandler, vm):
|
|||
|
||||
|
||||
@ZKConnection(config)
|
||||
def update_vm_tags(zkhandler, vm, action, tags):
|
||||
def update_vm_tag(zkhandler, vm, action, tag, protected=False):
|
||||
"""
|
||||
Update the tags of a VM.
|
||||
Update a tag of a VM.
|
||||
"""
|
||||
if action not in ['add', 'remove', 'replace']:
|
||||
return {"message": "Tag action must be one of 'add', 'remove', 'replace'."}, 400
|
||||
if action not in ['add', 'remove']:
|
||||
return {"message": "Tag action must be one of 'add', 'remove'."}, 400
|
||||
|
||||
dom_uuid = pvc_vm.getDomainUUID(zkhandler, vm)
|
||||
if not dom_uuid:
|
||||
return {"message": "VM not found."}, 404
|
||||
|
||||
retflag, retdata = pvc_vm.modify_vm_tags(zkhandler, vm, action, tags)
|
||||
retflag, retdata = pvc_vm.modify_vm_tag(zkhandler, vm, action, tag, protected=protected)
|
||||
|
||||
if retflag:
|
||||
retcode = 200
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -310,7 +310,18 @@ def getDomainDiskList(zkhandler, dom_uuid):
|
|||
# Get a list of domain tags
|
||||
#
|
||||
def getDomainTags(zkhandler, dom_uuid):
|
||||
tags = zkhandler.read(('domain.meta.tags', dom_uuid)).split(',')
|
||||
"""
|
||||
Get a list of tags for domain dom_uuid
|
||||
|
||||
The UUID must be validated before calling this function!
|
||||
"""
|
||||
tags = list()
|
||||
|
||||
for tag in zkhandler.children(('domain.meta.tags', dom_uuid)):
|
||||
tag_type = zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.type', tag))
|
||||
protected = bool(strtobool(zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.protected', tag))))
|
||||
tags.append({'name': tag, 'type': tag_type, 'protected': protected})
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
|
@ -318,10 +329,15 @@ def getDomainTags(zkhandler, dom_uuid):
|
|||
# Get a set of domain metadata
|
||||
#
|
||||
def getDomainMetadata(zkhandler, dom_uuid):
|
||||
domain_node_limit = zkhandler.read(('domain.meta.node_limit', uuid))
|
||||
domain_node_selector = zkhandler.read(('domain.meta.node_selector', uuid))
|
||||
domain_node_autostart = zkhandler.read(('domain.meta.autostart', uuid))
|
||||
domain_migration_method = zkhandler.read(('domain.meta.migrate_method', uuid))
|
||||
"""
|
||||
Get the domain metadata for domain dom_uuid
|
||||
|
||||
The UUID must be validated before calling this function!
|
||||
"""
|
||||
domain_node_limit = zkhandler.read(('domain.meta.node_limit', dom_uuid))
|
||||
domain_node_selector = zkhandler.read(('domain.meta.node_selector', dom_uuid))
|
||||
domain_node_autostart = zkhandler.read(('domain.meta.autostart', dom_uuid))
|
||||
domain_migration_method = zkhandler.read(('domain.meta.migrate_method', dom_uuid))
|
||||
|
||||
if not domain_node_limit:
|
||||
domain_node_limit = None
|
||||
|
@ -348,7 +364,7 @@ def getInformationFromXML(zkhandler, uuid):
|
|||
domain_failedreason = zkhandler.read(('domain.failed_reason', uuid))
|
||||
|
||||
domain_node_limit, domain_node_selector, domain_node_autostart, domain_migration_method = getDomainMetadata(zkhandler, uuid)
|
||||
|
||||
domain_tags = getDomainTags(zkhandler, uuid)
|
||||
domain_profile = zkhandler.read(('domain.profile', uuid))
|
||||
|
||||
domain_vnc = zkhandler.read(('domain.console.vnc', uuid))
|
||||
|
@ -369,8 +385,6 @@ def getInformationFromXML(zkhandler, uuid):
|
|||
else:
|
||||
stats_data = {}
|
||||
|
||||
domain_tags = getDomainTags(zkhandler, uuid)
|
||||
|
||||
domain_uuid, domain_name, domain_description, domain_memory, domain_vcpu, domain_vcputopo = getDomainMainDetails(parsed_xml)
|
||||
domain_networks = getDomainNetworks(parsed_xml, stats_data)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"version": "3", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}}
|
|
@ -24,6 +24,7 @@ import re
|
|||
import lxml.objectify
|
||||
import lxml.etree
|
||||
|
||||
from distutils.util import strtobool
|
||||
from uuid import UUID
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
|
@ -246,10 +247,18 @@ def define_vm(zkhandler, config_data, target_node, node_limit, node_selector, no
|
|||
(('domain.meta.migrate_method', dom_uuid), migration_method),
|
||||
(('domain.meta.node_limit', dom_uuid), formatted_node_limit),
|
||||
(('domain.meta.node_selector', dom_uuid), node_selector),
|
||||
(('domain.meta.tags', dom_uuid), ','.join(tags)),
|
||||
(('domain.meta.tags', dom_uuid), ''),
|
||||
(('domain.migrate.sync_lock', dom_uuid), ''),
|
||||
])
|
||||
|
||||
for tag in tags:
|
||||
tag_name = tag['name']
|
||||
zkhandler.write([
|
||||
(('domain.meta.tags', dom_uuid, 'tag.name', tag_name), tag['name']),
|
||||
(('domain.meta.tags', dom_uuid, 'tag.type', tag_name), tag['type']),
|
||||
(('domain.meta.tags', dom_uuid, 'tag.protected', tag_name), tag['protected']),
|
||||
])
|
||||
|
||||
return True, 'Added new VM with Name "{}" and UUID "{}" to database.'.format(dom_name, dom_uuid)
|
||||
|
||||
|
||||
|
@ -283,34 +292,37 @@ def modify_vm_metadata(zkhandler, domain, node_limit, node_selector, node_autost
|
|||
return True, 'Successfully modified PVC metadata of VM "{}".'.format(domain)
|
||||
|
||||
|
||||
def modify_vm_tags(zkhandler, domain, action, tags):
|
||||
def modify_vm_tag(zkhandler, domain, action, tag, protected=False):
|
||||
dom_uuid = getDomainUUID(zkhandler, domain)
|
||||
if not dom_uuid:
|
||||
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
|
||||
|
||||
if action in ['replace']:
|
||||
if action == 'add':
|
||||
zkhandler.write([
|
||||
(('domain.meta.tags', dom_uuid), ','.join(tags))
|
||||
(('domain.meta.tags', dom_uuid, 'tag.name', tag), tag),
|
||||
(('domain.meta.tags', dom_uuid, 'tag.type', tag), 'user'),
|
||||
(('domain.meta.tags', dom_uuid, 'tag.protected', tag), protected),
|
||||
])
|
||||
elif action in ['add']:
|
||||
current_tags = zkhandler.read(('domain.meta.tags', dom_uuid)).split(',')
|
||||
updated_tags = current_tags + tags
|
||||
zkhandler.write([
|
||||
(('domain.meta.tags', dom_uuid), ','.join(updated_tags))
|
||||
])
|
||||
elif action in ['remove']:
|
||||
current_tags = zkhandler.read(('domain.meta.tags', dom_uuid)).split(',')
|
||||
for tag in tags:
|
||||
if tag in current_tags:
|
||||
current_tags.remove(tag)
|
||||
zkhandler.write([
|
||||
(('domain.meta.tags', dom_uuid), ','.join(current_tags))
|
||||
|
||||
return True, 'Successfully added tag "{}" to VM "{}".'.format(tag, domain)
|
||||
elif action == 'remove':
|
||||
if not zkhandler.exists(('domain.meta.tags', dom_uuid, 'tag', tag)):
|
||||
return False, 'The tag "{}" does not exist.'.format(tag)
|
||||
|
||||
if zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.type', tag)) != 'user':
|
||||
return False, 'The tag "{}" is not a user tag and cannot be removed.'.format(tag)
|
||||
|
||||
if bool(strtobool(zkhandler.read(('domain.meta.tags', dom_uuid, 'tag.protected', tag)))):
|
||||
return False, 'The tag "{}" is protected and cannot be removed.'.format(tag)
|
||||
|
||||
zkhandler.delete([
|
||||
(('domain.meta.tags', dom_uuid, 'tag', tag))
|
||||
])
|
||||
|
||||
return True, 'Successfully removed tag "{}" from VM "{}".'.format(tag, domain)
|
||||
else:
|
||||
return False, 'Specified tag action is not available.'
|
||||
|
||||
return True, 'Successfully modified tags of VM "{}".'.format(domain)
|
||||
|
||||
|
||||
def modify_vm(zkhandler, domain, restart, new_vm_config):
|
||||
dom_uuid = getDomainUUID(zkhandler, domain)
|
||||
|
@ -433,7 +445,7 @@ def rename_vm(zkhandler, domain, new_domain):
|
|||
undefine_vm(zkhandler, dom_uuid)
|
||||
|
||||
# Define the new VM
|
||||
define_vm(zkhandler, 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')
|
||||
define_vm(zkhandler, 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'], tags=dom_info['tags'], initial_state='stop')
|
||||
|
||||
# If the VM is migrated, store that
|
||||
if dom_info['migrated'] != 'no':
|
||||
|
|
|
@ -579,6 +579,12 @@ class ZKSchema(object):
|
|||
'meta.tags': '/tags',
|
||||
'migrate.sync_lock': '/migrate_sync_lock'
|
||||
},
|
||||
# The schema of an individual domain tag entry (/domains/{domain}/tags/{tag})
|
||||
'tag': {
|
||||
'name': '', # The root key
|
||||
'type': '/type',
|
||||
'protected': '/protected'
|
||||
},
|
||||
# The schema of an individual network entry (/networks/{vni})
|
||||
'network': {
|
||||
'vni': '', # The root key
|
||||
|
|
Loading…
Reference in New Issue