Repository: ansible-collections/community.vmware

Issue:
vmware_tag_manager: Tag processing is very slow
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
Tag processing of a managed object takes a long time.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
vmware_tag_manager

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```
ansible [core 2.13.4]
  config file = None
  configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ansible/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/ansible/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/ansible/.local/bin/ansible
  python version = 3.10.6 (main, Aug 10 2022, 11:40:04) [GCC 11.3.0]
  jinja version = 3.0.3
  libyaml = True
```

##### COLLECTION VERSION
<!--- Paste verbatim output from "ansible-galaxy collection list <namespace>.<collection>"  between the quotes
for example: ansible-galaxy collection list community.general
-->
```
download_url: https://galaxy.ansible.com/download/community-vmware-3.0.0.tar.gz
format_version: 1.0.0
name: vmware
namespace: community
server: https://galaxy.ansible.com/api/
signatures: []
version: 3.0.0
version_url: https://galaxy.ansible.com/api/v2/collections/community/vmware/versions/3.0.0/
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```
INVENTORY_ENABLED(/home/ansible/automation/ansible.cfg) = ['host_list', 'script', 'auto', 'yaml', 'ini', 'toml']
TRANSFORM_INVALID_GROUP_CHARS(/home/ansible/automation/ansible.cfg) = ignore
```

##### OS / ENVIRONMENT
vSphere 7.0.3
The issue is not dependent of the underlying OS


##### STEPS TO REPRODUCE
Set a tag on a managed object, for example on a virtual machine:

```
- name: Set tag on virtual machine
  community.vmware.vmware_tag_manager:
    hostname: "{{ vcenter_hostname }}"
    username: "{{ vcenter_username }}"
    password: "{{ vcenter_password }}"
    validate_certs: false
    state: set
    moid: "{{ vm.moid }}"
    object_type: VirtualMachine
    tag_names:
      - category: Environment
        tag: Prod
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
Tag processing is fast.


##### ACTUAL RESULTS
In my case, it took more than `32s` until the tag was added to the virtual machine. Subsequent plays are not faster, even when nothing has to be changed:

<!--- Paste verbatim command output between quotes -->
```
TASK [vsphere : Set tag on virtual machine] ************************************
Friday 14 October 2022  13:12:11 +0200 (0:00:00.059)       0:00:06.178 ********
changed: [molecule-debian-bullseye.local -> localhost]

TASK [vsphere : Include tasks from 'guest_boot_options.yml'] *******************
Friday 14 October 2022  13:12:43 +0200 (0:00:32.651)       0:00:38.830 ********
[...]

Friday 14 October 2022  13:12:53 +0200 (0:00:00.052)       0:00:48.454 ********
===============================================================================
vsphere : Set tag on virtual machine ----------------------------------- 32.65s
[...]
```


Discussion:

Resolving patch:
diff --git a/plugins/module_utils/vmware_rest_client.py b/plugins/module_utils/vmware_rest_client.py
--- a/plugins/module_utils/vmware_rest_client.py
+++ b/plugins/module_utils/vmware_rest_client.py
@@ -486,29 +486,32 @@ def get_category_by_name(self, category_name=None):
 
         return self.search_svc_object_by_name(service=self.api_client.tagging.Category, svc_obj_name=category_name)
 
-    def get_tag_by_category(self, tag_name=None, category_name=None):
+    def get_tag_by_category(self, tag_name=None, category_name=None, category_id=None):
         """
         Return tag object by name and category name specified
         Args:
             tag_name: Name of tag
-            category_name: Name of category
-
+            category_name: Name of category (mutually exclusive with 'category_id')
+            category_id: Id of category, if known in advance (mutually exclusive with 'category_name')
         Returns: Tag object if found else None
         """
 
         if not tag_name:
             return None
 
-        if category_name:
-            category_obj = self.get_category_by_name(category_name=category_name)
+        if category_id or category_name:
+            if not category_id:
+                category_obj = self.get_category_by_name(category_name=category_name)
+
+                if not category_obj:
+                    return None
 
-            if not category_obj:
-                return None
+                category_id = category_obj.id
 
-            for tag_object in self.api_client.tagging.Tag.list():
+            for tag_object in self.api_client.tagging.Tag.list_tags_for_category(category_id):
                 tag_obj = self.api_client.tagging.Tag.get(tag_object)
 
-                if tag_obj.name == tag_name and tag_obj.category_id == category_obj.id:
+                if tag_obj.name == tag_name:
                     return tag_obj
         else:
             return self.search_svc_object_by_name(service=self.api_client.tagging.Tag, svc_obj_name=tag_name)
diff --git a/plugins/modules/vmware_tag_manager.py b/plugins/modules/vmware_tag_manager.py
--- a/plugins/modules/vmware_tag_manager.py
+++ b/plugins/modules/vmware_tag_manager.py
@@ -318,7 +318,9 @@ def ensure_state(self):
                     # User specified only tag
                     tag_name = tag
 
-            if category_name:
+            if category_obj:
+                tag_obj = self.get_tag_by_category(tag_name=tag_name, category_id=category_obj.id)
+            elif category_name:
                 tag_obj = self.get_tag_by_category(tag_name=tag_name, category_name=category_name)
             else:
                 tag_obj = self.get_tag_by_name(tag_name=tag_name)


Test patch: