From 75fb60b1b4fe398f7ceec57c05eb210de2e1f60d Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Wed, 14 Jul 2021 00:51:48 -0400 Subject: [PATCH] Add VM list filtering by tag Uses same method as state or node filtering, rather than altering how the main LIMIT field works. --- api-daemon/pvcapid/flaskapi.py | 13 +++- api-daemon/pvcapid/helper.py | 10 +-- client-cli/pvc/cli_lib/vm.py | 6 +- client-cli/pvc/pvc.py | 8 +- daemon-common/cluster.py | 2 +- daemon-common/vm.py | 12 ++- docs/manuals/swagger.json | 86 +++++++++++++++++---- node-daemon/pvcnoded/MetadataAPIInstance.py | 2 +- 8 files changed, 107 insertions(+), 32 deletions(-) diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py index c88fed0c..a2e63796 100755 --- a/api-daemon/pvcapid/flaskapi.py +++ b/api-daemon/pvcapid/flaskapi.py @@ -592,7 +592,7 @@ class API_Node_Root(Resource): name: limit type: string 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 name: daemon_state type: string @@ -844,6 +844,7 @@ class API_VM_Root(Resource): {'name': 'limit'}, {'name': 'node'}, {'name': 'state'}, + {'name': 'tag'}, ]) @Authenticator def get(self, reqargs): @@ -1092,7 +1093,7 @@ class API_VM_Root(Resource): name: limit type: string 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 name: node type: string @@ -1103,6 +1104,11 @@ class API_VM_Root(Resource): type: string required: false 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: 200: description: OK @@ -1114,6 +1120,7 @@ class API_VM_Root(Resource): return api_helper.vm_list( reqargs.get('node', None), reqargs.get('state', None), + reqargs.get('tag', None), reqargs.get('limit', None) ) @@ -1244,7 +1251,7 @@ class API_VM_Element(Resource): type: object 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([ {'name': 'limit'}, diff --git a/api-daemon/pvcapid/helper.py b/api-daemon/pvcapid/helper.py index d32de022..cb37e2ec 100755 --- a/api-daemon/pvcapid/helper.py +++ b/api-daemon/pvcapid/helper.py @@ -326,7 +326,7 @@ def vm_state(zkhandler, 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 retdata: @@ -355,7 +355,7 @@ def vm_node(zkhandler, 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 retdata: @@ -409,11 +409,11 @@ def vm_console(zkhandler, vm, lines=None): @pvc_common.Profiler(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. """ - 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 retdata: @@ -800,7 +800,7 @@ def vm_flush_locks(zkhandler, 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']: return {"message": "VM must be stopped to flush locks"}, 400 diff --git a/client-cli/pvc/cli_lib/vm.py b/client-cli/pvc/cli_lib/vm.py index 5d85dd00..d17c9f98 100644 --- a/client-cli/pvc/cli_lib/vm.py +++ b/client-cli/pvc/cli_lib/vm.py @@ -54,12 +54,12 @@ def vm_info(config, vm): 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}) 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.] """ params = dict() @@ -69,6 +69,8 @@ def vm_list(config, limit, target_node, target_state): params['node'] = target_node if target_state: params['state'] = target_state + if target_tag: + params['tag'] = target_tag response = call_api(config, 'get', '/vm', params=params) diff --git a/client-cli/pvc/pvc.py b/client-cli/pvc/pvc.py index 87cc2ba6..78d77384 100755 --- a/client-cli/pvc/pvc.py +++ b/client-cli/pvc/pvc.py @@ -1747,19 +1747,23 @@ def vm_dump(filename, domain): '-s', '--state', 'target_state', default=None, 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( '-r', '--raw', 'raw', is_flag=True, default=False, help='Display the raw list of VM names only.' ) @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. 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: retdata = pvc_vm.format_list(config, retdata, raw) else: diff --git a/daemon-common/cluster.py b/daemon-common/cluster.py index 7c6176fa..40483ab1 100644 --- a/daemon-common/cluster.py +++ b/daemon-common/cluster.py @@ -60,7 +60,7 @@ def getClusterInformation(zkhandler): retcode, node_list = pvc_node.get_list(zkhandler, None) # 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 retcode, network_list = pvc_network.get_list(zkhandler, None, None) diff --git a/daemon-common/vm.py b/daemon-common/vm.py index a97c5e93..10416f29 100644 --- a/daemon-common/vm.py +++ b/daemon-common/vm.py @@ -866,7 +866,7 @@ def get_info(zkhandler, domain): 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: # Verify node is valid 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: name = zkhandler.read(('domain', vm)) is_limit_match = False + is_tag_match = False is_node_match = False is_state_match = False @@ -920,6 +921,13 @@ def get_list(zkhandler, node, state, limit, is_fuzzy=True): else: 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 if node: vm_node = zkhandler.read(('domain.node', vm)) @@ -936,7 +944,7 @@ def get_list(zkhandler, node, state, limit, is_fuzzy=True): else: 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 # This helps parallelize the numerous Zookeeper calls a bit, within the bounds of the GIL, and diff --git a/docs/manuals/swagger.json b/docs/manuals/swagger.json index 5f17686b..6770b32a 100644 --- a/docs/manuals/swagger.json +++ b/docs/manuals/swagger.json @@ -224,7 +224,8 @@ "tags": { "description": "The tag(s) of the VM", "items": { - "type": "string" + "id": "VMTag", + "type": "object" }, "type": "array" } @@ -1386,6 +1387,28 @@ "description": "The current state of the VM", "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": { "description": "The type of the VM", "type": "string" @@ -2431,7 +2454,7 @@ "description": "", "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", "name": "limit", "required": false, @@ -5811,7 +5834,7 @@ "description": "", "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", "name": "limit", "required": false, @@ -5830,6 +5853,13 @@ "name": "state", "required": false, "type": "string" + }, + { + "description": "Limit list to VMs with this tag", + "in": "query", + "name": "tag", + "required": false, + "type": "string" } ], "responses": { @@ -5907,12 +5937,22 @@ "type": "string" }, { - "description": "The tag(s) of the VM", + "description": "The user tag(s) of the VM", "in": "query", "items": { "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, "type": "array" } @@ -6055,12 +6095,22 @@ "type": "string" }, { - "description": "The tag(s) of the VM", + "description": "The user tag(s) of the VM", "in": "query", "items": { "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, "type": "array" } @@ -6481,11 +6531,10 @@ "description": "", "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": [ "add", - "remove", - "replace" + "remove" ], "in": "query", "name": "action", @@ -6493,14 +6542,19 @@ "type": "string" }, { - "description": "The list of text tags to add/remove/replace-with", + "description": "The text value of the tag", "in": "query", - "items": { - "type": "string" - }, - "name": "tags", + "name": "tag", "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": { diff --git a/node-daemon/pvcnoded/MetadataAPIInstance.py b/node-daemon/pvcnoded/MetadataAPIInstance.py index 6fc69af6..148f883f 100644 --- a/node-daemon/pvcnoded/MetadataAPIInstance.py +++ b/node-daemon/pvcnoded/MetadataAPIInstance.py @@ -180,7 +180,7 @@ class MetadataAPIInstance(object): 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 - _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() for vm in vm_list: try: