Linuxfabrik / monitoring-plugins

220+ check plugins for Icinga and other Nagios-compatible monitoring applications. Each plugin is a standalone command line tool (written in Python) that provides a specific type of check.
https://linuxfabrik.ch
The Unlicense
220 stars 51 forks source link

haproxy-status: Add unix socket support as alternative to HTTP(S) #767

Open cruelsmith opened 5 months ago

cruelsmith commented 5 months ago

Describe the solution you'd like

HAProxy provides the possibility to use unix sockets for stats. In monitoring setups where checks can be executed local on the system that runs haproxy it provides an easy and safe way to monitor it without the need to expose the stats frontend to the network.

Below an patch which is inspired by https://github.com/m-erhardt/check-haproxy/blob/ee6ae35/check_haproxy.py#L133-L170 The patch adds the possibility to set the URL to unix://path/to/socket and receive the CSV stats by calling show stat on the socket. The parsing of the data identical and unchanged.

Additional context

From: cruelsmith <92088441+cruelsmith@users.noreply.github.com>
Date: Fri, 24 May 2024 10:38:02 +0200
Subject: [PATCH] Add socket support for haproxy-status.py

---
 check-plugins/haproxy-status/haproxy-status.py | 65 +++++++++++++++++++++++++++++++--------
 1 file changed, 51 insertions(+), 14 deletions(-)

diff --git a/check-plugins/haproxy-status/haproxy-status.py b/check-plugins/haproxy-status/haproxy-status.py
index 8dd7a88..7101ab9 100755
@@ -14,6 +17,8 @@
 import argparse  # pylint: disable=C0413
 import base64  # pylint: disable=C0413
 import sys  # pylint: disable=C0413
+import socket  # pylint: disable=C0413
+import time  # pylint: disable=C0413

 import lib.args  # pylint: disable=C0413
 import lib.base  # pylint: disable=C0413
@@ -139,24 +144,59 @@ def main():
     # fetch data
     if args.TEST is None:
         url = args.URL
-        if url[0:4] != 'http':
-            lib.base.oao('--url parameter has to start with "http://" or https://".', STATE_UNKNOWN)
-        url = url + ';csv'
+        if url[0:4] != 'http' and url[0:7] != 'unix://':
+            lib.base.oao('--url parameter has to start with "http://" or https:// or unix://".', STATE_UNKNOWN)

         insecure = getattr(args, 'insecure', False)

         # fetch the url
-        if args.USERNAME and args.PASSWORD:
-            auth = '{}:{}'.format(args.USERNAME, args.PASSWORD)
-            encoded_auth = lib.txt.to_text(base64.b64encode(lib.txt.to_bytes(auth)))
-            result = lib.base.coe(
-                lib.url.fetch(
-                    url, insecure=insecure,
-                    timeout=args.TIMEOUT,
-                    header={'Authorization': 'Basic {}'.format(encoded_auth)},
-            ))
-        else:
-            result = lib.base.coe(lib.url.fetch(url, insecure=insecure, timeout=args.TIMEOUT))
+        if url[0:4] == 'http':
+            url = url + ';csv'
+            if args.USERNAME and args.PASSWORD:
+                auth = '{}:{}'.format(args.USERNAME, args.PASSWORD)
+                encoded_auth = lib.txt.to_text(base64.b64encode(lib.txt.to_bytes(auth)))
+                result = lib.base.coe(
+                    lib.url.fetch(
+                        url, insecure=insecure,
+                        timeout=args.TIMEOUT,
+                        header={'Authorization': 'Basic {}'.format(encoded_auth)},
+                ))
+            else:
+                result = lib.base.coe(lib.url.fetch(url, insecure=insecure, timeout=args.TIMEOUT))
+        elif url[0:7] == 'unix://':
+            url = url[6:]
+            try:
+                # Open connection to haproxy socket
+                sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+                sock.connect(url)
+
+                # Send command
+                sock.sendall("show stat \n ".encode("ascii"))
+                time.sleep(0.1)
+
+                # Shut down sending
+                sock.shutdown(socket.SHUT_WR)
+
+                # Create buffer for receiving
+                result = ""
+
+                while True:
+                    # Write reply to buffer in chunks of 1024 bytes
+                    data = sock.recv(1024)
+                    if not data:
+                        break
+                    result += data.decode()
+
+                # Close socket connection
+                sock.close()
+            except FileNotFoundError:
+                lib.base.oao('Socket file {} not found!'.format(url), STATE_UNKNOWN)
+            except PermissionError:
+                lib.base.oao('Access to socket file {} denied!'.format(url), STATE_UNKNOWN)
+            except TimeoutError:
+                lib.base.oao('Connection to socket {} timed out!'.format(url), STATE_UNKNOWN)
+            except ConnectionError as err:
+                lib.base.oao('Error during socket connection: {}'.format(err), STATE_UNKNOWN) 
     else:
         # do not call the command, put in test data
         stdout, stderr, retc = lib.test.test(args.TEST)
-- 
2.34.1
markuslf commented 5 months ago

That's great, thank you! I will integrate it.