mirror of
https://github.com/opnsense/plugins.git
synced 2026-02-03 20:40:37 -05:00
net/haproxy: change server state and weight on-the-fly (#2213)
* add Frontend Controller add service with script * set single server state get haproxy server status list for jquery bootstrap * set single server weight add venv folder to .gitignore * set bulk server weight set bulk server state * add copyrights * set single server weight add venv folder to .gitignore
This commit is contained in:
parent
a971e25370
commit
aa194e874a
14 changed files with 1124 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
*.pyc
|
||||
.idea
|
||||
venv
|
||||
/*/*/work
|
||||
|
|
|
|||
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2021 Andreas Stuerz
|
||||
* Copyright (C) 2015 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\HAProxy\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\HAProxy\HAProxy;
|
||||
|
||||
/**
|
||||
* Class MaintenanceController
|
||||
* @package OPNsense\HAProxy
|
||||
*/
|
||||
class MaintenanceController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* jQuery bootstrap server list
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function searchServerAction()
|
||||
{
|
||||
return $this->getData(
|
||||
["server_status_list"],
|
||||
["rowCount", "current", "searchPhrase", "sort"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set server weight
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function serverWeightAction()
|
||||
{
|
||||
return $this->saveData(
|
||||
["server_weight"],
|
||||
["backend", "server", "weight"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set server administrative state
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function serverStateAction()
|
||||
{
|
||||
return $this->saveData(
|
||||
["server_state"],
|
||||
["backend", "server", "state"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set server administrative state for multiple servers
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function serverStateBulkAction()
|
||||
{
|
||||
return $this->saveData(
|
||||
["server_state_bulk"],
|
||||
["server_ids", "state"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set server weight for multiple servers
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function serverWeightBulkAction()
|
||||
{
|
||||
return $this->saveData(
|
||||
["server_weight_bulk"],
|
||||
["server_ids", "weight"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a backend command securely
|
||||
* @param array $command
|
||||
* @param array $arguments
|
||||
* @return string
|
||||
*/
|
||||
protected function safeBackendCmd(array $command, array $arguments = [])
|
||||
{
|
||||
$backend = new Backend();
|
||||
|
||||
foreach ($arguments as $name) {
|
||||
$val = $this->request->getPost($name);
|
||||
if (is_array($val) and $name == 'sort') {
|
||||
$sort = key(array_slice($val, 0, 1));
|
||||
$sort_dir = $val[$sort];
|
||||
$command[] = $sort;
|
||||
$command[] = $sort_dir;
|
||||
continue;
|
||||
}
|
||||
$command[] = $val;
|
||||
}
|
||||
|
||||
$command = array_map(function ($value) {
|
||||
return escapeshellarg(empty($value = trim($value)) ? null : $value);
|
||||
}, $command);
|
||||
|
||||
return trim($backend->configdRun("haproxy " . join(" ", $command)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a backend command to get data
|
||||
* @param array $command
|
||||
* @param array $arguments
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function getData(array $command, array $arguments = [])
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
return $this->safeBackendCmd($command, $arguments);
|
||||
}
|
||||
return ["status" => "unavailable"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a backend command to save data
|
||||
* @param array $command
|
||||
* @param array $arguments
|
||||
* @return array|string[]
|
||||
*/
|
||||
protected function saveData(array $command, array $arguments = [])
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
if ($error = $this->safeBackendCmd($command, $arguments)) {
|
||||
return [
|
||||
"status" => "error",
|
||||
"message" => $error
|
||||
];
|
||||
} else {
|
||||
return ["status" => "ok"];
|
||||
}
|
||||
}
|
||||
return [
|
||||
"status" => 'unavailable',
|
||||
"message" => 'only accept POST Requests.'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2021 Andreas Stuerz
|
||||
* Copyright (C) 2015 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\HAProxy;
|
||||
|
||||
/**
|
||||
* Class MaintenanceController
|
||||
* @package OPNsense\HAProxy
|
||||
*/
|
||||
class MaintenanceController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
// choose template
|
||||
$this->view->pick('OPNsense/HAProxy/maintenance');
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,10 @@
|
|||
<Counters VisibleName="Counters" url="/ui/haproxy/statistics#counters"/>
|
||||
<StickTables VisibleName="Stick Tables" url="/ui/haproxy/statistics#tables"/>
|
||||
</Statistics>
|
||||
<Log VisibleName="Log File" order="30" url="/ui/diagnostics/log/core/haproxy"/>
|
||||
<Maintenance VisibleName="Maintenance" order="30" url="/ui/haproxy/maintenance">
|
||||
<Server VisibleName="Server" url="/ui/haproxy/maintenance#server"/>
|
||||
</Maintenance>
|
||||
<Log VisibleName="Log File" order="40" url="/ui/diagnostics/log/core/haproxy"/>
|
||||
</HAProxy>
|
||||
</Services>
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,286 @@
|
|||
{#
|
||||
|
||||
Copyright (C) 2021 Andreas Stuerz
|
||||
OPNsense® is Copyright © 2014 – 2016 by Deciso B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#}
|
||||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
$("#grid-status").bootgrid('destroy');
|
||||
var grid_status = $("#grid-status").UIBootgrid({
|
||||
search: '/api/haproxy/maintenance/searchServer',
|
||||
options: {
|
||||
ajax: true,
|
||||
selection: true,
|
||||
multiSelect: true,
|
||||
keepSelection: true,
|
||||
rowCount:[10,25,50,100,500,1000],
|
||||
searchSettings: {
|
||||
delay: 250,
|
||||
characters: 1
|
||||
},
|
||||
formatters: {
|
||||
"commands": function (column, row) {
|
||||
buttons = ""
|
||||
buttons += "<button type=\"button\" title=\"Set administrative state to ready. Puts the server in normal mode.\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"ready\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-check\"></span></button>"
|
||||
buttons += " <button type=\"button\" title=\"Set administrative state to drain. Removes the server from load balancing but still allows it to be health checked and to accept new persistent connections\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"drain\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-sort-amount-desc\"></span></button>"
|
||||
buttons += " <button type=\"button\" title=\"Set administrative state to maintenance. Disables any traffic to the server as well as any health checks.\" class=\"btn btn-xs btn-default command-set-state\" data-state=\"maint\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-wrench\"></span></button>"
|
||||
buttons += " <button type=\"button\" title=\"Change a server's weight.\" class=\"btn btn-xs btn-default command-set-weight\" data-weight=\"" + row.weight + "\" data-row-id=\"" + row.id + "\"><span class=\"fa fa-balance-scale\"></span></button>"
|
||||
return buttons;
|
||||
},
|
||||
},
|
||||
}
|
||||
}).on("loaded.rs.jquery.bootgrid", function(){
|
||||
// set single - server state
|
||||
grid_status.find(".command-set-state").off().on("click", function(e) {
|
||||
var uuid = $(this).data("row-id");
|
||||
var backend = uuid.split("/")[0];
|
||||
var server = uuid.split("/")[1];
|
||||
var state = $(this).data("state");
|
||||
var payload = {
|
||||
'backend': backend,
|
||||
'server': server,
|
||||
'state': state
|
||||
};
|
||||
|
||||
question = '<b>{{ lang._('Server: ') }}' + uuid + '</b></br>';
|
||||
question += '<b>{{ lang._('State: ') }}' + state + '</b></br></br>';
|
||||
question += '{{ lang._('Set administrative state for this server?') }} </br></br>';
|
||||
|
||||
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
|
||||
question,
|
||||
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function() {
|
||||
$.post('/api/haproxy/maintenance/serverState', payload, function(data) {
|
||||
if (data.status != 'ok') {
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "{{ lang._('Error setting HAProxy server administrative state') }}",
|
||||
message: data.message,
|
||||
buttons: [{
|
||||
label: '{{ lang._('Close') }}',
|
||||
action: function(dialog){
|
||||
dialog.close();
|
||||
}
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
$("#grid-status").bootgrid("reload");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// set single - server weight
|
||||
grid_status.find(".command-set-weight").off().on("click", function(e) {
|
||||
var uuid = $(this).data("row-id");
|
||||
var backend = uuid.split("/")[0];
|
||||
var server = uuid.split("/")[1];
|
||||
var currentWeight = $(this).data("weight");
|
||||
|
||||
question = '<b>{{ lang._('Server: ') }}' + uuid + '</b></br></br>';
|
||||
question += '<b>{{ lang._('Weight: ') }}</b>';
|
||||
question += '<div class="form-group" style="display: block;">';
|
||||
question += '<input class="form-control" id="newWeight" value="' + currentWeight + '" type="text"/>';
|
||||
question += '</div>';
|
||||
question += '{{ lang._('Set weight for this server?') }} </br></br>';
|
||||
|
||||
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
|
||||
question,
|
||||
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function() {
|
||||
|
||||
var payload = {
|
||||
'backend': backend,
|
||||
'server': server,
|
||||
'weight': $("#newWeight").val()
|
||||
};
|
||||
|
||||
$.post('/api/haproxy/maintenance/serverWeight', payload, function(data) {
|
||||
if (data.status != 'ok') {
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "{{ lang._('Error setting HAProxy server weight') }}",
|
||||
message: data.message,
|
||||
buttons: [{
|
||||
label: '{{ lang._('Close') }}',
|
||||
action: function(dialog){
|
||||
dialog.close();
|
||||
}
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
$("#grid-status").bootgrid("reload");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// set bulk - server state
|
||||
grid_status.find("*[data-action=setStateBulk]").off().on("click", function(e) {
|
||||
var rows = $("#grid-status").bootgrid("getSelectedRows");
|
||||
var server_ids = rows.join()
|
||||
var state = $(this).data("state");
|
||||
var payload = {
|
||||
'server_ids': server_ids,
|
||||
'state': state
|
||||
};
|
||||
|
||||
if (rows != undefined && rows.length > 0) {
|
||||
question = '<b>{{ lang._('Selected server: ') }}</b></br>';
|
||||
question += '<ul>';
|
||||
$.each(rows, function(key, id){
|
||||
question += '<li>' + id + '</li>';
|
||||
});
|
||||
question += '</ul>';
|
||||
question += '<b>{{ lang._('State: ') }}' + state + '</b></br></br>';
|
||||
question += '{{ lang._('Set administrative state for all selected server?') }} </br></br>';
|
||||
|
||||
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
|
||||
question,
|
||||
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function() {
|
||||
$.post('/api/haproxy/maintenance/serverStateBulk', payload, function(data) {
|
||||
if (data.status != 'ok') {
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "{{ lang._('Error setting HAProxy server administrative state') }}",
|
||||
message: data.message,
|
||||
buttons: [{
|
||||
label: '{{ lang._('Close') }}',
|
||||
action: function(dialog){
|
||||
dialog.close();
|
||||
// reload - because some are successfully executed
|
||||
$("#grid-status").bootgrid("reload");
|
||||
}
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
$("#grid-status").bootgrid("deselect");
|
||||
$("#grid-status").bootgrid("reload");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// set bulk - server weight
|
||||
grid_status.find("*[data-action=setWeightBulk]").off().on("click", function(e) {
|
||||
var rows = $("#grid-status").bootgrid("getSelectedRows");
|
||||
var server_ids = rows.join()
|
||||
|
||||
if (rows != undefined && rows.length > 0) {
|
||||
question = '<b>{{ lang._('Selected server: ') }}</b></br>';
|
||||
question += '<ul>';
|
||||
$.each(rows, function(key, id){
|
||||
question += '<li>' + id + '</li>';
|
||||
});
|
||||
question += '</ul>';
|
||||
question += '<b>{{ lang._('Weight: ') }}</b>';
|
||||
question += '<div class="form-group" style="display: block;">';
|
||||
question += '<input class="form-control" id="newBulkWeight" value="" type="text"/>';
|
||||
question += '</div>';
|
||||
question += '{{ lang._('Set weight for all selected server?') }} </br></br>';
|
||||
|
||||
stdDialogConfirm('{{ lang._('Confirmation Required') }}',
|
||||
question,
|
||||
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function() {
|
||||
var payload = {
|
||||
'server_ids': server_ids,
|
||||
'weight': $("#newBulkWeight").val()
|
||||
};
|
||||
|
||||
$.post('/api/haproxy/maintenance/serverWeightBulk', payload, function(data) {
|
||||
if (data.status != 'ok') {
|
||||
BootstrapDialog.show({
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
title: "{{ lang._('Error setting HAProxy server weight') }}",
|
||||
message: data.message,
|
||||
buttons: [{
|
||||
label: '{{ lang._('Close') }}',
|
||||
action: function(dialog){
|
||||
dialog.close();
|
||||
// reload - because some are successfully executed
|
||||
$("#grid-status").bootgrid("reload");
|
||||
|
||||
}
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
$("#grid-status").bootgrid("deselect");
|
||||
$("#grid-status").bootgrid("reload");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#server"><b>{{ lang._('Server') }}</b></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="content-box tab-content">
|
||||
<div id="server" class="tab-pane fade in active">
|
||||
<!-- tab page "server" -->
|
||||
<table id="grid-status" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="string" data-identifier="true" data-visible="false">{{ lang._('id') }}</th>
|
||||
<th data-column-id="pxname" data-type="string">{{ lang._('Proxy') }}</th>
|
||||
<th data-column-id="svname" data-type="string">{{ lang._('Server') }}</th>
|
||||
<th data-column-id="addr" data-type="string">{{ lang._('Address') }}</th>
|
||||
<th data-column-id="status" data-type="string">{{ lang._('Status') }}</th>
|
||||
<th data-column-id="check_status" data-type="string">{{ lang._('Check Status') }}</th>
|
||||
<th data-column-id="weight" data-type="string">{{ lang._('Weight') }}</th>
|
||||
<th data-column-id="scur" data-type="string">{{ lang._('Sessions') }}</th>
|
||||
<th data-column-id="bin" data-type="string">{{ lang._('Bytes in') }}</th>
|
||||
<th data-column-id="bout" data-type="string">{{ lang._('Bytes out') }}</th>
|
||||
<th data-column-id="act" data-type="string">{{ lang._('Active') }}</th>
|
||||
<th data-column-id="downtime" data-type="string">{{ lang._('Downtime') }}</th>
|
||||
<th data-column-id="lastchg" data-type="string">{{ lang._('Last Change') }}</th>
|
||||
<th data-column-id="commands" data-width="8em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button data-action="setStateBulk" title="Set administrative state to ready for all selected items." data-state="ready" type="button" class="btn btn-xs btn-default"><span class="fa fa-check"></span></button>
|
||||
<button data-action="setStateBulk" title="Set administrative state to drain for all selected items." data-state="drain" type="button" class="btn btn-xs btn-default"><span class="fa fa-sort-amount-desc"></span></button>
|
||||
<button data-action="setStateBulk" title="Set administrative state to maintenance for all selected items." data-state="maint" type="button" class="btn btn-xs btn-default"><span class="fa fa-wrench"></span></button>
|
||||
<button data-action="setWeightBulk" data-weight="" type="button" class="btn btn-xs btn-default"><span class="fa fa-balance-scale"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ partial("layout_partials/base_dialog_processing") }}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""haproxy lib for socket commands.
|
||||
Based on: https://github.com/neurogeek/haproxyctl"""
|
||||
__version__ = "1.0"
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
# pylint: disable=locally-disabled, too-few-public-methods, no-self-use, invalid-name
|
||||
"""cmds.py - Implementations of the different HAProxy commands"""
|
||||
|
||||
import re
|
||||
import csv
|
||||
import json
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class Cmd():
|
||||
"""Cmd - Command base class"""
|
||||
req_args = []
|
||||
args = {}
|
||||
cmdTxt = ""
|
||||
helpTxt = ""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Argument to the command are given in kwargs only. We ignore *args."""
|
||||
self.args = kwargs
|
||||
valid_kwargs = [k for (k, v) in kwargs.items() if v is not None]
|
||||
|
||||
if not all([a in valid_kwargs for a in self.req_args]):
|
||||
raise Exception(f"Wrong number of arguments. Required arguments are: {self.WhatArgs()}")
|
||||
|
||||
def WhatArgs(self):
|
||||
"""Returns a formatted string of arguments to this command."""
|
||||
return ",".join(self.req_args)
|
||||
|
||||
@classmethod
|
||||
def getHelp(cls):
|
||||
"""Get formatted help string for this command."""
|
||||
txtArgs = ",".join(cls.req_args)
|
||||
|
||||
if not txtArgs:
|
||||
txtArgs = "None"
|
||||
return " ".join((cls.helpTxt, "Arguments: %s" % txtArgs))
|
||||
|
||||
def getCmd(self):
|
||||
"""Gets the command line for this command.
|
||||
The default behavior is to apply the args dict to cmdTxt
|
||||
"""
|
||||
return self.cmdTxt % self.args
|
||||
|
||||
def getResult(self, res):
|
||||
"""Returns raw results gathered from HAProxy"""
|
||||
if res == '\n':
|
||||
res = None
|
||||
return res
|
||||
|
||||
def getResultObj(self, res):
|
||||
"""Returns refined output from HAProxy, packed inside a Python obj i.e. a dict()"""
|
||||
return res
|
||||
|
||||
|
||||
class setServerAgent(Cmd):
|
||||
"""Set server agent command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s agent %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's agent to a new state."
|
||||
|
||||
|
||||
class setServerHealth(Cmd):
|
||||
"""Set server health command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s health %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's health to a new state."
|
||||
|
||||
|
||||
class setServerState(Cmd):
|
||||
"""Set server state command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s state %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's administrative state to a new state."
|
||||
|
||||
|
||||
class setServerWeight(Cmd):
|
||||
"""Set server weight command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s weight %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's weight to a new state."
|
||||
|
||||
|
||||
class showFBEnds(Cmd):
|
||||
"""Base class for getting a listing Frontends and Backends"""
|
||||
switch = ""
|
||||
cmdTxt = "show stat\r\n"
|
||||
|
||||
def getResult(self, res):
|
||||
return "\n".join(self._getResult(res))
|
||||
|
||||
def getResultObj(self, res):
|
||||
return self._getResult(res)
|
||||
|
||||
def _getResult(self, res):
|
||||
"""Show Frontend/Backends. To do this, we extract info from
|
||||
the stat command and filter out by a specific
|
||||
switch (FRONTEND/BACKEND)"""
|
||||
|
||||
if not self.switch:
|
||||
raise Exception("No action specified")
|
||||
|
||||
result = []
|
||||
lines = res.split('\n')
|
||||
cl = re.compile("^[^,].+," + self.switch.upper() + ",.*$")
|
||||
|
||||
for e in lines:
|
||||
me = re.match(cl, e)
|
||||
if me:
|
||||
result.append(e.split(",")[0])
|
||||
return result
|
||||
|
||||
|
||||
class showFrontends(showFBEnds):
|
||||
"""Show frontends command."""
|
||||
switch = "frontend"
|
||||
helpTxt = "List all Frontends."
|
||||
|
||||
|
||||
class showBackends(showFBEnds):
|
||||
"""Show backends command."""
|
||||
switch = "backend"
|
||||
helpTxt = "List all Backends."
|
||||
|
||||
|
||||
class showInfo(Cmd):
|
||||
"""Show info HAProxy command"""
|
||||
cmdTxt = "show info\r\n"
|
||||
helpTxt = "Show info on HAProxy instance."
|
||||
|
||||
def getResultObj(self, res):
|
||||
resDict = {}
|
||||
for line in res.split('\n'):
|
||||
k, v = line.split(':')
|
||||
resDict[k] = v
|
||||
|
||||
return resDict
|
||||
|
||||
|
||||
class showSessions(Cmd):
|
||||
"""Show sess HAProxy command"""
|
||||
cmdTxt = "show sess\r\n"
|
||||
helpTxt = "Show HAProxy sessions."
|
||||
|
||||
def getResultObj(self, res):
|
||||
return res.split('\n')
|
||||
|
||||
|
||||
class baseStat(Cmd):
|
||||
"""Base class for stats commands."""
|
||||
|
||||
def getDict(self, res):
|
||||
# clean response
|
||||
res = re.sub(r'^# ', '', res, re.MULTILINE)
|
||||
res = re.sub(r',\n', '\n', res, re.MULTILINE)
|
||||
res = re.sub(r',\n\n', '\n', res, re.MULTILINE)
|
||||
|
||||
csv_string = StringIO(res)
|
||||
return csv.DictReader(csv_string, delimiter=',')
|
||||
|
||||
def getBootstrapOutput(self, **kwargs):
|
||||
rows = kwargs['rows']
|
||||
# search
|
||||
if kwargs['search']:
|
||||
filtered_rows = []
|
||||
for row in rows:
|
||||
def inner(row):
|
||||
for k, v in row.items():
|
||||
if kwargs['search'] in v:
|
||||
return row
|
||||
return None
|
||||
|
||||
match = inner(row)
|
||||
if match:
|
||||
filtered_rows.append(match)
|
||||
rows = filtered_rows
|
||||
|
||||
# sort
|
||||
rows.sort(key=lambda k: k[kwargs['sort_col']], reverse=True if kwargs['sort_dir'] == 'desc' else False)
|
||||
|
||||
# pager
|
||||
total = len(rows)
|
||||
pages = [rows[i:i + kwargs['page_rows']] for i in range(0, total, kwargs['page_rows'])]
|
||||
if pages and (kwargs['page'] > len(pages) or kwargs['page'] < 1):
|
||||
raise KeyError(f"Current page {kwargs['page']} does not exist. Available pages: {len(pages)}")
|
||||
page = pages[kwargs['page'] - 1] if pages else []
|
||||
|
||||
return json.dumps({
|
||||
"rows": page,
|
||||
"total": total,
|
||||
"rowCount": kwargs['page_rows'],
|
||||
"current": kwargs['page']
|
||||
})
|
||||
|
||||
|
||||
class showServers(baseStat):
|
||||
"""Show all servers. If backend is given, show only servers for this backend. """
|
||||
cmdTxt = "show stat\r\n"
|
||||
helpTxt = "Lists all servers. Filter for servers in backend, if set."
|
||||
|
||||
def getResult(self, res):
|
||||
if self.args['output'] == 'json':
|
||||
return json.dumps(self.getResultObj(res))
|
||||
|
||||
if self.args['output'] == 'bootstrap':
|
||||
rows = self.getResultObj(res)
|
||||
args = {
|
||||
"rows": rows,
|
||||
"page": int(self.args['page']) if self.args['page'] != None else 1,
|
||||
"page_rows": int(self.args['page_rows']) if self.args['page_rows'] != None else len(rows),
|
||||
"search": self.args['search'],
|
||||
"sort_col": self.args['sort_col'] if self.args['sort_col'] else 'id',
|
||||
"sort_dir": self.args['sort_dir'],
|
||||
}
|
||||
return self.getBootstrapOutput(**args)
|
||||
|
||||
return self.getResultObj(res)
|
||||
|
||||
def getResultObj(self, res):
|
||||
servers = []
|
||||
|
||||
reader = self.getDict(res)
|
||||
for row in reader:
|
||||
# show only server
|
||||
if row['svname'] in ['BACKEND', 'FRONTEND']:
|
||||
continue
|
||||
|
||||
# filter server for given backend
|
||||
if self.args['backend'] and row['pxname'] != self.args['backend']:
|
||||
continue
|
||||
|
||||
# add id
|
||||
row['id'] = f"{row['pxname']}/{row['svname']}"
|
||||
row.move_to_end('id', last=False)
|
||||
servers.append(dict(row))
|
||||
|
||||
return servers
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
# pylint: disable=locally-disabled, too-few-public-methods, no-self-use, invalid-name
|
||||
"""conn.py - Connection module."""
|
||||
import re
|
||||
from socket import socket, AF_INET, AF_UNIX, SOCK_STREAM
|
||||
from haproxy import const
|
||||
|
||||
class HapError(Exception):
|
||||
"""Generic exception for haproxyctl."""
|
||||
pass
|
||||
|
||||
class HaPConn(object):
|
||||
"""HAProxy Socket object.
|
||||
This class abstract the socket interface so
|
||||
commands can be sent to HAProxy and results received and
|
||||
parse by the command objects"""
|
||||
|
||||
def __init__(self, sfile, socket_module=socket):
|
||||
"""Initializes an HAProxy and opens a connection to it
|
||||
(sfile, type) -> Path for the UNIX socket"""
|
||||
|
||||
self.sock = None
|
||||
sfile = sfile.strip()
|
||||
stype = AF_UNIX
|
||||
self.socket_module = socket_module
|
||||
|
||||
mobj = re.match(
|
||||
'(?P<proto>unix://|tcp://)(?P<addr>[^:]+):*(?P<port>[0-9]*)$', sfile)
|
||||
|
||||
if mobj:
|
||||
proto = mobj.groupdict().get('proto', None)
|
||||
addr = mobj.groupdict().get('addr', None)
|
||||
port = mobj.groupdict().get('port', '')
|
||||
|
||||
if not addr or not proto:
|
||||
raise HapError('Could not determine type of socket.')
|
||||
|
||||
if proto == const.HAP_TCP_PATH:
|
||||
if not port:
|
||||
raise HapError('When using a tcp socket, a port is needed.')
|
||||
stype = AF_INET
|
||||
sfile = (addr, int(port))
|
||||
|
||||
if proto == const.HAP_UNIX_PATH:
|
||||
stype = AF_UNIX
|
||||
sfile = addr
|
||||
|
||||
# Fallback should be sfile/AF_UNIX by default
|
||||
self.sfile = (sfile, stype)
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
"""Opens a connection for the socket.
|
||||
This function should only be called if
|
||||
self.closed() method was called"""
|
||||
|
||||
sfile, stype = self.sfile
|
||||
self.sock = self.socket_module(stype, SOCK_STREAM)
|
||||
self.sock.connect(sfile)
|
||||
|
||||
def sendCmd(self, cmd, objectify=False):
|
||||
"""Receives a command obj and sends it to the socket. Receives the output and passes it
|
||||
through the command to parse it.
|
||||
objectify -> Return an object instead of plain text"""
|
||||
|
||||
res = ""
|
||||
try:
|
||||
self.sock.send(cmd.getCmd())
|
||||
except TypeError:
|
||||
self.sock.send(bytearray(cmd.getCmd(), 'ASCII'))
|
||||
output = self.sock.recv(const.HAP_BUFSIZE)
|
||||
|
||||
while output:
|
||||
res += output.decode('ASCII')
|
||||
output = self.sock.recv(const.HAP_BUFSIZE)
|
||||
|
||||
if objectify:
|
||||
return cmd.getResultObj(res)
|
||||
|
||||
return cmd.getResult(res)
|
||||
|
||||
def close(self):
|
||||
"""Closes the socket"""
|
||||
self.sock.close()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"""const.py - Constants for haproxyctl."""
|
||||
HAP_OK = 1
|
||||
HAP_ERR = 2
|
||||
HAP_SOCK_ERR = 3
|
||||
HAP_BUFSIZE = 8192
|
||||
HAP_UNIX_PATH = 'unix://'
|
||||
HAP_TCP_PATH = 'tcp://'
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# pylint: disable=star-args, locally-disabled, too-few-public-methods, no-self-use, invalid-name
|
||||
"""test_cmds.py - Unittests related to command implementations."""
|
||||
import sys, os, unittest
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from haproxy import cmds
|
||||
|
||||
class TestCommands(unittest.TestCase):
|
||||
"""Tests all of the commands."""
|
||||
def setUp(self):
|
||||
|
||||
self.Resp = {"disable" : "disable server redis-ro/redis-ro0",
|
||||
"set-server-agent" : "set server redis-ro/redis-ro0 agent up",
|
||||
"set-server-health" : "set server redis-ro/redis-ro0 health stopping",
|
||||
"set-server-state" : "set server redis-ro/redis-ro0 state drain",
|
||||
"set-server-weight" : "set server redis-ro/redis-ro0 weight 10",
|
||||
"frontends" : "show stat",
|
||||
"info" : "show info",
|
||||
"sessions" : "show sess",
|
||||
"servers" : "show stat",
|
||||
|
||||
}
|
||||
|
||||
self.Resp = dict([(k, v + "\r\n") for k, v in self.Resp.items()])
|
||||
|
||||
def test_setServerAgent(self):
|
||||
"""Test 'set server agent' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "up"}
|
||||
cmdSetServerAgent = cmds.setServerAgent(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerAgent, self.Resp["set-server-agent"])
|
||||
|
||||
def test_setServerHealth(self):
|
||||
"""Test 'set server health' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "stopping"}
|
||||
cmdSetServerHealth = cmds.setServerHealth(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerHealth, self.Resp["set-server-health"])
|
||||
|
||||
def test_setServerState(self):
|
||||
"""Test 'set server state' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "drain"}
|
||||
cmdSetServerState = cmds.setServerState(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerState, self.Resp["set-server-state"])
|
||||
|
||||
def test_setServerWeight(self):
|
||||
"""Test 'set server weight' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "10"}
|
||||
cmdSetServerState = cmds.setServerWeight(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerState, self.Resp["set-server-weight"])
|
||||
|
||||
def test_showFrontends(self):
|
||||
"""Test 'frontends/backends' commands"""
|
||||
args = {}
|
||||
cmdFrontends = cmds.showFrontends(**args).getCmd()
|
||||
self.assertEqual(cmdFrontends, self.Resp["frontends"])
|
||||
|
||||
def test_showInfo(self):
|
||||
"""Test 'show info' command"""
|
||||
cmdShowInfo = cmds.showInfo().getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["info"])
|
||||
|
||||
def test_showSessions(self):
|
||||
"""Test 'show info' command"""
|
||||
cmdShowInfo = cmds.showSessions().getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["sessions"])
|
||||
|
||||
def test_showServers(self):
|
||||
"""Test 'show info' command"""
|
||||
args = {"backend": "redis-ro"}
|
||||
cmdShowInfo = cmds.showServers(**args).getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["servers"])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# pylint: disable=locally-disabled, too-few-public-methods, no-self-use, invalid-name, broad-except
|
||||
"""test_conn.py - Unittests related to connections to HAProxy."""
|
||||
import sys, os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from haproxy import conn
|
||||
import unittest
|
||||
from socket import AF_INET, AF_UNIX
|
||||
|
||||
class SimpleConnMock(object):
|
||||
"""Simple socket mock."""
|
||||
def __init__(self, stype, stream):
|
||||
self.stype = stype
|
||||
self.stream = stream
|
||||
|
||||
def connect(self, addr):
|
||||
"""Mocked socket.connect method."""
|
||||
pass
|
||||
|
||||
class TestConnection(unittest.TestCase):
|
||||
"""Tests different aspects of haproxyctl's connections to HAProxy."""
|
||||
|
||||
def testConnSimple(self):
|
||||
"""Tests that connection to non-protocol path works and fallsback to UNIX socket."""
|
||||
sfile = "/some/path/to/socket.sock"
|
||||
c = conn.HaPConn(sfile, socket_module=SimpleConnMock)
|
||||
addr, stype = c.sfile
|
||||
self.assertEqual(sfile, addr)
|
||||
self.assertEqual(stype, AF_UNIX)
|
||||
|
||||
def testConnUnixString(self):
|
||||
"""Tests that unix:// protocol works and connects to a socket."""
|
||||
sfile = "unix:///some/path/to/socket.socket"
|
||||
c = conn.HaPConn(sfile, socket_module=SimpleConnMock)
|
||||
addr, stype = c.sfile
|
||||
self.assertEqual("/some/path/to/socket.socket", addr)
|
||||
self.assertEqual(stype, AF_UNIX)
|
||||
|
||||
def testConnTCPString(self):
|
||||
"""Tests that tcp:// protocol works and connects to an IP."""
|
||||
sfile = "tcp://1.2.3.4:8080"
|
||||
c = conn.HaPConn(sfile, socket_module=SimpleConnMock)
|
||||
addr, stype = c.sfile
|
||||
ip, port = addr
|
||||
self.assertEqual("1.2.3.4", ip)
|
||||
self.assertEqual(8080, port)
|
||||
self.assertEqual(stype, AF_INET)
|
||||
|
||||
def testConnTCPStringNoPort(self):
|
||||
"""Tests that passing a tcp:// address with no port, raises an Exception."""
|
||||
sfile = "tcp://1.2.3.4"
|
||||
# Not using assertRaises because we still support 2.6
|
||||
try:
|
||||
conn.HaPConn(sfile, socket_module=SimpleConnMock)
|
||||
raise Exception('Connection should have thrown an exception')
|
||||
except conn.HapError:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
126
net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
Executable file
126
net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
|
||||
from haproxy.conn import HaPConn
|
||||
from haproxy import cmds
|
||||
|
||||
SOCKET = '/var/run/haproxy.socket'
|
||||
VALID_COMMANDS = {
|
||||
"set-server-agent": cmds.setServerAgent,
|
||||
"set-server-health": cmds.setServerHealth,
|
||||
"set-server-state": cmds.setServerState,
|
||||
"set-server-weight": cmds.setServerWeight,
|
||||
"show-frontends": cmds.showFrontends,
|
||||
"show-backends": cmds.showBackends,
|
||||
"show-info": cmds.showInfo,
|
||||
"show-sessions": cmds.showSessions,
|
||||
"show-servers": cmds.showServers,
|
||||
}
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description='Send haproxy commands via socket.')
|
||||
parser.add_argument(
|
||||
'command',
|
||||
choices=list(VALID_COMMANDS),
|
||||
help='The command to execute via haproxy socket'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--backend',
|
||||
help='Attempt action on given backend.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--server',
|
||||
help='Attempt action on given server.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--server-ids',
|
||||
help='Attempt action on a list of server, specified as a comma seperated list e.g. back1/server1,back2/server3',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--value',
|
||||
help='Specify value for a set command.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
help='Specify output format.',
|
||||
choices=['json', 'bootstrap'],
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--page-rows',
|
||||
help='Limit output to the specified numbers of rows per page.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--page',
|
||||
help='Output page number.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
help='Search for string.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort-col',
|
||||
help='Sort output on this column.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort-dir',
|
||||
help='Sort output in this direction.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--debug',
|
||||
type=bool,
|
||||
help='Show debug output.',
|
||||
default=False
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
args = get_args()
|
||||
command_class = VALID_COMMANDS.get(args.command, None)
|
||||
command_args = {key: val for key, val in vars(args).items() if key != "command"}
|
||||
|
||||
try:
|
||||
if args.server_ids:
|
||||
# bulk
|
||||
command_bulk_args = command_args
|
||||
command_bulk_args.pop('server_ids', None)
|
||||
for server_id in args.server_ids.split(","):
|
||||
command_bulk_args.update({
|
||||
'backend': server_id.split("/")[0],
|
||||
'server': server_id.split("/")[1]
|
||||
})
|
||||
con = HaPConn(SOCKET)
|
||||
if con:
|
||||
result = con.sendCmd(command_class(**command_bulk_args), objectify=False)
|
||||
if result:
|
||||
print(f"{server_id}: {result.strip()}")
|
||||
con.close()
|
||||
|
||||
else:
|
||||
# single
|
||||
con = HaPConn(SOCKET)
|
||||
if con:
|
||||
result = con.sendCmd(command_class(**command_args), objectify=False)
|
||||
if result:
|
||||
print(result.strip())
|
||||
else:
|
||||
print(f"Could not open socket {SOCKET}")
|
||||
|
||||
except Exception as exc:
|
||||
print(f"While talking to {SOCKET}: {exc}")
|
||||
if args['debug']:
|
||||
tb = traceback.format_exc()
|
||||
print(tb)
|
||||
|
|
@ -45,3 +45,33 @@ command:/usr/local/opnsense/scripts/OPNsense/HAProxy/queryStats.php
|
|||
parameters:%s
|
||||
type:script_output
|
||||
message:requesting haproxy statistics
|
||||
|
||||
[server_status_list]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
||||
parameters: show-servers --output bootstrap --page-rows %s --page %s --search %s --sort-col %s --sort-dir %s
|
||||
type:script_output
|
||||
message:show server status list
|
||||
|
||||
[server_state]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
||||
parameters: set-server-state --backend %s --server %s --value %s
|
||||
type:script_output
|
||||
message:change haproxy server state
|
||||
|
||||
[server_weight]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
||||
parameters: set-server-weight --backend %s --server %s --value %s
|
||||
type:script_output
|
||||
message:change haproxy server weight
|
||||
|
||||
[server_state_bulk]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
||||
parameters: set-server-state --server-ids %s --value %s
|
||||
type:script_output
|
||||
message:change haproxy state for multiple server
|
||||
|
||||
[server_weight_bulk]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
||||
parameters: set-server-weight --server-ids %s --value %s
|
||||
type:script_output
|
||||
message:change haproxy weight for multiple server
|
||||
Loading…
Reference in a new issue