From 90a4827284acd3cb072cdfeef323c522802c6449 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Wed, 9 Oct 2024 14:55:11 +0200
Subject: haproxy: T6745: Rename `reverse-proxy` to `haproxy`
---
src/conf_mode/load-balancing_haproxy.py | 206 ++++++++++++++++++++++
src/conf_mode/load-balancing_reverse-proxy.py | 206 ----------------------
src/conf_mode/pki.py | 2 +-
src/migration-scripts/reverse-proxy/1-to-2 | 27 +++
src/op_mode/load-balancing_haproxy.py | 237 ++++++++++++++++++++++++++
src/op_mode/restart.py | 10 +-
src/op_mode/reverseproxy.py | 237 --------------------------
7 files changed, 476 insertions(+), 449 deletions(-)
create mode 100644 src/conf_mode/load-balancing_haproxy.py
delete mode 100755 src/conf_mode/load-balancing_reverse-proxy.py
create mode 100755 src/migration-scripts/reverse-proxy/1-to-2
create mode 100755 src/op_mode/load-balancing_haproxy.py
delete mode 100755 src/op_mode/reverseproxy.py
(limited to 'src')
diff --git a/src/conf_mode/load-balancing_haproxy.py b/src/conf_mode/load-balancing_haproxy.py
new file mode 100644
index 000000000..45042dd52
--- /dev/null
+++ b/src/conf_mode/load-balancing_haproxy.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023-2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+
+from sys import exit
+from shutil import rmtree
+
+from vyos.config import Config
+from vyos.configverify import verify_pki_certificate
+from vyos.configverify import verify_pki_ca_certificate
+from vyos.utils.dict import dict_search
+from vyos.utils.process import call
+from vyos.utils.network import check_port_availability
+from vyos.utils.network import is_listen_port_bind_service
+from vyos.pki import find_chain
+from vyos.pki import load_certificate
+from vyos.pki import load_private_key
+from vyos.pki import encode_certificate
+from vyos.pki import encode_private_key
+from vyos.template import render
+from vyos.utils.file import write_file
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+load_balancing_dir = '/run/haproxy'
+load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg'
+systemd_service = 'haproxy.service'
+systemd_override = '/run/systemd/system/haproxy.service.d/10-override.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['load-balancing', 'haproxy']
+ if not conf.exists(base):
+ return None
+ lb = conf.get_config_dict(base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True,
+ with_pki=True)
+
+ return lb
+
+def verify(lb):
+ if not lb:
+ return None
+
+ if 'backend' not in lb or 'service' not in lb:
+ raise ConfigError(f'"service" and "backend" must be configured!')
+
+ for front, front_config in lb['service'].items():
+ if 'port' not in front_config:
+ raise ConfigError(f'"{front} service port" must be configured!')
+
+ # Check if bind address:port are used by another service
+ tmp_address = front_config.get('address', '0.0.0.0')
+ tmp_port = front_config['port']
+ if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \
+ not is_listen_port_bind_service(int(tmp_port), 'haproxy'):
+ raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')
+
+ for back, back_config in lb['backend'].items():
+ if 'http_check' in back_config:
+ http_check = back_config['http_check']
+ if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']:
+ raise ConfigError(f'"expect status" and "expect string" can not be configured together!')
+
+ if 'health_check' in back_config:
+ if back_config['mode'] != 'tcp':
+ raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' +
+ f'health-check whilst in TCP mode!')
+ if 'http_check' in back_config:
+ raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!')
+
+ if 'server' not in back_config:
+ raise ConfigError(f'"{back} server" must be configured!')
+
+ for bk_server, bk_server_conf in back_config['server'].items():
+ if 'address' not in bk_server_conf or 'port' not in bk_server_conf:
+ raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!')
+
+ if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
+ raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
+
+ if 'ssl' in back_config:
+ if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
+ raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')
+
+ # Check if http-response-headers are configured in any frontend/backend where mode != http
+ for group in ['service', 'backend']:
+ for config_name, config in lb[group].items():
+ if 'http_response_headers' in config and config['mode'] != 'http':
+ raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
+
+ for front, front_config in lb['service'].items():
+ for cert in dict_search('ssl.certificate', front_config) or []:
+ verify_pki_certificate(lb, cert)
+
+ for back, back_config in lb['backend'].items():
+ tmp = dict_search('ssl.ca_certificate', back_config)
+ if tmp: verify_pki_ca_certificate(lb, tmp)
+
+
+def generate(lb):
+ if not lb:
+ # Delete /run/haproxy/haproxy.cfg
+ config_files = [load_balancing_conf_file, systemd_override]
+ for file in config_files:
+ if os.path.isfile(file):
+ os.unlink(file)
+ # Delete old directories
+ if os.path.isdir(load_balancing_dir):
+ rmtree(load_balancing_dir, ignore_errors=True)
+
+ return None
+
+ # Create load-balance dir
+ if not os.path.isdir(load_balancing_dir):
+ os.mkdir(load_balancing_dir)
+
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in lb['pki']['ca'].values()} if 'ca' in lb['pki'] else {}
+
+ # SSL Certificates for frontend
+ for front, front_config in lb['service'].items():
+ if 'ssl' not in front_config:
+ continue
+
+ if 'certificate' in front_config['ssl']:
+ cert_names = front_config['ssl']['certificate']
+
+ for cert_name in cert_names:
+ pki_cert = lb['pki']['certificate'][cert_name]
+ cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
+ cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
+
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
+
+ if 'private' in pki_cert and 'key' in pki_cert['private']:
+ loaded_key = load_private_key(pki_cert['private']['key'], passphrase=None, wrap_tags=True)
+ key_pem = encode_private_key(loaded_key, passphrase=None)
+ write_file(cert_key_path, key_pem)
+
+ # SSL Certificates for backend
+ for back, back_config in lb['backend'].items():
+ if 'ssl' not in back_config:
+ continue
+
+ if 'ca_certificate' in back_config['ssl']:
+ ca_name = back_config['ssl']['ca_certificate']
+ ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
+ ca_chains = []
+
+ pki_ca_cert = lb['pki']['ca'][ca_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain))
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
+
+ render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb)
+ render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb)
+
+ return None
+
+def apply(lb):
+ call('systemctl daemon-reload')
+ if not lb:
+ call(f'systemctl stop {systemd_service}')
+ else:
+ call(f'systemctl reload-or-restart {systemd_service}')
+
+ return None
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py
deleted file mode 100755
index 17226efe9..000000000
--- a/src/conf_mode/load-balancing_reverse-proxy.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import os
-
-from sys import exit
-from shutil import rmtree
-
-from vyos.config import Config
-from vyos.configverify import verify_pki_certificate
-from vyos.configverify import verify_pki_ca_certificate
-from vyos.utils.dict import dict_search
-from vyos.utils.process import call
-from vyos.utils.network import check_port_availability
-from vyos.utils.network import is_listen_port_bind_service
-from vyos.pki import find_chain
-from vyos.pki import load_certificate
-from vyos.pki import load_private_key
-from vyos.pki import encode_certificate
-from vyos.pki import encode_private_key
-from vyos.template import render
-from vyos.utils.file import write_file
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-load_balancing_dir = '/run/haproxy'
-load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg'
-systemd_service = 'haproxy.service'
-systemd_override = '/run/systemd/system/haproxy.service.d/10-override.conf'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- base = ['load-balancing', 'reverse-proxy']
- if not conf.exists(base):
- return None
- lb = conf.get_config_dict(base,
- get_first_key=True,
- key_mangling=('-', '_'),
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True,
- with_pki=True)
-
- return lb
-
-def verify(lb):
- if not lb:
- return None
-
- if 'backend' not in lb or 'service' not in lb:
- raise ConfigError(f'"service" and "backend" must be configured!')
-
- for front, front_config in lb['service'].items():
- if 'port' not in front_config:
- raise ConfigError(f'"{front} service port" must be configured!')
-
- # Check if bind address:port are used by another service
- tmp_address = front_config.get('address', '0.0.0.0')
- tmp_port = front_config['port']
- if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \
- not is_listen_port_bind_service(int(tmp_port), 'haproxy'):
- raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')
-
- for back, back_config in lb['backend'].items():
- if 'http_check' in back_config:
- http_check = back_config['http_check']
- if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']:
- raise ConfigError(f'"expect status" and "expect string" can not be configured together!')
-
- if 'health_check' in back_config:
- if back_config['mode'] != 'tcp':
- raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' +
- f'health-check whilst in TCP mode!')
- if 'http_check' in back_config:
- raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!')
-
- if 'server' not in back_config:
- raise ConfigError(f'"{back} server" must be configured!')
-
- for bk_server, bk_server_conf in back_config['server'].items():
- if 'address' not in bk_server_conf or 'port' not in bk_server_conf:
- raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!')
-
- if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
- raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
-
- if 'ssl' in back_config:
- if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
- raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')
-
- # Check if http-response-headers are configured in any frontend/backend where mode != http
- for group in ['service', 'backend']:
- for config_name, config in lb[group].items():
- if 'http_response_headers' in config and config['mode'] != 'http':
- raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
-
- for front, front_config in lb['service'].items():
- for cert in dict_search('ssl.certificate', front_config) or []:
- verify_pki_certificate(lb, cert)
-
- for back, back_config in lb['backend'].items():
- tmp = dict_search('ssl.ca_certificate', back_config)
- if tmp: verify_pki_ca_certificate(lb, tmp)
-
-
-def generate(lb):
- if not lb:
- # Delete /run/haproxy/haproxy.cfg
- config_files = [load_balancing_conf_file, systemd_override]
- for file in config_files:
- if os.path.isfile(file):
- os.unlink(file)
- # Delete old directories
- if os.path.isdir(load_balancing_dir):
- rmtree(load_balancing_dir, ignore_errors=True)
-
- return None
-
- # Create load-balance dir
- if not os.path.isdir(load_balancing_dir):
- os.mkdir(load_balancing_dir)
-
- loaded_ca_certs = {load_certificate(c['certificate'])
- for c in lb['pki']['ca'].values()} if 'ca' in lb['pki'] else {}
-
- # SSL Certificates for frontend
- for front, front_config in lb['service'].items():
- if 'ssl' not in front_config:
- continue
-
- if 'certificate' in front_config['ssl']:
- cert_names = front_config['ssl']['certificate']
-
- for cert_name in cert_names:
- pki_cert = lb['pki']['certificate'][cert_name]
- cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
- cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
-
- loaded_pki_cert = load_certificate(pki_cert['certificate'])
- cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
-
- write_file(cert_file_path,
- '\n'.join(encode_certificate(c) for c in cert_full_chain))
-
- if 'private' in pki_cert and 'key' in pki_cert['private']:
- loaded_key = load_private_key(pki_cert['private']['key'], passphrase=None, wrap_tags=True)
- key_pem = encode_private_key(loaded_key, passphrase=None)
- write_file(cert_key_path, key_pem)
-
- # SSL Certificates for backend
- for back, back_config in lb['backend'].items():
- if 'ssl' not in back_config:
- continue
-
- if 'ca_certificate' in back_config['ssl']:
- ca_name = back_config['ssl']['ca_certificate']
- ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
- ca_chains = []
-
- pki_ca_cert = lb['pki']['ca'][ca_name]
- loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain))
- write_file(ca_cert_file_path, '\n'.join(ca_chains))
-
- render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb)
- render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb)
-
- return None
-
-def apply(lb):
- call('systemctl daemon-reload')
- if not lb:
- call(f'systemctl stop {systemd_service}')
- else:
- call(f'systemctl reload-or-restart {systemd_service}')
-
- return None
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 233d73ba8..45e0129a3 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -71,7 +71,7 @@ sync_search = [
},
{
'keys': ['certificate', 'ca_certificate'],
- 'path': ['load_balancing', 'reverse_proxy'],
+ 'path': ['load_balancing', 'haproxy'],
},
{
'keys': ['key'],
diff --git a/src/migration-scripts/reverse-proxy/1-to-2 b/src/migration-scripts/reverse-proxy/1-to-2
new file mode 100755
index 000000000..61612bc36
--- /dev/null
+++ b/src/migration-scripts/reverse-proxy/1-to-2
@@ -0,0 +1,27 @@
+# Copyright 2024 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see .
+
+# T6745: Rename base node to haproxy
+
+from vyos.configtree import ConfigTree
+
+base = ['load-balancing', 'reverse-proxy']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ config.rename(base, 'haproxy')
diff --git a/src/op_mode/load-balancing_haproxy.py b/src/op_mode/load-balancing_haproxy.py
new file mode 100755
index 000000000..ae6734e16
--- /dev/null
+++ b/src/op_mode/load-balancing_haproxy.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023-2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import json
+import socket
+import sys
+
+from tabulate import tabulate
+from vyos.configquery import ConfigTreeQuery
+
+import vyos.opmode
+
+socket_path = '/run/haproxy/admin.sock'
+timeout = 5
+
+
+def _execute_haproxy_command(command):
+ """Execute a command on the HAProxy UNIX socket and retrieve the response.
+
+ Args:
+ command (str): The command to be executed.
+
+ Returns:
+ str: The response received from the HAProxy UNIX socket.
+
+ Raises:
+ socket.error: If there is an error while connecting or communicating with the socket.
+
+ Finally:
+ Closes the socket connection.
+
+ Notes:
+ - HAProxy expects a newline character at the end of the command.
+ - The socket connection is established using the HAProxy UNIX socket.
+ - The response from the socket is received and decoded.
+
+ Example:
+ response = _execute_haproxy_command('show stat')
+ print(response)
+ """
+ try:
+ # HAProxy expects new line for command
+ command = f'{command}\n'
+
+ # Connect to the HAProxy UNIX socket
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(socket_path)
+
+ # Set the socket timeout
+ sock.settimeout(timeout)
+
+ # Send the command
+ sock.sendall(command.encode())
+
+ # Receive and decode the response
+ response = b''
+ while True:
+ data = sock.recv(4096)
+ if not data:
+ break
+ response += data
+ response = response.decode()
+
+ return (response)
+
+ except socket.error as e:
+ print(f"Error: {e}")
+
+ finally:
+ # Close the socket
+ sock.close()
+
+
+def _convert_seconds(seconds):
+ """Convert seconds to days, hours, minutes, and seconds.
+
+ Args:
+ seconds (int): The number of seconds to convert.
+
+ Returns:
+ tuple: A tuple containing the number of days, hours, minutes, and seconds.
+ """
+ minutes = seconds // 60
+ hours = minutes // 60
+ days = hours // 24
+
+ return days, hours % 24, minutes % 60, seconds % 60
+
+
+def _last_change_format(seconds):
+ """Format the time components into a string representation.
+
+ Args:
+ seconds (int): The total number of seconds.
+
+ Returns:
+ str: The formatted time string with days, hours, minutes, and seconds.
+
+ Examples:
+ >>> _last_change_format(1434)
+ '23m54s'
+ >>> _last_change_format(93734)
+ '1d0h23m54s'
+ >>> _last_change_format(85434)
+ '23h23m54s'
+ """
+ days, hours, minutes, seconds = _convert_seconds(seconds)
+ time_format = ""
+
+ if days:
+ time_format += f"{days}d"
+ if hours:
+ time_format += f"{hours}h"
+ if minutes:
+ time_format += f"{minutes}m"
+ if seconds:
+ time_format += f"{seconds}s"
+
+ return time_format
+
+
+def _get_json_data():
+ """Get haproxy data format JSON"""
+ return _execute_haproxy_command('show stat json')
+
+
+def _get_raw_data():
+ """Retrieve raw data from JSON and organize it into a dictionary.
+
+ Returns:
+ dict: A dictionary containing the organized data categorized
+ into frontend, backend, and server.
+ """
+
+ data = json.loads(_get_json_data())
+ lb_dict = {'frontend': [], 'backend': [], 'server': []}
+
+ for key in data:
+ frontend = []
+ backend = []
+ server = []
+ for entry in key:
+ obj_type = entry['objType'].lower()
+ position = entry['field']['pos']
+ name = entry['field']['name']
+ value = entry['value']['value']
+
+ dict_entry = {'pos': position, 'name': {name: value}}
+
+ if obj_type == 'frontend':
+ frontend.append(dict_entry)
+ elif obj_type == 'backend':
+ backend.append(dict_entry)
+ elif obj_type == 'server':
+ server.append(dict_entry)
+
+ if len(frontend) > 0:
+ lb_dict['frontend'].append(frontend)
+ if len(backend) > 0:
+ lb_dict['backend'].append(backend)
+ if len(server) > 0:
+ lb_dict['server'].append(server)
+
+ return lb_dict
+
+
+def _get_formatted_output(data):
+ """
+ Format the data into a tabulated output.
+
+ Args:
+ data (dict): The data to be formatted.
+
+ Returns:
+ str: The tabulated output representing the formatted data.
+ """
+ table = []
+ headers = [
+ "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change"
+ ]
+
+ for key in data:
+ for item in data[key]:
+ row = [None] * len(headers)
+
+ for element in item:
+ if 'pxname' in element['name']:
+ row[0] = element['name']['pxname']
+ elif 'svname' in element['name']:
+ row[1] = element['name']['svname']
+ elif 'status' in element['name']:
+ row[2] = element['name']['status']
+ elif 'req_rate' in element['name']:
+ row[3] = element['name']['req_rate']
+ elif 'rtime' in element['name']:
+ row[4] = f"{element['name']['rtime']} ms"
+ elif 'lastchg' in element['name']:
+ row[5] = _last_change_format(element['name']['lastchg'])
+ table.append(row)
+
+ out = tabulate(table, headers, numalign="left")
+ return out
+
+
+def show(raw: bool):
+ config = ConfigTreeQuery()
+ if not config.exists('load-balancing haproxy'):
+ raise vyos.opmode.UnconfiguredSubsystem('Haproxy is not configured')
+
+ data = _get_raw_data()
+ if raw:
+ return data
+ else:
+ return _get_formatted_output(data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index a83c8b9d8..3b0031f34 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -41,6 +41,10 @@ service_map = {
'systemd_service': 'pdns-recursor',
'path': ['service', 'dns', 'forwarding'],
},
+ 'haproxy': {
+ 'systemd_service': 'haproxy',
+ 'path': ['load-balancing', 'haproxy'],
+ },
'igmp_proxy': {
'systemd_service': 'igmpproxy',
'path': ['protocols', 'igmp-proxy'],
@@ -53,10 +57,6 @@ service_map = {
'systemd_service': 'avahi-daemon',
'path': ['service', 'mdns', 'repeater'],
},
- 'reverse_proxy': {
- 'systemd_service': 'haproxy',
- 'path': ['load-balancing', 'reverse-proxy'],
- },
'router_advert': {
'systemd_service': 'radvd',
'path': ['service', 'router-advert'],
@@ -83,10 +83,10 @@ services = typing.Literal[
'dhcpv6',
'dns_dynamic',
'dns_forwarding',
+ 'haproxy',
'igmp_proxy',
'ipsec',
'mdns_repeater',
- 'reverse_proxy',
'router_advert',
'snmp',
'ssh',
diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/reverseproxy.py
deleted file mode 100755
index 19704182a..000000000
--- a/src/op_mode/reverseproxy.py
+++ /dev/null
@@ -1,237 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import json
-import socket
-import sys
-
-from tabulate import tabulate
-from vyos.configquery import ConfigTreeQuery
-
-import vyos.opmode
-
-socket_path = '/run/haproxy/admin.sock'
-timeout = 5
-
-
-def _execute_haproxy_command(command):
- """Execute a command on the HAProxy UNIX socket and retrieve the response.
-
- Args:
- command (str): The command to be executed.
-
- Returns:
- str: The response received from the HAProxy UNIX socket.
-
- Raises:
- socket.error: If there is an error while connecting or communicating with the socket.
-
- Finally:
- Closes the socket connection.
-
- Notes:
- - HAProxy expects a newline character at the end of the command.
- - The socket connection is established using the HAProxy UNIX socket.
- - The response from the socket is received and decoded.
-
- Example:
- response = _execute_haproxy_command('show stat')
- print(response)
- """
- try:
- # HAProxy expects new line for command
- command = f'{command}\n'
-
- # Connect to the HAProxy UNIX socket
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.connect(socket_path)
-
- # Set the socket timeout
- sock.settimeout(timeout)
-
- # Send the command
- sock.sendall(command.encode())
-
- # Receive and decode the response
- response = b''
- while True:
- data = sock.recv(4096)
- if not data:
- break
- response += data
- response = response.decode()
-
- return (response)
-
- except socket.error as e:
- print(f"Error: {e}")
-
- finally:
- # Close the socket
- sock.close()
-
-
-def _convert_seconds(seconds):
- """Convert seconds to days, hours, minutes, and seconds.
-
- Args:
- seconds (int): The number of seconds to convert.
-
- Returns:
- tuple: A tuple containing the number of days, hours, minutes, and seconds.
- """
- minutes = seconds // 60
- hours = minutes // 60
- days = hours // 24
-
- return days, hours % 24, minutes % 60, seconds % 60
-
-
-def _last_change_format(seconds):
- """Format the time components into a string representation.
-
- Args:
- seconds (int): The total number of seconds.
-
- Returns:
- str: The formatted time string with days, hours, minutes, and seconds.
-
- Examples:
- >>> _last_change_format(1434)
- '23m54s'
- >>> _last_change_format(93734)
- '1d0h23m54s'
- >>> _last_change_format(85434)
- '23h23m54s'
- """
- days, hours, minutes, seconds = _convert_seconds(seconds)
- time_format = ""
-
- if days:
- time_format += f"{days}d"
- if hours:
- time_format += f"{hours}h"
- if minutes:
- time_format += f"{minutes}m"
- if seconds:
- time_format += f"{seconds}s"
-
- return time_format
-
-
-def _get_json_data():
- """Get haproxy data format JSON"""
- return _execute_haproxy_command('show stat json')
-
-
-def _get_raw_data():
- """Retrieve raw data from JSON and organize it into a dictionary.
-
- Returns:
- dict: A dictionary containing the organized data categorized
- into frontend, backend, and server.
- """
-
- data = json.loads(_get_json_data())
- lb_dict = {'frontend': [], 'backend': [], 'server': []}
-
- for key in data:
- frontend = []
- backend = []
- server = []
- for entry in key:
- obj_type = entry['objType'].lower()
- position = entry['field']['pos']
- name = entry['field']['name']
- value = entry['value']['value']
-
- dict_entry = {'pos': position, 'name': {name: value}}
-
- if obj_type == 'frontend':
- frontend.append(dict_entry)
- elif obj_type == 'backend':
- backend.append(dict_entry)
- elif obj_type == 'server':
- server.append(dict_entry)
-
- if len(frontend) > 0:
- lb_dict['frontend'].append(frontend)
- if len(backend) > 0:
- lb_dict['backend'].append(backend)
- if len(server) > 0:
- lb_dict['server'].append(server)
-
- return lb_dict
-
-
-def _get_formatted_output(data):
- """
- Format the data into a tabulated output.
-
- Args:
- data (dict): The data to be formatted.
-
- Returns:
- str: The tabulated output representing the formatted data.
- """
- table = []
- headers = [
- "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change"
- ]
-
- for key in data:
- for item in data[key]:
- row = [None] * len(headers)
-
- for element in item:
- if 'pxname' in element['name']:
- row[0] = element['name']['pxname']
- elif 'svname' in element['name']:
- row[1] = element['name']['svname']
- elif 'status' in element['name']:
- row[2] = element['name']['status']
- elif 'req_rate' in element['name']:
- row[3] = element['name']['req_rate']
- elif 'rtime' in element['name']:
- row[4] = f"{element['name']['rtime']} ms"
- elif 'lastchg' in element['name']:
- row[5] = _last_change_format(element['name']['lastchg'])
- table.append(row)
-
- out = tabulate(table, headers, numalign="left")
- return out
-
-
-def show(raw: bool):
- config = ConfigTreeQuery()
- if not config.exists('load-balancing reverse-proxy'):
- raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured')
-
- data = _get_raw_data()
- if raw:
- return data
- else:
- return _get_formatted_output(data)
-
-
-if __name__ == '__main__':
- try:
- res = vyos.opmode.run(sys.modules[__name__])
- if res:
- print(res)
- except (ValueError, vyos.opmode.Error) as e:
- print(e)
- sys.exit(1)
--
cgit v1.2.3