summaryrefslogtreecommitdiff
path: root/src/op_mode/reverseproxy.py
blob: 44ffd7a37f6876dcfd28729b9b5715082c6f0cbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env python3
#
# Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.

import json
import socket
import sys
import typing

from sys import exit
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)