community.zabbix icon indicating copy to clipboard operation
community.zabbix copied to clipboard

Error -32500: Application error., Cannot update "groups" for a discovered host

Open ironbishop opened this issue 4 years ago • 14 comments

SUMMARY

When disabling a Zabbix host, the task fails if the host has been created by a discovery (host.flags=4).

The same issue occours for properties: tls_connect, tls_accept, groups, proxy_hostid.

ISSUE TYPE
  • Bug Report
COMPONENT NAME

https://github.com/ansible-collections/community.zabbix/blob/705bf3da8f04ceb352906410dfeae03fefc83044/plugins/modules/zabbix_host.py#L1123

ANSIBLE VERSION
ansible 2.9.6
  config file = ansible/ansible.cfg
  configured module search path = ['.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = ansible/venv/lib64/python3.6/site-packages/ansible
  executable location = ansible/venv/bin/ansible
  python version = 3.6.8 (default, Aug  7 2019, 17:28:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

CONFIGURATION
DEFAULT_CALLBACK_WHITELIST(ansible/ansible.cfg) = ['profile_tasks', 'timer']
HOST_KEY_CHECKING(ansible/ansible.cfg) = False
INVENTORY_ENABLED(ansible/ansible.cfg) = ['script', 'host_list', 'yaml', 'ini', 'vmware_vm_inventory']
USE_PERSISTENT_CONNECTIONS(ansible/ansible.cfg) = True
OS / ENVIRONMENT
  • Linux *** 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
  • CentOS Linux release 7.6.1810 (Core)
STEPS TO REPRODUCE
  1. Use the zabbix_host_info to find some hosts.
  2. Loop to disable them, leaving a note in the Description field.
    - name: enable host in Zabbix Server
      when:
        - item.status == '1'
      environment:
        no_proxy: "127.0.0.1,localhost,.local,{{zabbix_server_host}}"
      local_action:
        module: zabbix_host
        server_url: "{{zabbix_api_url}}"
        validate_certs: no
        login_user: "{{zabbix_api_user}}"
        login_password: "{{zabbix_api_pass}}"
        host_name: "{{item.host}}"
        status: enabled
        description: "{{ item.description + '\n\n' if item.description else '' }}Enabled on {{ lookup('env','CURRENT_TIMESTAMP') }}{{ ' by ' + lookup('env','GITLAB_USER_NAME') + ', using Ansible through Gitlab (job ID: ' + lookup('env','CI_JOB_ID') + ').' if lookup('env','GITLAB_USER_NAME') else '' }}"
      retries: 2
      delay: 2
      throttle: 1
      register: result
      until: result is not failed
      loop: "{{ zabbix_host_infos.hosts }}"
EXPECTED RESULTS

The module should change only the requested property, in this case "status" and "description", not all of them. Or maybe use flags to discern which properties are read-only.

ACTUAL RESULTS

Error message:

  • Failed to update host omissis: ('Error -32500: Application error., Cannot update "groups" for a discovered host "omissis". while sending {"jsonrpc": "2.0", "method": "host.update", "params": {"hostid": "25149", "groups": [{"groupid": "636"}], "status": 0, "tls_connect": 1, "tls_accept": 1, "proxy_hostid": 18862, "description": "Enabled on Tue Jun 16 18:01:56 CEST 2020"}, "auth": "2ab80fd1bccd459decb84e74049dd5d4", "id": 9}', -32500)

ironbishop avatar Jun 16 '20 16:06 ironbishop

removed my comment as this was another issue (see #92 )

rockaut avatar Jun 17 '20 11:06 rockaut

Thank you for reporting this @ironbishop. Seems that original module design assumed that few parameters would always be updated no matter what (zabbix_host.py:L521 specifically).

Looking at the host.update API documentation, this should not be required. I will try to delve deeper into this, confirm my assumptions and try to come up with a fix.

D3DeFi avatar Jun 18 '20 12:06 D3DeFi

@ironbishop I must be doing something wrong. I've now tried to setup both network discovery and auto registration, but every time my hosts tab gets populated by a new host I can edit it just fine and the host.flags is set to 0 every time. Can you maybe provide more details regarding the discovery process you use?

I am for now working on a fix blinded :)

Edit: and also what zabbix version are you using?

D3DeFi avatar Jun 23 '20 07:06 D3DeFi

The other one, that is: the Low Level Discovery (LLD). https://www.zabbix.com/documentation/current/manual/discovery/low_level_discovery

In LLD, for example in "Template VM VMware" distributed with the official Zabbix release, you create each discovered host. They are called "Host prototype". In this type of host almost all fields are read-only, see the screenshot:

image

It's the same in all versions, I tested 5.0, 4.4, 4.2 and 3.4.

ironbishop avatar Jun 23 '20 13:06 ironbishop

May I recap please? You are discovering hosts through VMware LLD which then are read-only? That is per definition the case, so until here all is right.

You now want to disable them (which is possible) with ansible and here it fails? Yes that would be improvement on the ansible module side as it should only update fields it really needs.

But here's my BUT: even if you may disable them with ansible they would get enabled again through LLD rerun later. There were some new possibilities in the latest release to work around this (I think it got mentioned in this video by Dmitry Lambert on LLD Overrides (https://www.youtube.com/watch?v=rufZHXGl0yE&t=421s))

rockaut avatar Jun 23 '20 13:06 rockaut

When I manually disable them, the LLD will not enable them because "Create Enabled" is different from "Enabled".

"Create Enabled" sets the state only for new objects.

The new "Override" feature in Zabbix 5 is a different thing.

ironbishop avatar Jun 23 '20 15:06 ironbishop

Of course you're right, sorry.

rockaut avatar Jun 23 '20 15:06 rockaut

@D3DeFi I dont think that will work at all if I get it right.

https://www.zabbix.com/documentation/current/manual/api/reference/host/update is stating

The host groups must have the groupid property defined. All host groups that are not listed in the request will be unlinked.

So if the module isn't including the groups they will get removed? Or is this just if the field is there but empty? Haven't tried it yet as I'm already "offline".

/EDIT: couldn't let it go and tried it. If groups field is removed from api call all is good, groups don't get updated/removed. So I had a look at the code and mitigating this is currently cumbersome as the function uses arguments instead of the difference here. As a quickfix/workaround the update function could also look at host_groups (currently not passed to function) and if empty dropping the field before request.

rockaut avatar Jun 23 '20 15:06 rockaut

/EDIT: couldn't let it go and tried it. If groups field is removed from api call all is good, groups don't get updated/removed. So I had a look at the code and mitigating this is currently cumbersome as the function uses arguments instead of the difference here. As a quickfix/workaround the update function could also look at host_groups (currently not passed to function) and if empty dropping the field before request.

Yup, exactly this. I was looking at this today for almost an hour and couldn't do any reasonable change for it. I had urge to start writing the module anew.

I think we can implement some ugly hacky way for now that will check if user provided {groups,tls_*,proxy,etc} match those parameters on the existing host. If yes, they wont be passed to zbx.update

D3DeFi avatar Jun 23 '20 18:06 D3DeFi

What about just passing the module object to the functions additionally? So you could check it in the function, while also keeping the basic structure for now.

/EDIT: Actually that might fit pretty good as after the ZabbixBase refactoring this could easily translate to self._module then? Granted, it's not pretty because first the list is generated needlessly but a workaround is a workaround.

if not module.params['host_groups']:
  parameters.pop("group_ids", none)

rockaut avatar Jun 24 '20 05:06 rockaut

What about just passing the module object to the functions additionally? So you could check it in the function, while also keeping the basic structure for now.

/EDIT: Actually that might fit pretty good as after the ZabbixBase refactoring this could easily translate to self._module then? Granted, it's not pretty because first the list is generated needlessly but a workaround is a workaround.

if not module.params['host_groups']:
  parameters.pop("group_ids", none)

sorry for the late answer, this is not good as you will have trouble recognizing situations where user wants to remove something. Groups will work like this:

if self._module.params['host_groups'] is not None:
    parameters['groups'] = group_ids

as the default for host_groups is None, you can easily recognize it from [] and this will not remove user's possibility to wipe out groups:

host_groups:
  -

The trickier ones are parameters that have default values. For example, if user omits tls_accept as seen in this issue, you will have no way of telling whether you need to pass default or not other then looking into existing host:

if tls_accept != existing_host['tls_accept']:
    parameters['tls_accept']

This duplicates whole check_all_properties method. I'd rather do a bigger overhaul, but that needs more time.

D3DeFi avatar Jul 04 '20 16:07 D3DeFi

Wait. There should not be a default at all, and it cannot be empty, because at least 1 group is mandatory for all hosts in Zabbix.

If the user omits a parameter, you just don't pass it! The API states: "The hostid property must be defined for each host, all other properties are optional."

  • If I don't pass "groups", I expect the module not to mess with groups.
  • If I pass a single group, I expect the module to overwrite the host's groups, because that's what the API does.
  • If I want to add a group, I need to do a double operation: ask the module the host's groups, add one to the list, then ask the module to update the host.
    • It would be nice to have a "mode" parameter in the module, like "add/replace/remove" but that's for another issue...
  • If I don't pass "tls_accept", the module expects me to be aware of the default value.

ironbishop avatar Jul 06 '20 07:07 ironbishop

Wait. There should not be a default at all, and it cannot be empty, because at least 1 group is mandatory for all hosts in Zabbix.

Yes, my example was unfortunate in this regard. At least 1 zabbix host group is required for the host.

If the user omits a parameter, you just don't pass it! The API states: "The hostid property must be defined for each host, all other properties are optional."

  • If I don't pass "groups", I expect the module not to mess with groups.

  • If I pass a single group, I expect the module to overwrite the host's groups, because that's what the API does.

  • If I want to add a group, I need to do a double operation: ask the module the host's groups, add one to the list, then ask the module to update the host.

    • It would be nice to have a "mode" parameter in the module, like "add/replace/remove" but that's for another issue...
  • If I don't pass "tls_accept", the module expects me to be aware of the default value.

Yes, but my previous comment was not related to the issue rather than to @rockaut ‘s last comment.

You however need a default values for situation when a host is created for the first time. We try to keep those values same as the API describes. If you don’t pass tls_connect to the API it will still set ‘no encryption’ for the host. Might as well be set explicitly by the module as well.

As for the update, indeed we should not pass all of the values that are being passed. Module is unfortunately of ancient design from times before zabbix 3.0. If I do recall correctly, back then calling zbx.host.update simply failed if you only passed sole ID without anything else.

I am not sure why original authors decided to do the module this way, but I would like to introduce such a change in a bigger rework rather than placing another workaround a top of other thousand workarounds we already have there.

Edit: oh and regarding default values in the previous comment - those are related to the module argument_spec. So if lets say ‘host_groups’ are defaulting to None, we really should not try to pass it to the API call.

D3DeFi avatar Jul 06 '20 09:07 D3DeFi

You however need a default values for situation when a host is created for the first time. We try to keep those values same as the API describes. If you don’t pass tls_connect to the API it will still set ‘no encryption’ for the host. Might as well be set explicitly by the module as well.

Uh? Oh, the module should "Create/update/delete Zabbix hosts", even if it's 3 different API calls.

Could it exit with failure, if the mandatory arguments are not present?

There are modules with "conditional-mandatory" arguments, like in "vmware_guest", parameter "domainadmin" is only mandatory with "joindomain" (first example that came to mind).

... from times before zabbix 3.0. If I do recall correctly, back then calling zbx.host.update simply failed if you only passed sole ID without anything else.

From the docs, looks like 1.8 was like that. Was changed in 2.0. (I'm lucky enough to never have used Zabbix before 3.4.)

ironbishop avatar Jul 06 '20 10:07 ironbishop

Just wanted to add that this is still the case and happens when one wants to link a template to a discoverd host, too.

Marx1st avatar May 04 '23 14:05 Marx1st