1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

Merge branch 'fix-api' into 'develop'

Add new API actions for remote management

Closes #116

See merge request !33
This commit is contained in:
Steffen Vogel 2017-08-10 18:19:49 +02:00
commit 10e2175582
11 changed files with 270 additions and 28 deletions

View file

@ -54,6 +54,11 @@ struct api_session {
struct web_buffer body; /**< HTTP body / WS payload */
struct web_buffer headers; /**< HTTP headers */
} response;
struct {
char name[64];
char ip[64];
} peer;
bool completed; /**< Did we receive the complete body yet? */

View file

@ -56,6 +56,8 @@ struct super_node {
} cli;
enum state state;
char *uri; /**< URI of configuration */
config_t cfg; /**< Pointer to configuration file */
json_t *json; /**< JSON representation of the same config. */

View file

@ -55,14 +55,20 @@ int api_ws_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *
ret = api_session_init(s, w->api, API_MODE_WS);
if (ret)
return -1;
list_push(&w->api->sessions, s);
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), s->peer.name, sizeof(s->peer.name), s->peer.ip, sizeof(s->peer.ip));
debug(LOG_API, "New API session initiated: version=%d, mode=websocket", s->version);
debug(LOG_API, "New API session initiated: version=%d, mode=websocket, remote=%s (%s)", s->version, s->peer.name, s->peer.ip);
break;
case LWS_CALLBACK_CLOSED:
ret = api_session_destroy(s);
if (ret)
return -1;
list_remove(&w->api->sessions, s);
debug(LOG_API, "Closed API session");
@ -117,8 +123,12 @@ int api_http_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void
ret = api_session_init(s, w->api, API_MODE_HTTP);
if (ret)
return -1;
list_push(&w->api->sessions, s);
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), s->peer.name, sizeof(s->peer.name), s->peer.ip, sizeof(s->peer.ip));
debug(LOG_API, "New API session initiated: version=%d, mode=http", s->version);
debug(LOG_API, "New API session initiated: version=%d, mode=http, remote=%s (%s)", s->version, s->peer.name, s->peer.ip);
/* Prepare HTTP response header */
const char headers[] = "HTTP/1.1 200 OK\r\n"
@ -143,6 +153,10 @@ int api_http_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void
ret = api_session_destroy(s);
if (ret)
return -1;
if (w->api->sessions.state == STATE_INITIALIZED)
list_remove(&w->api->sessions, s);
break;
case LWS_CALLBACK_HTTP_BODY:
@ -166,7 +180,7 @@ int api_http_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void
web_buffer_write(&s->response.body, wsi);
if (s->completed && s->response.body.len == 0)
return -1;
return -1; /* Close connection */
break;
default:
@ -209,6 +223,11 @@ int api_start(struct api *a)
int api_stop(struct api *a)
{
info("Stopping API sub-system");
for (int i = 0; i < 10 && list_length(&a->sessions) > 0; i++) {
info("Wait for API requests to complete");
usleep(100 * 1e-3);
}
list_destroy(&a->sessions, (dtor_cb_t) api_session_destroy, false);

93
lib/api/actions/restart.c Normal file
View file

@ -0,0 +1,93 @@
/** The "restart" API action.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* 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/>.
*********************************************************************************/
#include "plugin.h"
#include "api.h"
#include "super_node.h"
#include "log.h"
static char *config;
void api_restart_handler()
{
int ret;
char *argv[] = { "villas-node", config, NULL };
ret = execvpe("/proc/self/exe", argv, environ);
if (ret)
serror("Failed to restart");
}
static int api_restart(struct api_action *h, json_t *args, json_t **resp, struct api_session *s)
{
int ret;
json_error_t err;
/* If no config is provided via request, we will use the previous one */
if (s->api->super_node->uri)
config = strdup(s->api->super_node->uri);
if (args) {
ret = json_unpack_ex(args, &err, 0, "{ s?: s }", "config", &config);
if (ret < 0) {
*resp = json_string("failed to parse request");
return -1;
}
}
/* Increment API restart counter */
char *scnt = getenv("VILLAS_API_RESTART_COUNT");
int cnt = scnt ? atoi(scnt) : 0;
char buf[32];
snprintf(buf, sizeof(buf), "%d", cnt + 1);
/* We pass some env variables to the new process */
setenv("VILLAS_API_RESTART_COUNT", buf, 1);
setenv("VILLAS_API_RESTART_REMOTE", s->peer.ip, 1);
*resp = json_pack("{ s: i, s: s, s: s }",
"restarts", cnt,
"config", config,
"remote", s->peer.ip
);
/* Register exit handler */
ret = atexit(api_restart_handler);
if (ret)
return 0;
/* Properly terminate current instance */
killme(SIGTERM);
return 0;
}
static struct plugin p = {
.name = "restart",
.description = "restart VILLASnode with new configuration",
.type = PLUGIN_TYPE_API,
.api.cb = api_restart
};
REGISTER_PLUGIN(&p)

View file

@ -1,4 +1,4 @@
/** The "restart" API ressource.
/** The "shutdown" API action.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
@ -23,17 +23,18 @@
#include "plugin.h"
#include "api.h"
/** @todo not implemented yet */
static int api_restart(struct api_action *h, json_t *args, json_t **resp, struct api_session *s)
static int api_shutdown(struct api_action *h, json_t *args, json_t **resp, struct api_session *s)
{
return -1;
killme(SIGTERM);
return 0;
}
static struct plugin p = {
.name = "restart",
.description = "restart VILLASnode with new configuration",
.name = "shutdown",
.description = "stop VILLASnode",
.type = PLUGIN_TYPE_API,
.api.cb = api_restart
.api.cb = api_shutdown
};
REGISTER_PLUGIN(&p)

View file

@ -64,7 +64,8 @@ int api_session_run_command(struct api_session *s, json_t *json_in, json_t **jso
char *id;
struct plugin *p;
json_t *json_args = NULL, *json_resp;
json_t *json_args = NULL;
json_t *json_resp = NULL;
ret = json_unpack(json_in, "{ s: s, s: s, s?: o }",
"action", &action,
@ -99,10 +100,12 @@ int api_session_run_command(struct api_session *s, json_t *json_in, json_t **jso
"code", ret,
"error", "command failed");
else
*json_out = json_pack("{ s: s, s: s, s: o }",
*json_out = json_pack("{ s: s, s: s }",
"action", action,
"id", id,
"response", json_resp);
"id", id);
if (json_resp)
json_object_set(*json_out, "response", json_resp);
out: debug(LOG_API, "API request completed with code: %d", ret);

View file

@ -134,6 +134,14 @@ int super_node_parse_uri(struct super_node *sn, const char *uri)
config_set_destructor(&sn->cfg, config_dtor);
config_set_auto_convert(&sn->cfg, 1);
cfg_root = config_root_setting(&sn->cfg);
/* Little hack to properly report configuration filename in error messages
* We add the uri as a "hook" object to the root setting.
* See cerror() on how this info is used.
*/
config_setting_set_hook(cfg_root, strdup(uri));
/* Parse config */
ret = config_read(&sn->cfg, f);
if (ret != CONFIG_TRUE) {
@ -143,6 +151,7 @@ int super_node_parse_uri(struct super_node *sn, const char *uri)
json_error_t err;
json_t *json;
rewind(f);
json = json_loadf(f, 0, &err);
if (json) {
ret = json_to_config(json, cfg_root);
@ -150,32 +159,27 @@ int super_node_parse_uri(struct super_node *sn, const char *uri)
error("Failed t convert JSON to configuration file");
}
else {
error("Failed to parse configuration");
{ INDENT
warn("conf: %s in %s:%d", config_error_text(&sn->cfg), uri, config_error_line(&sn->cfg));
warn("json: %s in %s:%d:%d", err.text, err.source, err.line, err.column);
}
error("Failed to parse configuration");
}
#else
error("Failed to parse configuration");
{ INDENT
warn("%s in %s:%d", config_error_text(&sn->cfg), uri, config_error_line(&sn->cfg));
}
error("Failed to parse configuration");
#endif
}
/* Little hack to properly report configuration filename in error messages
* We add the uri as a "hook" object to the root setting.
* See cerror() on how this info is used.
*/
cfg_root = config_root_setting(&sn->cfg);
config_setting_set_hook(cfg_root, strdup(uri));
/* Close configuration file */
if (af)
afclose(af);
else if (f != stdin)
fclose(f);
sn->uri = strdup(uri);
return super_node_parse(sn, cfg_root);
}
@ -429,11 +433,11 @@ int super_node_stop(struct super_node *sn)
}
}
#ifdef WITH_WEB
web_stop(&sn->web);
#endif
#ifdef WITH_API
api_stop(&sn->api);
#endif
#ifdef WITH_WEB
web_stop(&sn->web);
#endif
log_stop(&sn->log);
@ -459,6 +463,9 @@ int super_node_destroy(struct super_node *sn)
log_destroy(&sn->log);
config_destroy(&sn->cfg);
if (sn->uri)
free(sn->uri);
sn->state = STATE_DESTROYED;

View file

@ -302,7 +302,7 @@ int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx))
info("Initialize signals");
struct sigaction sa_quit = {
.sa_flags = SA_SIGINFO,
.sa_flags = SA_SIGINFO | SA_NODEFER,
.sa_sigaction = cb
};

View file

@ -236,7 +236,7 @@ int web_start(struct web *w)
int web_stop(struct web *w)
{
if (w->state == STATE_STARTED)
if (w->state != STATE_STARTED)
return 0;
info("Stopping Web sub-system");

View file

@ -0,0 +1,73 @@
#!/bin/bash
#
# Integration test for remote API
#
# @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
# @license GNU General Public License (version 3)
#
# VILLASnode
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# 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/>.
##################################################################################
set -e
LOCAL_CONF=$(mktemp)
FETCHED_CONF=$(mktemp)
cat <<EOF > ${LOCAL_CONF}
{
"nodes" : {
"node1" : {
"type" : "socket",
"local" : "*:12000",
"remote" : "127.0.0.1:12001"
}
},
"paths" : [
{
"in" : "node1", "out" : "node1",
"hooks" : [ { "type" : "print" } ]
}
]
}
EOF
# Start without a configuration
villas-node &
# Wait for node to complete init
sleep 0.2
# Restart with configuration
curl -sX POST --data '{ "action" : "restart", "request" : { "config": "'${LOCAL_CONF}'" }, "id" : "5a786626-fbc6-4c04-98c2-48027e68c2fa" }' http://localhost/api/v1
# Wait for node to complete init
sleep 0.2
# Fetch config via API
curl -sX POST --data '{ "action" : "config", "id" : "5a786626-fbc6-4c04-98c2-48027e68c2fa" }' http://localhost/api/v1 > ${FETCHED_CONF}
# Shutdown VILLASnode
kill %%
# Compare local config with the fetched one
diff -u <(jq -S .response < ${FETCHED_CONF}) <(jq -S . < ${LOCAL_CONF})
RC=$?
rm -f ${LOCAL_CONF} ${FETCHED_CONF}
exit $RC

View file

@ -0,0 +1,39 @@
#!/bin/bash
#
# Integration test for remote API
#
# @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
# @license GNU General Public License (version 3)
#
# VILLASnode
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# 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/>.
##################################################################################
set -e
# Start without a configuration
timeout -s SIGKILL 3 villas-node &
# Wait for node to complete init
sleep 1
# Restart with configuration
curl -sX POST --data '{ "action" : "shutdown", "id" : "5a786626-fbc6-4c04-98c2-48027e68c2fa" }' http://localhost/api/v1
# Wait returns the return code of villas-node
# which will be 0 (success) in case of normal shutdown
# or <>0 (fail) in case the 3 second timeout was reached
wait $!