Add VM list filtering by tag

Uses same method as state or node filtering, rather than altering how
the main LIMIT field works.
This commit is contained in:
Joshua Boniface 2021-07-14 00:51:48 -04:00
parent 9ea9ac3b8a
commit 75fb60b1b4
8 changed files with 107 additions and 32 deletions

View File

@ -592,7 +592,7 @@ class API_Node_Root(Resource):
name: limit name: limit
type: string type: string
required: false required: false
description: A search limit; fuzzy by default, use ^/$ to force exact matches description: A search limit in the name, tags, or an exact UUID; fuzzy by default, use ^/$ to force exact matches
- in: query - in: query
name: daemon_state name: daemon_state
type: string type: string
@ -844,6 +844,7 @@ class API_VM_Root(Resource):
{'name': 'limit'}, {'name': 'limit'},
{'name': 'node'}, {'name': 'node'},
{'name': 'state'}, {'name': 'state'},
{'name': 'tag'},
]) ])
@Authenticator @Authenticator
def get(self, reqargs): def get(self, reqargs):
@ -1092,7 +1093,7 @@ class API_VM_Root(Resource):
name: limit name: limit
type: string type: string
required: false required: false
description: A name search limit; fuzzy by default, use ^/$ to force exact matches description: A search limit in the name, tags, or an exact UUID; fuzzy by default, use ^/$ to force exact matches
- in: query - in: query
name: node name: node
type: string type: string
@ -1103,6 +1104,11 @@ class API_VM_Root(Resource):
type: string type: string
required: false required: false
description: Limit list to VMs in this state description: Limit list to VMs in this state
- in: query
name: tag
type: string
required: false
description: Limit list to VMs with this tag
responses: responses:
200: 200:
description: OK description: OK
@ -1114,6 +1120,7 @@ class API_VM_Root(Resource):
return api_helper.vm_list( return api_helper.vm_list(
reqargs.get('node', None), reqargs.get('node', None),
reqargs.get('state', None), reqargs.get('state', None),
reqargs.get('tag', None),
reqargs.get('limit', None) reqargs.get('limit', None)
) )
@ -1244,7 +1251,7 @@ class API_VM_Element(Resource):
type: object type: object
id: Message id: Message
""" """
return api_helper.vm_list(None, None, vm, is_fuzzy=False) return api_helper.vm_list(None, None, None, vm, is_fuzzy=False)
@RequestParser([ @RequestParser([
{'name': 'limit'}, {'name': 'limit'},

View File

@ -326,7 +326,7 @@ def vm_state(zkhandler, vm):
""" """
Return the state of virtual machine VM. Return the state of virtual machine VM.
""" """
retflag, retdata = pvc_vm.get_list(zkhandler, None, None, vm, is_fuzzy=False) retflag, retdata = pvc_vm.get_list(zkhandler, None, None, None, vm, is_fuzzy=False)
if retflag: if retflag:
if retdata: if retdata:
@ -355,7 +355,7 @@ def vm_node(zkhandler, vm):
""" """
Return the current node of virtual machine VM. Return the current node of virtual machine VM.
""" """
retflag, retdata = pvc_vm.get_list(zkhandler, None, None, vm, is_fuzzy=False) retflag, retdata = pvc_vm.get_list(zkhandler, None, None, None, vm, is_fuzzy=False)
if retflag: if retflag:
if retdata: if retdata:
@ -409,11 +409,11 @@ def vm_console(zkhandler, vm, lines=None):
@pvc_common.Profiler(config) @pvc_common.Profiler(config)
@ZKConnection(config) @ZKConnection(config)
def vm_list(zkhandler, node=None, state=None, limit=None, is_fuzzy=True): def vm_list(zkhandler, node=None, state=None, tag=None, limit=None, is_fuzzy=True):
""" """
Return a list of VMs with limit LIMIT. Return a list of VMs with limit LIMIT.
""" """
retflag, retdata = pvc_vm.get_list(zkhandler, node, state, limit, is_fuzzy) retflag, retdata = pvc_vm.get_list(zkhandler, node, state, tag, limit, is_fuzzy)
if retflag: if retflag:
if retdata: if retdata:
@ -800,7 +800,7 @@ def vm_flush_locks(zkhandler, vm):
""" """
Flush locks of a (stopped) VM. Flush locks of a (stopped) VM.
""" """
retflag, retdata = pvc_vm.get_list(zkhandler, None, None, vm, is_fuzzy=False) retflag, retdata = pvc_vm.get_list(zkhandler, None, None, None, vm, is_fuzzy=False)
if retdata[0].get('state') not in ['stop', 'disable']: if retdata[0].get('state') not in ['stop', 'disable']:
return {"message": "VM must be stopped to flush locks"}, 400 return {"message": "VM must be stopped to flush locks"}, 400

View File

@ -54,12 +54,12 @@ def vm_info(config, vm):
return False, response.json().get('message', '') return False, response.json().get('message', '')
def vm_list(config, limit, target_node, target_state): def vm_list(config, limit, target_node, target_state, target_tag):
""" """
Get list information about VMs (limited by {limit}, {target_node}, or {target_state}) Get list information about VMs (limited by {limit}, {target_node}, or {target_state})
API endpoint: GET /api/v1/vm API endpoint: GET /api/v1/vm
API arguments: limit={limit}, node={target_node}, state={target_state} API arguments: limit={limit}, node={target_node}, state={target_state}, tag={target_tag}
API schema: [{json_data_object},{json_data_object},etc.] API schema: [{json_data_object},{json_data_object},etc.]
""" """
params = dict() params = dict()
@ -69,6 +69,8 @@ def vm_list(config, limit, target_node, target_state):
params['node'] = target_node params['node'] = target_node
if target_state: if target_state:
params['state'] = target_state params['state'] = target_state
if target_tag:
params['tag'] = target_tag
response = call_api(config, 'get', '/vm', params=params) response = call_api(config, 'get', '/vm', params=params)

View File

@ -1747,19 +1747,23 @@ def vm_dump(filename, domain):
'-s', '--state', 'target_state', default=None, '-s', '--state', 'target_state', default=None,
help='Limit list to VMs in the specified state.' help='Limit list to VMs in the specified state.'
) )
@click.option(
'-g', '--tag', 'target_tag', default=None,
help='Limit list to VMs with the specified tag.'
)
@click.option( @click.option(
'-r', '--raw', 'raw', is_flag=True, default=False, '-r', '--raw', 'raw', is_flag=True, default=False,
help='Display the raw list of VM names only.' help='Display the raw list of VM names only.'
) )
@cluster_req @cluster_req
def vm_list(target_node, target_state, limit, raw): def vm_list(target_node, target_state, target_tag, limit, raw):
""" """
List all virtual machines; optionally only match names or full UUIDs matching regex LIMIT. List all virtual machines; optionally only match names or full UUIDs matching regex LIMIT.
NOTE: Red-coloured network lists indicate one or more configured networks are missing/invalid. NOTE: Red-coloured network lists indicate one or more configured networks are missing/invalid.
""" """
retcode, retdata = pvc_vm.vm_list(config, limit, target_node, target_state) retcode, retdata = pvc_vm.vm_list(config, limit, target_node, target_state, target_tag)
if retcode: if retcode:
retdata = pvc_vm.format_list(config, retdata, raw) retdata = pvc_vm.format_list(config, retdata, raw)
else: else:

View File

@ -60,7 +60,7 @@ def getClusterInformation(zkhandler):
retcode, node_list = pvc_node.get_list(zkhandler, None) retcode, node_list = pvc_node.get_list(zkhandler, None)
# Get vm information object list # Get vm information object list
retcode, vm_list = pvc_vm.get_list(zkhandler, None, None, None) retcode, vm_list = pvc_vm.get_list(zkhandler, None, None, None, None)
# Get network information object list # Get network information object list
retcode, network_list = pvc_network.get_list(zkhandler, None, None) retcode, network_list = pvc_network.get_list(zkhandler, None, None)

View File

@ -866,7 +866,7 @@ def get_info(zkhandler, domain):
return True, domain_information return True, domain_information
def get_list(zkhandler, node, state, limit, is_fuzzy=True): def get_list(zkhandler, node, state, tag, limit, is_fuzzy=True):
if node: if node:
# Verify node is valid # Verify node is valid
if not common.verifyNode(zkhandler, node): if not common.verifyNode(zkhandler, node):
@ -904,6 +904,7 @@ def get_list(zkhandler, node, state, limit, is_fuzzy=True):
for vm in full_vm_list: for vm in full_vm_list:
name = zkhandler.read(('domain', vm)) name = zkhandler.read(('domain', vm))
is_limit_match = False is_limit_match = False
is_tag_match = False
is_node_match = False is_node_match = False
is_state_match = False is_state_match = False
@ -920,6 +921,13 @@ def get_list(zkhandler, node, state, limit, is_fuzzy=True):
else: else:
is_limit_match = True is_limit_match = True
if tag:
vm_tags = zkhandler.children(('domain.meta.tags', vm))
if tag in vm_tags:
is_tag_match = True
else:
is_tag_match = True
# Check on node # Check on node
if node: if node:
vm_node = zkhandler.read(('domain.node', vm)) vm_node = zkhandler.read(('domain.node', vm))
@ -936,7 +944,7 @@ def get_list(zkhandler, node, state, limit, is_fuzzy=True):
else: else:
is_state_match = True is_state_match = True
get_vm_info[vm] = True if is_limit_match and is_node_match and is_state_match else False get_vm_info[vm] = True if is_limit_match and is_tag_match and is_node_match and is_state_match else False
# Obtain our VM data in a thread pool # Obtain our VM data in a thread pool
# This helps parallelize the numerous Zookeeper calls a bit, within the bounds of the GIL, and # This helps parallelize the numerous Zookeeper calls a bit, within the bounds of the GIL, and

View File

@ -224,7 +224,8 @@
"tags": { "tags": {
"description": "The tag(s) of the VM", "description": "The tag(s) of the VM",
"items": { "items": {
"type": "string" "id": "VMTag",
"type": "object"
}, },
"type": "array" "type": "array"
} }
@ -1386,6 +1387,28 @@
"description": "The current state of the VM", "description": "The current state of the VM",
"type": "string" "type": "string"
}, },
"tags": {
"description": "The tag(s) of the VM",
"items": {
"id": "VMTag",
"properties": {
"name": {
"description": "The name of the tag",
"type": "string"
},
"protected": {
"description": "Whether the tag is protected or not",
"type": "boolean"
},
"type": {
"description": "The type of the tag (user, system)",
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"type": { "type": {
"description": "The type of the VM", "description": "The type of the VM",
"type": "string" "type": "string"
@ -2431,7 +2454,7 @@
"description": "", "description": "",
"parameters": [ "parameters": [
{ {
"description": "A search limit; fuzzy by default, use ^/$ to force exact matches", "description": "A search limit in the name, tags, or an exact UUID; fuzzy by default, use ^/$ to force exact matches",
"in": "query", "in": "query",
"name": "limit", "name": "limit",
"required": false, "required": false,
@ -5811,7 +5834,7 @@
"description": "", "description": "",
"parameters": [ "parameters": [
{ {
"description": "A name search limit; fuzzy by default, use ^/$ to force exact matches", "description": "A search limit in the name, tags, or an exact UUID; fuzzy by default, use ^/$ to force exact matches",
"in": "query", "in": "query",
"name": "limit", "name": "limit",
"required": false, "required": false,
@ -5830,6 +5853,13 @@
"name": "state", "name": "state",
"required": false, "required": false,
"type": "string" "type": "string"
},
{
"description": "Limit list to VMs with this tag",
"in": "query",
"name": "tag",
"required": false,
"type": "string"
} }
], ],
"responses": { "responses": {
@ -5907,12 +5937,22 @@
"type": "string" "type": "string"
}, },
{ {
"description": "The tag(s) of the VM", "description": "The user tag(s) of the VM",
"in": "query", "in": "query",
"items": { "items": {
"type": "string" "type": "string"
}, },
"name": "tags", "name": "user_tags",
"required": false,
"type": "array"
},
{
"description": "The protected user tag(s) of the VM",
"in": "query",
"items": {
"type": "string"
},
"name": "protected_tags",
"required": false, "required": false,
"type": "array" "type": "array"
} }
@ -6055,12 +6095,22 @@
"type": "string" "type": "string"
}, },
{ {
"description": "The tag(s) of the VM", "description": "The user tag(s) of the VM",
"in": "query", "in": "query",
"items": { "items": {
"type": "string" "type": "string"
}, },
"name": "tags", "name": "user_tags",
"required": false,
"type": "array"
},
{
"description": "The protected user tag(s) of the VM",
"in": "query",
"items": {
"type": "string"
},
"name": "protected_tags",
"required": false, "required": false,
"type": "array" "type": "array"
} }
@ -6481,11 +6531,10 @@
"description": "", "description": "",
"parameters": [ "parameters": [
{ {
"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": [ "enum": [
"add", "add",
"remove", "remove"
"replace"
], ],
"in": "query", "in": "query",
"name": "action", "name": "action",
@ -6493,14 +6542,19 @@
"type": "string" "type": "string"
}, },
{ {
"description": "The list of text tags to add/remove/replace-with", "description": "The text value of the tag",
"in": "query", "in": "query",
"items": { "name": "tag",
"type": "string"
},
"name": "tags",
"required": true, "required": true,
"type": "array" "type": "string"
},
{
"default": false,
"description": "Set the protected state of the tag",
"in": "query",
"name": "protected",
"required": false,
"type": "boolean"
} }
], ],
"responses": { "responses": {

View File

@ -180,7 +180,7 @@ class MetadataAPIInstance(object):
client_macaddr = host_information.get('mac_address', None) client_macaddr = host_information.get('mac_address', None)
# Find the VM with that MAC address - we can't assume that the hostname is actually right # Find the VM with that MAC address - we can't assume that the hostname is actually right
_discard, vm_list = pvc_vm.get_list(self.zkhandler, None, None, None) _discard, vm_list = pvc_vm.get_list(self.zkhandler, None, None, None, None)
vm_details = dict() vm_details = dict()
for vm in vm_list: for vm in vm_list:
try: try: