Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Watchdog endpoint #273

Merged
merged 5 commits into from
Sep 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIEndpoint.inc");

class APIServicesServiceWatchdog extends APIEndpoint {
public function __construct() {
$this->url = "/api/v1/services/service_watchdog";
}

protected function get() {
return (new APIServicesServiceWatchdogRead())->call();
}

protected function put() {
return (new APIServicesServiceWatchdogUpdate())->call();
}
}
18 changes: 18 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc
Original file line number Diff line number Diff line change
@@ -2159,6 +2159,24 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "IPsec phase 2 unique ID does not exist"
],
2259 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Service Watchdog service 'name' is required"
],
2260 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Service Watchdog service 'name' is unknown"
],
2261 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Duplicate Service Watchdog services are not allowed"
],

// 3000-3999 reserved for /api/v1/interfaces API calls
3000 => [
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");


class APIServicesServiceWatchdogRead extends APIModel {

public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-services-servicewatchdog"];
$this->packages = ["pfSense-pkg-Service_Watchdog"];
$this->package_includes = ["servicewatchdog.inc"];
}

public function action() {
return APIResponse\get(0, $this->get_watched_services());
}

public static function get_watched_services() {
global $config;
$services = ($config["installedpackages"]["servicewatchdog"]["item"]) ?: [];

# Loop through each configured service and ensure data is formatted correctly
foreach ($services as &$service) {
$service["enabled"] = isset($service["enabled"]);
$service["status"] = isset($service["status"]);
$service["notify"] = isset($service["notify"]);
}

return $services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");


class APIServicesServiceWatchdogUpdate extends APIModel {
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-services-servicewatchdog"];
$this->packages = ["pfSense-pkg-Service_Watchdog"];
$this->package_includes = ["servicewatchdog.inc"];
$this->change_note = "Modified Service Watchdog configuration via API";
}

public function action() {
# Write the updated configuration and reload service watchdog if changes were made
if ($this->validated_data) {
$this->config["installedpackages"]["servicewatchdog"]["item"] = $this->validated_data;
$this->write_config();
servicewatchdog_cron_job();
}

return APIResponse\get(0, APIServicesServiceWatchdogRead::get_watched_services());
}

public function validate_payload() {
$this->__validate_services();
}

private function __validate_services() {
# Validate the optional 'services' field
if (isset($this->initial_data["services"])) {
# Variables
$watched_services = [];

# Revert validated data so it can be repopulated with the updated values
$this->validated_data = [];

# Loop through each requested service and configure it accordingly
foreach ($this->initial_data["services"] as $service) {
# Ensure this item is an array
$service = (is_array($service)) ? $service : [];

# Require the 'name' subfield
if (!isset($service["name"])) {
$this->errors[] = APIResponse\get(2259);
}
# Require this service to be watchable
elseif (!$this->is_service_watchable($service["name"])) {
$this->errors[] = APIResponse\get(2260);
}
# Do not allow duplicates of the same service
elseif (in_array($service["name"], $watched_services)) {
$this->errors[] = APIResponse\get(2261);
}
# Otherwise, this service is valid.
else {
# Mark this service as watched so duplicates will not be allowed
$watched_services[] = $service["name"];

# Get the configuration for this service
$serv_conf = $this->is_service_watchable($service["name"]);

# Allow clients to set the notify field
if ($service["notify"] === true) {
$serv_conf["notify"] = true;
}

# Add this service to our validated configuration
$this->validated_data[] = $serv_conf;
}
}
}
}

public function is_service_watchable($name) {
# Loop through compatible services and check if this service is found
foreach (get_services() as $service) {
if ($service["name"] === $name) {
return $service;
}
}
return false;
}
}
50 changes: 50 additions & 0 deletions pfSense-pkg-API/files/usr/local/www/api/documentation/openapi.yml
Original file line number Diff line number Diff line change
@@ -10816,6 +10816,55 @@ paths:
summary: Restart services
tags:
- Services
/api/v1/services/service_watchdog:
get:
operationId: APIServicesServiceWatchdogRead
description: 'Read the current Service Watchdog configuration. <br><br>

_Requires at least one of the following privileges:_ [`page-all`, `page-system-advanced-admin`]<br>
_Requires all of the following add-on packages:_ [`pfSense-pkg-Service_Watchdog`]'
responses:
"200":
$ref: '#/components/responses/Success'
"401":
$ref: '#/components/responses/AuthenticationFailed'
summary: Read Service Watchdog configuration
tags:
- Services > Service Watchdog
put:
operationId: APIServicesServiceWatchdogUpdate
description: 'Update the Service Watchdog configuration This will replace any existing Service Watchdog
configuration.<br><br>

_Requires at least one of the following privileges:_ [`page-all`, `page-system-advanced-admin`]<br>
_Requires all of the following add-on packages:_ [`pfSense-pkg-Service_Watchdog`]'
requestBody:
content:
application/json:
schema:
properties:
services:
description: Services that Service Watchdog will monitor and automatically restart upon crash.
type: array
items:
type: object
properties:
name:
description: Name of the service that Service Watchdog will monitor.
type: string
notify:
description: Send a notification when Service Watchdog restarts this service.
type: boolean
required:
- name
responses:
"200":
$ref: '#/components/responses/Success'
"401":
$ref: '#/components/responses/AuthenticationFailed'
summary: Update Service Watchdog configuration
tags:
- Services > Service Watchdog
/api/v1/services/sshd:
get:
operationId: APIServicesSSHdRead
@@ -13897,6 +13946,7 @@ tags:
- name: Services > OpenVPN > CSC
- name: Services > OpenVPN > Client
- name: Services > OpenVPN > Server
- name: Services > Service Watchdog
- name: Services > SSHD
- name: Services > SYSLOGD
- name: Services > Unbound
74 changes: 74 additions & 0 deletions tests/test_api_v1_services_service_watchdog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2022 Jared Hendrickson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Class used to test the /api/v1/services/service_watchdog endpoint."""
import e2e_test_framework


class APIE2ETestServicesServiceWatchdog(e2e_test_framework.APIE2ETest):
"""Class used to test the /api/v1/services/service_watchdog endpoint."""
uri = "/api/v1/services/service_watchdog"
put_tests = [
{
"name": "Check pfSense-pkg-Service_Watchdog installed constraint",
"status": 500,
"return": 12
},
{
"name": "Install pfSense-pkg-Service_Watchdog so we can test further",
"method": "POST",
"uri": "/api/v1/system/package",
"resp_time": 30,
"payload": {
"name": "pfSense-pkg-Service_Watchdog"
}
},
{
"name": "Check service 'name' required constraint",
"status": 400,
"return": 2259,
"payload": {"services": [{}]}
},
{
"name": "Check service 'name' options constraint",
"status": 400,
"return": 2260,
"payload": {"services": [{"name": "INVALID"}]}
},
{
"name": "Check service 'name' no duplicates constraint",
"status": 400,
"return": 2261,
"payload": {"services": [{"name": "unbound"}, {"name": "unbound"}]}
},
{
"name": "Update watched services",
"payload": {"services": [{"name": "unbound", "notify": True}]}
},
{
"name": "Read the Service Watchdog configuration",
"method": "GET"
},
{
"name": "Uninstall pfSense-pkg-Service_Watchdog",
"method": "DELETE",
"uri": "/api/v1/system/package",
"resp_time": 30,
"payload": {
"name": "pfSense-pkg-Service_Watchdog"
}
},
]


APIE2ETestServicesServiceWatchdog()