From e190e9c138c1c80f7a18e6cf9474cdb9ae2ccbaa Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Sun, 15 Dec 2024 20:09:09 +0100 Subject: [PATCH] System: High Availability: Status - backend code for https://github.com/opnsense/core/issues/7899 This commit wraps our xmlrpc functions via configd and wires them via an api controller. In the long run we should consider moving to RESTful interfaces, but for now we will keep and cleanup the xmlrpc code. configd action "system ha services_cached" caches the service list for a couple of seconds to improve searchability via our standard grid functions. --- contrib/IXR/IXR_Library.php | 1 + plist | 1 + src/etc/inc/XMLRPC_Client.inc | 57 ++++++++++ .../Core/Api/HasyncStatusController.php | 103 ++++++++++++++++++ .../scripts/system/ha_xmlrpc_exec.php | 70 ++++++++++++ .../conf/actions.d/actions_system.conf | 13 +++ 6 files changed, 245 insertions(+) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncStatusController.php create mode 100755 src/opnsense/scripts/system/ha_xmlrpc_exec.php diff --git a/contrib/IXR/IXR_Library.php b/contrib/IXR/IXR_Library.php index 2f2c814c4..3cbe7344f 100644 --- a/contrib/IXR/IXR_Library.php +++ b/contrib/IXR/IXR_Library.php @@ -176,6 +176,7 @@ class IXR_Message var $faultString; var $methodName; var $params; + var $currentTag; // Current variable stacks var $_arraystructs = array(); // The stack used to keep track of the current array/struct diff --git a/plist b/plist index 64d9d4347..91199c5ad 100644 --- a/plist +++ b/plist @@ -1259,6 +1259,7 @@ /usr/local/opnsense/scripts/system/crl_fetch.py /usr/local/opnsense/scripts/system/flush_config_history /usr/local/opnsense/scripts/system/get_locales.php +/usr/local/opnsense/scripts/system/ha_xmlrpc_exec.php /usr/local/opnsense/scripts/system/nameservers.php /usr/local/opnsense/scripts/system/remote_backup.php /usr/local/opnsense/scripts/system/rfc5246_cipher_suites.csv diff --git a/src/etc/inc/XMLRPC_Client.inc b/src/etc/inc/XMLRPC_Client.inc index 36c06fd93..1acf86837 100644 --- a/src/etc/inc/XMLRPC_Client.inc +++ b/src/etc/inc/XMLRPC_Client.inc @@ -28,6 +28,56 @@ require_once("IXR/IXR_Library.php"); +/** + * Simple wrapper around xmlrpc sync using configuration data. + * In the long run we should likely switch to RESTful implementations as xmlrpc is rather old and less common + * nowadays. + * + * @param string $method method to call + * @param array $params parameters to pass + * @param bool $debug debug mode + * @return array + */ +function xmlrpc_execute($method, $params = [], $debug = false) +{ + global $config; + $synchronizeto = null; + $hasync = $config['hasync']; + if (is_ipaddrv6($hasync['synchronizetoip'])) { + $hasync['synchronizetoip'] = "[{$hasync['synchronizetoip']}]"; + } + + if (!empty($hasync['synchronizetoip'])) { + // determine target url + if (substr($hasync['synchronizetoip'],0, 4) == 'http') { + // URL provided + if (substr($hasync['synchronizetoip'], strlen($hasync['synchronizetoip'])-1, 1) == '/') { + $synchronizeto = $hasync['synchronizetoip']."xmlrpc.php"; + } else { + $synchronizeto = $hasync['synchronizetoip']."/xmlrpc.php"; + } + } elseif (!empty($config['system']['webgui']['protocol'])) { + // no url provided, assume the backup is using the same settings as our box. + $port = $config['system']['webgui']['port']; + if (!empty($port)) { + $synchronizeto = $config['system']['webgui']['protocol'] . '://'.$hasync['synchronizetoip'].':'.$port."/xmlrpc.php"; + } else { + $synchronizeto = $config['system']['webgui']['protocol'] . '://'.$hasync['synchronizetoip']."/xmlrpc.php" ; + } + } + + $username = empty($hasync['username']) ? "root" : $hasync['username']; + $client = new SimpleXMLRPC_Client($synchronizeto,240); + $client->debug=$debug; + $client->setCredentials($username, $hasync['password']); + if ($client->query($method, $params)) { + return $client->getResponse(); + } + } + return false; +} + + /** * Simple XMLRPC client based on the components from IXR * mainly used for backward compatibility of ha sync feature @@ -64,6 +114,13 @@ class SimpleXMLRPC_Client */ private $timeout = 60; + + /** + * request url + * @var string + */ + private $url = ''; + /** * (last) response message * @var null diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncStatusController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncStatusController.php new file mode 100644 index 000000000..cb181585b --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/HasyncStatusController.php @@ -0,0 +1,103 @@ +configdRun('system ha exec exec_sync'); + $backend->configdRun('system ha exec reload_templates'); + return json_decode($backend->configdpRun('system ha exec', [$action, $service, $service_id]), true); + } + + public function versionAction() + { + return json_decode((new Backend())->configdRun('system ha exec version'), true); + } + + public function servicesAction() + { + $data = json_decode((new Backend())->configdRun('system ha services_cached'), true); + $records = !empty($data['response']) ? $data['response'] : []; + return $this->searchRecordsetBase($records); + } + + public function stopAction($service=null, $service_id=null) + { + if ($this->request->isPost()) { + return $this->remoteServiceAction('stop', $service, $service_id); + } + return ["status" => "failed"]; + } + + public function startAction($service=null, $service_id=null) + { + if ($this->request->isPost()) { + return $this->remoteServiceAction('start', $service, $service_id); + } + return ["status" => "failed"]; + } + + public function restartAction($service=null, $service_id=null) + { + if ($this->request->isPost()) { + return $this->remoteServiceAction('restart', $service, $service_id); + } + return ["status" => "failed"]; + } + + public function restartAllAction($service=null, $service_id=null) + { + if (true || $this->request->isPost()) { + $backend = new Backend(); + + $services = json_decode((new Backend())->configdRun('system ha exec services'), true); + if (!empty($services['response'])) { + $backend->configdRun('system ha exec exec_sync'); + $backend->configdRun('system ha exec reload_templates'); + foreach ($services['response'] as $service) { + $backend->configdpRun('system ha exec', ['restart', $service['name'], $service['id'] ?? '']); + } + return ["status" => "ok", "count" => count($services['response'])]; + } + + return $this->remoteServiceAction('restart', $service, $service_id); + } + return ["status" => "failed"]; + } +} diff --git a/src/opnsense/scripts/system/ha_xmlrpc_exec.php b/src/opnsense/scripts/system/ha_xmlrpc_exec.php new file mode 100755 index 000000000..a736f020d --- /dev/null +++ b/src/opnsense/scripts/system/ha_xmlrpc_exec.php @@ -0,0 +1,70 @@ +#!/usr/local/bin/php + $service, "id" => $service_id]); + echo json_encode(["response" => $result, 'status' => 'ok']); + break; + case 'start': + $result=xmlrpc_execute('opnsense.start_service', ["service" => $service, "id" => $service_id]); + echo json_encode(["response" => $result, 'status' => 'ok']); + break; + case 'restart': + $result=xmlrpc_execute('opnsense.restart_service', ["service" => $service, "id" => $service_id]); + echo json_encode(["response" => $result, 'status' => 'ok']); + break; + case 'reload_templates': + xmlrpc_execute('opnsense.configd_reload_all_templates'); + echo json_encode(["status" => "done"]); + break; + case 'exec_sync': + configd_run('filter sync'); + echo json_encode(["status" => "done"]); + break; + case 'version': + echo json_encode(["response" => xmlrpc_execute('opnsense.firmware_version')]); + break; + case 'services': + echo json_encode(["response" => xmlrpc_execute('opnsense.list_services')]); + break; + default: + echo json_encode(['status' => 'error', 'message' => 'usage ha_xmlrpc_exec.php action [service_id]']); +} + + + diff --git a/src/opnsense/service/conf/actions.d/actions_system.conf b/src/opnsense/service/conf/actions.d/actions_system.conf index 107d92b00..b5ca401c8 100644 --- a/src/opnsense/service/conf/actions.d/actions_system.conf +++ b/src/opnsense/service/conf/actions.d/actions_system.conf @@ -117,6 +117,19 @@ type:script_output message: Return ha sync options cache_ttl:60 +[ha.exec] +command:/usr/local/opnsense/scripts/system/ha_xmlrpc_exec.php +parameters:%s %s %s +type:script_output +message: execute ha action %s (%s %s) on ha node + +[ha.services_cached] +command:/usr/local/opnsense/scripts/system/ha_xmlrpc_exec.php services +parameters: +type:script_output +message: query remote service list +cache_ttl:5 + [list.nameservers] command:/usr/local/opnsense/scripts/system/nameservers.php parameters:%s