centreon-plugins icon indicating copy to clipboard operation
centreon-plugins copied to clipboard

fix(http/collection): infinite loop and json encoding

Open garnier-quentin opened this issue 1 year ago • 7 comments

Description

That patch fixes 2 issues and add 2 capabilities:

  1. fix infinite loop when the attribute parse is not defined
  2. fix following error: UNKNOWN: hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this) at /usr/lib/centreon/plugins/centreon_protocol_http.pl line 348.
  3. add the capability to use attribute full_url
  4. add the capability to use attribute functions

Type of change

  • [X] Patch fixing an issue (non-breaking change)
  • [X] New functionality (non-breaking change)
  • [ ] Breaking change (patch or feature) that might cause side effects breaking part of the Software

Target serie

  • [X] 22.04.x
  • [X] 22.10.x
  • [X] 23.04.x
  • [X] 23.10.x
  • [X] 24.04.x (master)

How this pull request can be tested ?

The first issue can be tested with following file:

{
    "mapping": {},
    "http": {
        "requests": [
            {
                "name": "google",
                "hostname": "www.google.fr",
                "proto": "https",
                "port": "443",
                "endpoint": "/",
                "method": "GET",
                "insecure": 1,
                "timeout": 30,
                "backend": "curl",
                "rtype": "txt"
            }
        ]
    },
    "selection": [
        {
            "name": "google",
            "critical": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "exit": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "formatting": {
                "printf_msg":"google failed: %s",
                "printf_var":[
                    "%(builtin.httpCode.google)"
                ],
                "display_ok": true
            }
        }
    ],
    "formatting":{
        "custom_message_global": "login site ok",
        "separator": "-"
    }
}

The second issue can be tested with the following file if your JSON::XS version is lower than 4.0:

{
    "mapping": {},
    "http": {
        "requests": [
            {
                "name": "google",
                "hostname": "www.google.fr",
                "proto": "https",
                "port": "443",
                "endpoint": "/",
                "method": "GET",
                "insecure": 1,
                "timeout": 30,
                "backend": "curl",
                "rtype": "txt"
            }
        ]
    },
    "selection": [
        {
            "name": "google",
            "critical": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "exit": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "formatting": {
                "printf_msg":"authentication failed: %s",
                "printf_var":[
                    "%(builtin.httpCode.google)"
                ],
                "display_ok": true
            }
        }
    ],
    "formatting":{
        "custom_message_global": "login site ok",
        "separator": "-"
    }
}

To test the capabilities:

{
    "mapping": {},
    "http": {
        "requests": [
            {
                "name": "google",
                "full_url": "https://www.google.fr/",
                "method": "GET",
                "functions": [
                    {
                        "type": "capture",
                        "src": "%(http.tables.googleLookup.[0].server)",
                        "pattern": "^(.)",
                        "groups": [
                            { "offset": 0, "save": "%(serverFirstChar)" }
                        ]
                    }
                ],
                "insecure": 1,
                "timeout": 30,
                "backend": "curl",
                "rtype": "txt",
                "parse": [
                    {
                        "name": "lookup",
                        "type": "header",
                        "re": "Server\\s*:\\s*(\\S+)",
                        "modifier": "ms",
                        "entries": [
                            { "id": "server", "offset": "1" }
                        ]
                    }
                ]
            }
        ]
    },
    "selection": [
        {
            "name": "google",
            "critical": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "exit": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "formatting": {
                "printf_msg":"google check: %s (first server char: %s)",
                "printf_var":[
                    "%(builtin.httpCode.google)",
                    "%(serverFirstChar)"
                ],
                "display_ok": true
            }
        }
    ],
    "formatting":{
        "custom_message_global": "login site ok",
        "separator": "-"
    }
}

You should have following output:

OK: google check: 200 (first server char: g)

Checklist

Community contributors & Centreon team

  • [X] I have followed the coding style guidelines provided by Centreon
  • [ ] I have commented my code, especially new classes, functions or any legacy code modified. (docblock)
  • [ ] I have commented my code, especially hard-to-understand areas of the PR.
  • [ ] I have rebased my development branch on the base branch (master, maintenance).

garnier-quentin avatar Feb 15 '24 14:02 garnier-quentin

Hey @garnier-quentin! Look at https://github.com/centreon/centreon-plugins/pull/4850 because I've moved the parsing outside the call_http function (I put it in parse_structure which is more accurate). It fixes the second issue you're mentioning. It may not fix the first though. I also put the builtin in cache as you did. Just maybe retrieve the parsing-outside-the-call-hhtp part and add it to your PR? It is really useful.

cgagnaire avatar Feb 19 '24 14:02 cgagnaire

I will check to add your change and have only one PR.

garnier-quentin avatar Feb 29 '24 09:02 garnier-quentin

I have done the change @cgagnaire . I dont want to add the parse after the scenario_stopped. Maybe you need to use the parsing to stop the scenario.

garnier-quentin avatar Mar 05 '24 12:03 garnier-quentin

I have done the change @cgagnaire . I dont want to add the parse after the scenario_stopped. Maybe you need to use the parsing to stop the scenario.

Thanks @garnier-quentin. I don't think I get it how I can parse something if the API does not answer.

cgagnaire avatar Mar 06 '24 16:03 cgagnaire

Thanks @garnier-quentin. I don't think I get it how I can parse something if the API does not answer.

Some API provides a code 500/401 with a json response

garnier-quentin avatar Mar 06 '24 16:03 garnier-quentin

Thanks @garnier-quentin. I don't think I get it how I can parse something if the API does not answer.

Some API provides a code 500/401 with a json response

Sure some do, but my use case is in case of the host not being available or the service (ie the API) not responding. Your simple test file is too simple, try this one :

{
    "mapping": {},
    "http": {
        "requests": [
            {
                "name": "google",
                "hostname": "127.0.0.2",
                "proto": "https",
                "port": "443",
                "endpoint": "/",
                "method": "GET",
                "insecure": 1,
                "timeout": 30,
                "backend": "curl",
                "scenario_stopped": "%(builtin.httpCode.google) != 200",
                "rtype": "json",
                "parse": [
                    {
                        "name": "result",
                        "path": "$..blabla",
                        "entries": [
                            {
                                "id": "bloublou"
                            }
                        ]
                    }
                ]
            }
        ]
    },
    "selection": [
        {
            "name": "google",
            "critical": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "exit": "defined(%(builtin.httpCode.google)) and %(builtin.httpCode.google) != 200",
            "formatting": {
                "printf_msg":"google failed: %s",
                "printf_var":[
                    "%(builtin.httpCode.google)"
                ],
                "display_ok": true
            }
        }
    ],
    "formatting":{
        "custom_message_global": "login site ok",
        "separator": "-"
    }
}

With your version I'll get:

UNKNOWN: Cannot decode response (add --debug option to display returned content)
======> request send
GET https://127.0.0.2:443/
User-Agent: centreon::plugins::backend::http::useragent

======> response done
500 Can't connect to 127.0.0.2:443 (Connection refused)
Content-Type: text/plain
Client-Date: Wed, 06 Mar 2024 17:21:15 GMT
Client-Warning: Internal response

Can't connect to 127.0.0.2:443 (Connection refused)

Connection refused at /usr/share/perl5/vendor_perl/LWP/Protocol/http.pm line 50.
malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before "Can't connect to 127...") at centreon_protocol_http_test.pl line 445.

With mine:

CRITICAL: google failed: 500
======> request send
GET https://127.0.0.2:443/
User-Agent: centreon::plugins::backend::http::useragent

======> response done
500 Can't connect to 127.0.0.2:443 (Connection refused)
Content-Type: text/plain
Client-Date: Wed, 06 Mar 2024 17:20:50 GMT
Client-Warning: Internal response

Can't connect to 127.0.0.2:443 (Connection refused)

Connection refused at /usr/share/perl5/vendor_perl/LWP/Protocol/http.pm line 50.
======> variables
google failed: 500

That's because I'll look at the scenario_stopped directive before parsing the result which here is the LWP failing to connected to the host (as I write this: shouln't it be curl as we use curl backend !?).

With your last config file you'll get:

UNKNOWN: [http > requests > google > 0] unknown or empty table 'googleLookup'. Available tables are (not empty based on your conf) :
======> request send
GET https://127.0.0.2/
User-Agent: centreon::plugins::backend::http::useragent

======> response done
500 Can't connect to 127.0.0.2:443 (Connection refused)
Content-Type: text/plain
Client-Date: Wed, 06 Mar 2024 17:25:04 GMT
Client-Warning: Internal response

Can't connect to 127.0.0.2:443 (Connection refused)

Connection refused at /usr/share/perl5/vendor_perl/LWP/Protocol/http.pm line 50.

I'll get:

CRITICAL: google check: 500 (first server char: ) - Key 'serverFirstChar' not found in ('name', 'builtin.httpMessage.google', 'builtin.currentTime', 'builtin.httpExecutionTime.google', 'builtin.httpCode.google')
======> request send
GET https://127.0.0.2:443/
User-Agent: centreon::plugins::backend::http::useragent

======> response done
500 Can't connect to 127.0.0.2:443 (Connection refused)
Content-Type: text/plain
Client-Date: Wed, 06 Mar 2024 17:26:31 GMT
Client-Warning: Internal response

Can't connect to 127.0.0.2:443 (Connection refused)

Connection refused at /usr/share/perl5/vendor_perl/LWP/Protocol/http.pm line 50.
======> variables
google check: 500 (first server char: )

But by adding a test selection before the google selection like:

        {
            "name": "test",
            "critical": "%(builtin.httpCode.google) != 200",
            "exit": "%(builtin.httpCode.google) != 200",
            "formatting": {
                "printf_msg":"API returned HTTP code '%s' with message '%s'",
                "printf_var":[
                    "%(builtin.httpCode.google)",
                    "%(builtin.httpMessage.google)"
                ],
                "display_ok": true
            }
        },

I'll get:

CRITICAL: API returned HTTP code '500' with message 'Can't connect to 127.0.0.2:443 (Connection refused)'
======> request send
GET https://127.0.0.2:443/
User-Agent: centreon::plugins::backend::http::useragent

======> response done
500 Can't connect to 127.0.0.2:443 (Connection refused)
Content-Type: text/plain
Client-Date: Wed, 06 Mar 2024 17:37:54 GMT
Client-Warning: Internal response

Can't connect to 127.0.0.2:443 (Connection refused)

Connection refused at /usr/share/perl5/vendor_perl/LWP/Protocol/http.pm line 50.
======> variables
API returned HTTP code '500' with message 'Can't connect to 127.0.0.2:443 (Connection refused)'

That's because the exit and critical directives of the google selection are not matched before parsing, so maybe we could do something about it also.

cgagnaire avatar Mar 06 '24 17:03 cgagnaire

@cgagnaire i added the attribute scenario_stopped_first. I also fix the curl backend attribute. It's ok now

garnier-quentin avatar Apr 12 '24 14:04 garnier-quentin

Thanks again for the work @garnier-quentin and @cgagnaire ! The code changes will be merged with #5044

omercier avatar May 31 '24 11:05 omercier