ansible-collections / community.zabbix

Zabbix Ansible modules
http://galaxy.ansible.com/community/zabbix
Other
322 stars 284 forks source link

Registering a host for the 2nd time fails when using force: false because of duplicate main interface (zabbix 5.4) #459

Closed nerijus closed 2 years ago

nerijus commented 3 years ago

Using community.zabbix 1.4.0, zabbix 5.4.4, ansible 2.10.9 (issue similar to #244 and #253): Failed to update host example.com: ('Error -32602: Invalid params., Host cannot have more than one default interface of the same type. while sending {"jsonrpc": "2.0", "method": "hostinterface.create", "params": {"main": 1, "type": 1, "useip": 0, "ip": "185.xx.xx.xx", "dns": "example.com", "port": "10050", "available": "1", "error": "", "errors_from": "0", "disable_until": "0", "details": {}, "hostid": "10119"}, "auth": "be46....", "id": 15}', -32602)

The full traceback is:
  File "/tmp/ansible_zabbix_host_payload_l6_fjreo/ansible_zabbix_host_payload.zip/ansible_collections/community/zabbix/plugins/modules/zabbix_host.py", line 558, in update_host
  File "/usr/lib/python3.9/site-packages/zabbix_api.py", line 341, in method
    return self.universal("%s.%s" % (self.data["prefix"], name), opts[0])
  File "/usr/lib/python3.9/site-packages/zabbix_api.py", line 79, in wrapper
    return self.do_request(self.json_obj(method, opts))['result']
  File "/usr/lib/python3.9/site-packages/zabbix_api.py", line 348, in do_request
    return self.parent.do_request(req)
  File "/usr/lib/python3.9/site-packages/zabbix_api.py", line 299, in do_request
    raise ZabbixAPIException(msg, jobj['error']['code'])
fatal: [example.com]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "ca_cert": null,
            "description": null,
            "force": false,
            "host_groups": [
                "prod"
            ],
            "host_name": "example.com",
            "http_login_password": null,
            "http_login_user": null,
            "interfaces": [
                {
                    "details": {},
                    "dns": "example.com",
                    "interfaceid": "16",
                    "ip": "185.xx.xx.xx",
                    "main": 1,
                    "port": "10050",
                    "type": 1,
                    "useip": 0
                },
                {
                    "available": "1",
                    "details": {},
                    "disable_until": "0",
                    "dns": "example.com",
                    "error": "",
                    "errors_from": "0",
                    "hostid": "10119",
                    "ip": "185.xx.xx.xx",
                    "main": 1,
                    "port": "10050",
                    "type": 1,
                    "useip": 0
                }
            ],
            "inventory_mode": "automatic",
            "inventory_zabbix": null,
            "ipmi_authtype": null,
            "ipmi_password": null,
            "ipmi_privilege": null,
            "ipmi_username": null,
            "link_templates": [
                "Template App SSH Service",
                "Template ICMP Ping",
                "Template OS Linux"
            ],
            "login_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "login_user": "ansible",
            "macros": null,
            "proxy": "",
            "server_url": "https://zabbix.example.com",
            "state": "present",
            "status": "enabled",
            "tags": null,
            "timeout": 10,
            "tls_accept": 2,
            "tls_connect": 2,
            "tls_psk": "c1a.........",
            "tls_psk_identity": "ansible",
            "tls_subject": null,
            "validate_certs": true,
            "visible_name": "example.com"
        }
    }
}
nerijus commented 3 years ago

As using force: true works (but removes templates), I am using the following patch for now:

--- zabbix_host.py.orig 2021-09-02 17:23:52.971858938 +0300
+++ zabbix_host.py      2021-09-14 18:30:53.702858283 +0300
@@ -1118,7 +1118,7 @@
             # When force=no is specified, append existing interfaces to interfaces to update. When
             # no interfaces have been specified, copy existing interfaces as specified from the API.
             # Do the same with templates and host groups.
-            if not force or not interfaces:
+            if not interfaces:
                 for interface in copy.deepcopy(exist_interfaces):
                     # remove values not used during hostinterface.add/update calls
                     for key in tuple(interface.keys()):
D3DeFi commented 3 years ago

Minor releases of Zabbix are extreme wonder of breaking semver :)

This one should be obvious tho. When we compare interfaces in zabbix_host#L757+ we compare each key in existing interface (one retrieved from zabbix) to keys present in interface provided by user. If they are not matching and force=no, then we append existing interface to the list of interfaces we pass to API.

From your output:

            "interfaces": [
                {
                    "details": {},
                    "dns": "example.com",
                    "interfaceid": "16",
                    "ip": "185.xx.xx.xx",
                    "main": 1,
                    "port": "10050",
                    "type": 1,
                    "useip": 0
                },
                {
                    "available": "1",
                    "details": {},
                    "disable_until": "0",
                    "dns": "example.com",
                    "error": "",
                    "errors_from": "0",
                    "hostid": "10119",
                    "ip": "185.xx.xx.xx",
                    "main": 1,
                    "port": "10050",
                    "type": 1,
                    "useip": 0
                }
            ],

The first one was probably provided by you, the second one was retrieved from the Zabbix API and somehow not matched to the first one. Maybe the additional fields (available, errors, etc...) that I don't remember being there before broke something in the checks.

Needs actual testing tho if I am not mistaken somehow. Not sure if I would be able to find some spare time in the close future

nerijus commented 3 years ago

As we don't provide additional fields as parameters, it is probably better to not compare them (i.e. ignore them) at all?

nerijus commented 3 years ago

Commenting out lines:

                     elif str(exist_interface[key]) != str(interface[key]):
                            return True

did not help.

dslimp commented 3 years ago

@nerijus thanks, that helped