import agent_util
import os
from agent_util import float
import sys
###################################3
# -*- coding: utf-8 -*-
import logging
import socket
logger = logging.getLogger(__name__)
class TimeoutException(Exception):
pass
##################################
def get_status_headers(config):
# branch logic based on socket vs http interface
if "socket_cfg_file" in config:
buffer = ""
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(config["socket_cfg_file"])
client.settimeout(2.0)
out = "show stat" + "\n"
client.send(out.encode("utf-8"))
f = client.makefile()
output = f.read().split("\n")
client.close()
if "stats_http_host" in config and "stats_http_port" in config and "stats_http_path" in config:
auth_string = ""
if "stats_http_user" in config and "stats_http_password" in config:
auth_string = "%s:%s@" % (config["stats_http_user"], config["stats_http_password"])
stats_endpoint = "http://%s%s:%s/%s?stats;csv;norefresh" % (auth_string,config["stats_http_host"], config["stats_http_port"], config["stats_http_path"].replace("/", ""))
ret, output = agent_util.execute_command("curl '%s'" % stats_endpoint)
# extract the values from the output
output = output.split("\n")
header_line = output[0].split(",")
metric_values = []
for values in output[1:]:
if len(header_line) == len(values.split(",")):
metric_values.append(dict(zip(header_line, values.split(","))))
return metric_values, output
class HAProxyPlugin(agent_util.Plugin):
textkey = "haproxy"
label = "HAProxy"
@classmethod
def get_metadata(self, config):
status = agent_util.SUPPORTED
msg = None
# check if haproxy is installed
installed = agent_util.which("haproxy")
if not installed:
msg = "haproxy binary not found"
self.log.info(msg)
status = agent_util.UNSUPPORTED
return {}
# if http interface is being used, check if curl is present
if "stats_http_host" in config and not agent_util.which("curl", exc=False):
self.log.info('curl not found!')
status = agent_util.UNSUPPORTED
msg = "Curl is not installed - please install"
return {}
# if sock interface is being used, check if we can access the file
if "socket_cfg_file" in config:
socket_cfg_file = config["socket_cfg_file"]
if config.get("debug", False):
self.log.debug("HAProxy socket file path %s" % socket_cfg_file)
if not os.access(socket_cfg_file, os.W_OK):
self.log.error(
"Agent does not have permission to access the HAProxy socket file. Please adjust permissions."
)
status = agent_util.MISCONFIGURED
msg = "Agent does not have permission to access the HAProxy socket file. Please adjust permissions."
return {}
pxservers = set()
svnames = set()
try:
metric_values = None
output = None
metric_values, output = get_status_headers(config)
if config.get("debug", False):
self.log.info("#####################################################")
self.log.info("HAProxy command 'show stat' output:")
self.log.info(output)
self.log.info("#####################################################")
for values in metric_values:
pxname = values["# pxname"]
svname = values["svname"]
if svname == 'BACKEND':
# We skip the Backend checks
continue
pxservers.add(pxname + " " + svname)
except Exception:
status = agent_util.MISCONFIGURED
self.log.exception("Couldn't get haproxy status")
self.log.info("#####################################################")
self.log.info("HAProxy command 'show stat' output:")
self.log.info(output)
self.log.info("#####################################################")
msg = "Couldn't get haproxy status, make sure haproxy is running, haproxy configuration file is valid and the status module is enabled"
pxservers = list(pxservers)
pxservers.sort()
backend_pxservers = list(filter(lambda x: 'http_back' in x or 'BACKEND' in x, pxservers))
pxservers += ['Total Backend', 'Total Frontend']
if status is agent_util.SUPPORTED and not pxservers:
status = agent_util.MISCONFIGURED
msg = "No Proxy servers found."
metadata = {
"qcur": {
"label": "Current HAProxy service queued requests ",
"options": pxservers,
"status": status,
"error_message": msg,
},
"scur": {
"label": "Current HAProxy service sessions",
"options": pxservers,
"status": status,
"error_message": msg,
},
"stot": {
"label": "HAProxy service sessions",
"options": pxservers,
"status": status,
"error_message": msg,
},
"stot_rate": {
"label": "HAProxy service session rate",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "sessions per second",
},
"bin": {
"label": "Bytes In of HAProxy service",
"options": pxservers,
"status": status,
"error_message": msg,
},
"bin_rate": {
"label": "Rate of Bytes In of HAProxy service",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "B/s",
},
"bout": {
"label": "Bytes Out of HAProxy service",
"options": pxservers,
"status": status,
"error_message": msg,
},
"bout_rate": {
"label": "Rate of Bytes Out of HAProxy service",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "B/s",
},
"ereq": {
"label": "HAProxy service error requests",
"options": pxservers,
"status": status,
"error_message": msg,
},
"eresp": {
"label": "HAProxy service response errors",
"options": pxservers,
"status": status,
"error_message": msg,
},
"econ": {
"label": "HAProxy service connection errors",
"options": pxservers,
"status": status,
"error_message": msg,
},
"rate": {
"label": "Sessions created per second",
"options": pxservers,
"status": status,
"error_message": msg,
},
"req_rate": {
"label": "HTTP requests per second",
"options": pxservers,
"status": status,
"error_message": msg,
},
"dreq": {
"label": "Requests denied due to ACL restrictions",
"options": pxservers,
"status": status,
"error_message": msg,
},
"act": {
"label": "Number of HAProxy service active servers",
"options": backend_pxservers,
"status": status,
"error_message": msg,
},
"dresp": {
"label": "Responses denied due to ACL restrictions",
"options": pxservers,
"status": status,
"error_message": msg,
},
"wredis": {
"label": "Redispatched requests",
"options": pxservers,
"status": status,
"error_message": msg,
},
"wretr": {
"label": "Connection retries",
"options": pxservers,
"status": status,
"error_message": msg,
},
"bck": {
"label": "Number of HAProxy service backup servers",
"options": backend_pxservers,
"status": status,
"error_message": msg,
},
"hrsp_1xx": {
"label": "http responses with 1xx code",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"hrsp_2xx": {
"label": "http responses with 2xx code",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"hrsp_3xx": {
"label": "http responses with 3xx code",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"hrsp_4xx": {
"label": "http responses with 4xx code",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"hrsp_5xx": {
"label": "http responses with 5xx code",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"hrsp_other": {
"label": "http responses with other codes (protocol error)",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "req/min"
},
"status": {
"label": "webserver status",
"options": pxservers,
"status": status,
"error_message": msg,
"unit": "boolean"
}
}
return metadata
def check(self, textkey, pxserver, config):
metric_values, output = get_status_headers(config)
res = 0
isRate = False
cached = 0
cache = None
if (textkey.endswith("_rate") and (textkey != "req_rate")) or textkey.startswith('hrsp'):
isRate = True
if not textkey.startswith('hrsp'):
textkey = textkey.split("_")[0]
self.log.debug('Cache textkey {}'.format(textkey))
cache = self.get_cache_results(textkey, "haproxy")
if pxserver.startswith('Total'):
# Calculate the total of all services
svname = pxserver.split(' ')[-1].upper()
res = 0
for metric in metric_values:
if metric.get('svname') == svname:
self.log.debug('Total Calc SV {} Textkey {} PX {} Val {}'.format(
metric.get('svname'), textkey, metric['# pxname'], metric[textkey]
))
try:
if metric[textkey] == "": continue
elif metric[textkey] == "UP" or metric[textkey] == "OPEN":
res = 1
elif metric[textkey] == "DOWN":
res = 0
else:
res += int(metric[textkey])
except Exception:
self.log.exception("Unable to parse metric value")
else:
# Checking a specific haproxy service metric.
pxname = pxserver[: pxserver.rfind(" " + pxserver.split(" ")[-1])]
svname = pxserver.split(" ")[-1]
for metric in metric_values:
self.log.debug("Metric value: %s " % metric[textkey])
if pxname == metric["# pxname"] and svname == metric["svname"]:
res = metric[textkey]
if metric[textkey] in ('UP', 'OPEN'):
res = 1
elif 'DOWN' == metric[textkey]:
res = 0
else:
try:
res = int(res)
except:
return None
if isRate is True:
self.cache_result(textkey, "haproxy", res)
if not cache:
return None
delta, cached_result = cache[0]
if res < cached_result:
return None
val = (res - cached_result) / float(delta)
if textkey.startswith('hrsp'):
# We want actually requests per minute.
val = val * 60
return val
return res