mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
153 lines
3.8 KiB
Python
153 lines
3.8 KiB
Python
"""
|
|
Author: Steffen Vogel <post@steffenvogel.de>
|
|
SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
|
SPDX-License-Identifier: Apache-2.0
|
|
""" # noqa: E501
|
|
|
|
import datetime
|
|
import json
|
|
import logging
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
from tempfile import NamedTemporaryFile
|
|
|
|
import requests
|
|
|
|
LOGGER = logging.getLogger("villas.node")
|
|
|
|
|
|
class Node(object):
|
|
api_version = "v2"
|
|
|
|
def __init__(
|
|
self,
|
|
api_url=None,
|
|
log_filename=None,
|
|
config_filename=None,
|
|
config={},
|
|
executable="villas-node",
|
|
**kwargs,
|
|
):
|
|
self.api_url = api_url
|
|
self.log_filename = log_filename
|
|
self.executable = executable
|
|
|
|
if config_filename and config:
|
|
raise RuntimeError(
|
|
"Can't provide config_filename and " "config at the same time!"
|
|
)
|
|
|
|
if config_filename:
|
|
with open(config_filename) as f:
|
|
self.config = json.load(f)
|
|
else:
|
|
self.config = config
|
|
|
|
# Try to deduct api_url from config
|
|
if self.api_url is None:
|
|
port = config.get("http", {}).get("port")
|
|
if port is None:
|
|
port = 80 if os.getuid() == 0 else 8080
|
|
|
|
self.api_url = f"http://localhost:{port}"
|
|
|
|
def start(self):
|
|
self.config_file = NamedTemporaryFile(mode="w+", suffix=".json")
|
|
|
|
json.dump(self.config, self.config_file)
|
|
|
|
self.config_file.flush()
|
|
|
|
if self.log_filename is None:
|
|
now = datetime.datetime.now()
|
|
fmt = "villas-node_%Y-%m-%d_%H-%M-%S.log"
|
|
self.log_filename = now.strftime(fmt)
|
|
|
|
self.log = open(self.log_filename, "w+")
|
|
|
|
LOGGER.info(
|
|
f"Starting VILLASnode instance with config: {self.config_file.name}" # noqa: E501
|
|
)
|
|
|
|
self.child = subprocess.Popen(
|
|
[self.executable, self.config_file.name],
|
|
stdout=self.log,
|
|
stderr=self.log,
|
|
)
|
|
|
|
def pause(self):
|
|
LOGGER.info("Pausing VILLASnode instance")
|
|
self.child.send_signal(signal.SIGSTOP)
|
|
|
|
def resume(self):
|
|
LOGGER.info("Resuming VILLASnode instance")
|
|
self.child.send_signal(signal.SIGCONT)
|
|
|
|
def stop(self):
|
|
LOGGER.info("Stopping VILLASnode instance")
|
|
self.child.send_signal(signal.SIGTERM)
|
|
self.child.wait()
|
|
|
|
self.log.close()
|
|
|
|
def restart(self):
|
|
LOGGER.info("Restarting VILLASnode instance")
|
|
self.request("restart")
|
|
|
|
@property
|
|
def active_config(self):
|
|
return self.request("config")
|
|
|
|
@property
|
|
def nodes(self):
|
|
return self.request("nodes")
|
|
|
|
@property
|
|
def paths(self):
|
|
return self.request("paths")
|
|
|
|
@property
|
|
def status(self):
|
|
return self.request("status")
|
|
|
|
def load_config(self, i):
|
|
if type(i) is dict:
|
|
cfg = i
|
|
elif type(i) is str:
|
|
cfg = json.loads(i)
|
|
elif hasattr(i, "read"): # file-like?
|
|
cfg = json.load(i)
|
|
else:
|
|
raise TypeError()
|
|
|
|
req = {"config": cfg}
|
|
|
|
self.request("restart", method="POST", json=req)
|
|
|
|
def request(self, action, method="GET", **args):
|
|
if "timeout" not in args:
|
|
args["timeout"] = 1
|
|
|
|
r = requests.request(
|
|
method, f"{self.api_url}/api/{self.api_version}/{action}", **args
|
|
)
|
|
r.raise_for_status()
|
|
|
|
return r.json()
|
|
|
|
def get_local_version(self):
|
|
ver = subprocess.check_output([self.executable, "-V"])
|
|
|
|
return ver.decode("ascii").rstrip()
|
|
|
|
def get_version(self):
|
|
resp = self.request("status")
|
|
|
|
return resp["version"]
|
|
|
|
def is_running(self):
|
|
if self.child is None:
|
|
return False
|
|
else:
|
|
return self.child.poll() is None
|