Commit 72a58295 authored by Jérome Perrin's avatar Jérome Perrin

Update Release Candidate

parents b11feabe 052c39fa
......@@ -5,6 +5,7 @@ extends =
../git/buildout.cfg
../jsoncpp/buildout.cfg
../tinyxml2/buildout.cfg
../zlib/buildout.cfg
parts =
mavsdk
......@@ -41,7 +42,7 @@ configure-options =
-DCMAKE_C_FLAGS="${:CMAKE_CFLAGS}"
-DCMAKE_CXX_FLAGS="${:CMAKE_CFLAGS}"
-DCMAKE_INSTALL_PREFIX=@@LOCATION@@
-DCMAKE_INSTALL_RPATH=${:CMAKE_LIBRARY_PATH}
-DCMAKE_INSTALL_RPATH=${:CMAKE_LIBRARY_PATH}:@@LOCATION@@/lib
-DPKG_CONFIG_EXECUTABLE=${pkgconfig:location}/bin/pkg-config
-DSUPERBUILD=OFF
-Bbuild/default
......@@ -53,7 +54,7 @@ environment =
CMAKE_LIBRARY_PATH=${:CMAKE_LIBRARY_PATH}
CMAKE_PROGRAM_PATH=${cmake:location}/bin
PATH=${pkgconfig:location}/bin/:%(PATH)s
LDFLAGS=-L${jsoncpp:location}/lib -Wl,-rpath=${jsoncpp:location}/lib
LDFLAGS=-L${curl:location}/lib -Wl,-rpath=${curl:location}/lib -L${jsoncpp:location}/lib -Wl,-rpath=${jsoncpp:location}/lib -L${tinyxml2:location}/lib -Wl,-rpath=${tinyxml2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -Wl,-rpath=@@LOCATION@@/lib
CMAKE_CFLAGS=-I${tinyxml2:location}/include
CMAKE_LIBRARY_PATH=${curl:location}/lib:${jsoncpp:location}/lib:${tinyxml2:location}/lib
CMAKE_LIBRARY_PATH=${curl:location}/lib:${jsoncpp:location}/lib:${tinyxml2:location}/lib:${zlib:location}/lib
......@@ -3,8 +3,8 @@ parts = mbedtls
[mbedtls]
recipe = slapos.recipe.cmmi
url = https://tls.mbed.org/download/mbedtls-2.16.0-gpl.tgz
md5sum = 899b8385c0b47fb3e0e52baca16c93c8
url = https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/v2.28.2.tar.gz
md5sum = 421c47c18ef46095e3ad38ffc0543e11
shared = true
configure-command = echo
make-targets = install DESTDIR=@@LOCATION@@
......@@ -8,10 +8,9 @@ parts = qjs-wrapper
[qjs-wrapper]
recipe = slapos.recipe.cmmi
shared = true
configure-command = true
url = https://lab.nexedi.com/nexedi/qjs-wrapper/-/archive/v1.0/qjs-wrapper-v1.0.tar.gz
md5sum = 0f1393fa15d46b2b551836197af9de46
url = https://lab.nexedi.com/nexedi/qjs-wrapper/-/archive/v1.2/qjs-wrapper-v1.2.tar.gz
md5sum = e335fc2251610c09dde3e74787317d98
environment =
C_INCLUDE_PATH=include:${open62541:location}/include:${open62541:location}/deps:${open62541:location}/src/pubsub:${quickjs:location}/include
CPLUS_INCLUDE_PATH=include:${mavsdk:location}/include:${mavsdk:location}/include/mavsdk
......
......@@ -28,7 +28,7 @@ from setuptools import setup, find_packages
import glob
import os
version = '1.0.297'
version = '1.0.305'
name = 'slapos.cookbook'
long_description = open("README.rst").read()
......
......@@ -5,6 +5,7 @@ import sys
import os
import signal
import subprocess
import time
from collections import defaultdict
from inotify_simple import INotify, flags
......@@ -14,24 +15,34 @@ def _wait_files_creation(file_list):
# Establish a list of directory and subfiles.
# and test existence before watching, so that we don't miss an event.
directories = defaultdict(dict)
for f in file_list:
dirname, filename = os.path.split(f)
directories[dirname][filename] = os.path.lexists(f)
def check_if_files_exists():
for f in file_list:
dirname, filename = os.path.split(f)
directories[dirname][filename] = os.path.lexists(f)
check_if_files_exists()
def all_files_exists():
return all(all(six.itervalues(files)) for files in six.itervalues(directories))
with INotify() as inotify:
watchdescriptors = {inotify.add_watch(dirname,
flags.CREATE | flags.DELETE | flags.MOVED_TO | flags.MOVED_FROM
): dirname
for dirname in directories}
while not all_files_exists():
for event in inotify.read():
directory = directories[watchdescriptors[event.wd]]
if event.name in directory:
directory[event.name] = event.mask & (flags.CREATE | flags.MOVED_TO)
try:
watchdescriptors = {inotify.add_watch(dirname,
flags.CREATE | flags.DELETE | flags.MOVED_TO | flags.MOVED_FROM
): dirname
for dirname in directories}
except OSError as e:
if e.errno not in (errno.ENOSPC, errno.EMFILE):
raise
print('Error using inotify, falling back to polling')
while not all_files_exists():
time.sleep(0.1)
check_if_files_exists()
else:
while not all_files_exists():
for event in inotify.read():
directory = directories[watchdescriptors[event.wd]]
if event.name in directory:
directory[event.name] = event.mask & (flags.CREATE | flags.MOVED_TO)
def _libc():
from ctypes import CDLL, get_errno, c_char_p, c_int, c_ulong, util
......
......@@ -175,6 +175,12 @@ class TestPidFile(WrapperTestCase):
class TestWaitForFiles(WrapperTestCase):
env = None
if sys.platform.startswith("linux"):
expected_output = 'done\n'
else:
expected_output = 'Error using inotify, falling back to polling\ndone\n'
def getOptions(self):
self.waitfile = self.getTempPath('wait')
return {
......@@ -192,6 +198,7 @@ class TestWaitForFiles(WrapperTestCase):
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
env=self.env,
)
self.addCleanup(self.terminate_process, process)
if process.poll():
......@@ -207,13 +214,57 @@ class TestWaitForFiles(WrapperTestCase):
for _ in range(20):
time.sleep(0.1)
if process.poll() is not None:
self.assertEqual(process.stdout.read(), 'done\n')
self.assertEqual(process.stdout.read(), self.expected_output)
self.assertEqual(process.returncode, 0)
break
else:
self.fail('process did not start after file was created')
@unittest.skipUnless(sys.platform.startswith("linux"), "Inotify is linux only")
class TestWaitForFilesInotifyError(TestWaitForFiles):
def setUp(self):
super(TestWaitForFilesInotifyError, self).setUp()
# use LD_PRELOAD to inject errors into inotify_add_watch calls
inotify_mock_c = self.getTempPath('inotify_mock.c')
inotify_mock_o = self.getTempPath('inotify_mock.o')
inotify_mock_so = self.getTempPath('inotify_mock.so')
with open(inotify_mock_c, 'w') as f:
f.write('''
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>
int inotify_add_watch(int fd, const char *pathname, uint32_t mask) {
errno = ENOSPC;
return -1;
}
/* This is a bit tricky because inotify_simple calls
inotify_add_watch with ctypes.CDLL("libc.so"), which uses
dlopen("libc.so") and dlsym("inotify_add_watch"), so we first
override dlsym to return our own inotify_add_watch.
https://github.com/chrisjbillington/inotify_simple/blob/55737898/inotify_simple.py#L110
*/
extern void *__libc_dlsym (void *, const char *);
void *dlsym(void *handle, const char *symbol) {
if (strcmp(symbol, "inotify_add_watch") == 0) {
return (void *)inotify_add_watch;
}
return (void *)__libc_dlsym(handle, symbol);
}
''')
subprocess.check_call(['gcc', '-c', '-fPIC', '-o', inotify_mock_o, inotify_mock_c])
subprocess.check_call(['gcc', '-shared', '-o', inotify_mock_so, inotify_mock_o])
self.env = dict(
os.environ,
PYTHONUNBUFFERED='1',
LD_PRELOAD=inotify_mock_so)
expected_output = 'Error using inotify, falling back to polling\ndone\n'
class TestPrivateTmpFS(WrapperTestCase):
def getOptions(self):
self.tmpdir = self.getTempPath('tmpdir')
......
......@@ -14,7 +14,7 @@
# not need these here).
[instance-profile]
filename = instance.cfg
md5sum = 41f5acc071609a0c4b5ada295ede6bac
md5sum = 82c476f22e6b55b674640abef959c3c2
[template-fluentd]
filename = instance-fluentd.cfg
......
......@@ -42,7 +42,7 @@ recipe = slapos.recipe.build
slapparameter-dict = $${slap-configuration:configuration}
init =
import re
options['text'] = options['slapparameter-dict'].get('conf_text') or ''
options['text'] = options['slapparameter-dict'].get('conf_text') or ' '
options['port-list'] = re.findall(r'<source>.*port (\d+).*<\/source>', options['text'], re.DOTALL)
[fluentd-agent-conf]
......
......@@ -2,26 +2,29 @@
## Presentation ##
* Deploy `user.js` script on a drone to fly it
* Deploy `user.js` flight script on a drone swarm
* Compile all required libraries to run flight scripts
* Compile all required libraries to run the flight script
## Parameters ##
* autopilot-ip: IPv4 address to identify the autpilot from the companion board
* autopilot-ip: IPv4 address to identify the autopilot from the companion board
* id: User chosen ID for the drone (must be unique in a swarm, will be used as an identifier in multicast communications)
* drone-guid-list: List of computer id on which flight script must be deployed
* is-a-simulation: Must be set to 'true' to automatically take off during simulation
* multicast-ipv6: IPv6 of the multicast group of the swarm
* multicast-ip: IPv6 of the multicast group of the swarm
* net-if: Network interface used for multicast traffic
* drone-id-list: List of the drone IDs of the swarm (recommended to add the current drone ID)
* flight-script: URL of user's script to execute to fly drone swarm
* flight-script: User script to execute to fly drone swarm
* subscriber-guid-list: List of computer id on which subscription script must be deployed
## How it works ##
Run `quickjs binary location` `scripts location`/main.js `scripts location`/user.js
For each computer listed in `drone-guid-list` and `subscriber-guid-list` a drone SR will be instanciated.
Each instance will return a `instance-path`. Under this path one will find `quickjs binary` in `bin` folder
and `scripts` in `etc` folder.
Run `quickjs binary location` `scripts location`/main.js `scripts location`/user.js .
......@@ -14,16 +14,24 @@
# not need these here).
[instance-profile]
filename = instance.cfg
md5sum = 7d4969239eb9d46bb44d57fc32b68c44
md5sum = 360b58007c25727b7bd8a9154d5cafd4
[instance-default]
filename = instance-default.cfg
md5sum = b26633b118cddd7c7b8dfd61b360999c
[instance-drone]
filename = instance-drone.cfg
md5sum = 1ff50063f5a54712a0bc0ff38fa74630
[main]
filename = main.js
md5sum = 4b1b27ea3e06b8d40cbc33f0ec617601
md5sum = 2118d7908a909c585e03531147b1d540
[pubsub]
filename = pubsub.js
md5sum = 4a0c63f9e088fa525a3699484d193c4d
md5sum = 1555496ad591a31a845f33488d5c335d
[worker]
filename = worker.js
md5sum = 5ed534e9ca56b9c0ee321b96b5d7c458
md5sum = 3893fb9228603e8c74f803476ec64dec
{% set autopilot_ip = slapparameter_dict.get('autopilotIp', '192.168.27.1') -%}
{% set flight_script = slapparameter_dict.get('flightScript', 'https://lab.nexedi.com/nexedi/flight-scripts/raw/master/default.js') -%}
{% set is_a_simulation = slapparameter_dict.get('isASimulation', False) -%}
{% set multicast_ip = slapparameter_dict.get('multicastIp', 'ff15::1111') -%}
{% set net_if = slapparameter_dict.get('netIf', 'eth0') -%}
{% set drone_guid_list = slapparameter_dict.get('droneGuidList', []) -%}
{% set subscriber_guid_list = slapparameter_dict.get('subscriberGuidList', []) -%}
{% set guid_list = drone_guid_list + subscriber_guid_list -%}
{% set nb_peer = len(guid_list) -%}
{% set drone_id_list = [] -%}
{% set subscriber_id_list = [] -%}
{% set part_list = ['publish-connection-information'] -%}
{% for id, guid in enumerate(guid_list) -%}
{% set request_drone_section_title = 'request-drone' ~ id -%}
{% do part_list.append(request_drone_section_title) %}
[{{ request_drone_section_title }}]
<= slap-connection
recipe = slapos.cookbook:request.serialised
name = Drone{{ id }}
software-url = $${:software-release-url}
software-type = drone
return = instance-path
sla-computer_guid = {{ guid }}
config-autopilotIp = {{ autopilot_ip }}
config-numberOfPeers = {{ dumps(nb_peer) }}
config-id = {{ dumps(id) }}
config-isASimulation = {{ dumps(is_a_simulation) }}
{% if guid in drone_guid_list -%}
{% do drone_id_list.append(id) %}
config-isADrone = {{ dumps(True) }}
config-flightScript = {{ flight_script }}
{% else -%}
{% do subscriber_id_list.append(id) %}
config-isADrone = {{ dumps(False) }}
config-flightScript = https://lab.nexedi.com/nexedi/flight-scripts/raw/master/subscribe.js
{% endif -%}
config-multicastIp = {{ multicast_ip }}
config-netIf = {{ net_if }}
{% endfor %}
[publish-connection-information]
recipe = slapos.cookbook:publish.serialised
drone-id-list = {{ dumps(drone_id_list) }}
subscriber-id-list = {{ dumps(subscriber_id_list) }}
[buildout]
parts =
{%- for part in part_list %}
{{ part }}
{%- endfor -%}
{
"$schema": "http://json-schema.org/draft-06/schema",
"type": "object",
"description": "Parameters to instantiate JS drone",
"additionalProperties": false,
"properties": {
"autopilotIp": {
"title": "IP address of the drone's autopilot",
"description": "IP used to create a connection with the autopilot.",
"type": "string"
},
"numberOfPeers": {
"title": "Number of Peers",
"description": "Number of drones and subscribers in the swarm",
"type": "integer"
},
"id": {
"title": "drone ID",
"description": "Drone unique identifier",
"type": "integer"
},
"isADrone": {
"title": "Set the requested instance as a drone",
"description": "The option used to determine if the instance is a drone. This affects the context of the user script (e.g. if it should be linked to an autopilot or publish its GPS coordinates)",
"type": "boolean"
},
"isASimulation": {
"title": "Set the flight as a simulation",
"description": "The option used to determine if the flight is real or if it is a simulation. This affects the context of the flight (e.g. if the take off is manual or automatic).",
"type": "boolean"
},
"multicastIp": {
"title": "IP of the multicast group",
"description": "IP address used to communicate with the other drones.",
"type": "string"
},
"netIf": {
"title": "Network interface",
"description": "Interface used for multicast traffic.",
"type": "string"
},
"flightScript": {
"title": "Script's URL of the flight",
"description": "URL of the script which will be executed for the flight. This URL must be publicly accesible so that all drones can fetch the script.",
"type": "string"
}
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Values returned by drone instantiation",
"additionalProperties": false,
"properties": {
"instance-path": {
"description": "Path of the directory where the quickjs binary and the flight scripts are located",
"type": "string"
}
},
"type": "object"
}
[buildout]
parts =
main
symlink-quickjs-binary
publish-connection-information
[directory]
recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory}
bin = $${:home}/bin
etc = $${:home}/etc
var = $${:home}/var
log = $${:var}/log
[js-dynamic-template]
recipe = slapos.recipe.template:jinja2
rendered = $${directory:etc}/$${:_buildout_section_name_}.js
template = ${buildout:directory}/$${:_buildout_section_name_}.js
extra-context =
context =
import json_module json
raw qjs_wrapper ${qjs-wrapper:location}/lib/libqjswrapper.so
raw configuration {{ configuration }}
$${:extra-context}
[main]
<= js-dynamic-template
extra-context =
key log_dir directory:log
key pubsub_script pubsub:rendered
key worker_script worker:rendered
[pubsub]
<= js-dynamic-template
[worker]
<= js-dynamic-template
[symlink-quickjs-binary]
recipe = slapos.recipe.build
binary-path = ${quickjs:location}/bin/qjs
target = $${directory:bin}/qjs
init =
import os
if not os.path.exists(options['target']):
os.symlink(options['binary-path'], options['target'])
[publish-connection-information]
recipe = slapos.cookbook:publish.serialised
instance-path = $${directory:home}
......@@ -4,47 +4,47 @@
"description": "Parameters to instantiate JS drone",
"additionalProperties": false,
"properties": {
"autopilot-ip": {
"autopilotIp": {
"title": "IP address of the drone's autopilot",
"description": "IP used to create a connection with the autopilot.",
"type": "string",
"default": "192.168.27.1"
},
"id": {
"title": "Drone ID",
"description": "Unique identifier of the drone.",
"type": "integer",
"default": 1
"droneGuidList": {
"title": "List of drones computer ID",
"description": "List of computer ID of drones in the swarm",
"type": "array",
"default": []
},
"is-a-simulation": {
"isASimulation": {
"title": "Set the flight as a simulation",
"description": "The option used to determine if the flight is real or if it is a simulation. This affects the context of the flight (e.g. if the take off is manual or automatic).",
"type": "boolean",
"default": false
},
"multicast-ipv6": {
"multicastIpv6": {
"title": "IP of the multicast group",
"description": "IP address used to communicate with the other drones.",
"type": "string",
"default": "ff15::1111"
},
"net-if": {
"netIf": {
"title": "Network interface",
"description": "Interface used for multicast traffic.",
"type": "string",
"default": "eth0"
},
"drone-id-list": {
"title": "List of drones IDs",
"description": "List of identifiers of drones.",
"flightScript": {
"title": "Script's URL of the flight",
"description": "URL of the script which will be executed for the flight. This URL must be publicly accesible so that the drone can fetch the script.",
"type": "string",
"default": "https://lab.nexedi.com/nexedi/flight-scripts/raw/master/default.js"
},
"subscriberGuidList": {
"title": "List of subscribers computer ID",
"description": "List of computer ID of swarms subscribers",
"type": "array",
"default": []
},
"flight-script": {
"title": "Script of the flight",
"description": "Script which will be executed for the flight",
"type": "string",
"textarea": true
}
}
}
......@@ -2,6 +2,15 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Values returned by drone swarm (default) instantiation",
"additionalProperties": false,
"properties": {},
"properties": {
"drone-id-list": {
"description": "List of drones IDs",
"type": "array"
},
"subscriber-id-list": {
"description": "List of subscribers IDs",
"type": "array"
}
},
"type": "object"
}
[buildout]
parts =
main
user
switch-softwaretype
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
offline = true
[directory]
recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory}
etc = $${:home}/etc
var = $${:home}/var
log = $${:var}/log
[switch-softwaretype]
recipe = slapos.cookbook:switch-softwaretype
default = instance-default:output
drone = instance-drone:output
RootSoftwareInstance = $${:default}
[slap-configuration]
recipe = slapos.cookbook:slapconfiguration
recipe = slapos.cookbook:slapconfiguration.serialised
computer = $${slap_connection:computer_id}
partition = $${slap_connection:partition_id}
url = $${slap_connection:server_url}
key = $${slap_connection:key_file}
cert = $${slap_connection:cert_file}
[drone]
recipe = slapos.recipe.build
slapparameter-dict = $${slap-configuration:configuration}
init =
options['autopilot-ip'] = options['slapparameter-dict'].get('autopilot-ip', '192.168.27.1')
options['id'] = options['slapparameter-dict'].get('id', 1)
options['is-a-simulation'] = options['slapparameter-dict'].get('is-a-simulation', False)
options['multicast-ipv6'] = options['slapparameter-dict'].get('multicast-ip', 'ff15::1111')
options['net-if'] = options['slapparameter-dict'].get('net-if', 'eth0')
options['drone-id-list'] = options['slapparameter-dict'].get('drone-id-list', [])
options['is-a-drone'] = 'flight-script' in options['slapparameter-dict']
subscription_script = '''
me.onStart = function() {
me.f = me.fdopen(me.in, "r");
console.log("Use q to quit");
};
[dynamic-template-base]
recipe = slapos.recipe.template:jinja2
url = ${buildout:directory}/$${:_buildout_section_name_}.cfg
output = $${buildout:directory}/$${:_buildout_section_name_}
me.onUpdate= function(timestamp) {
while(me.f.getline() != "q") {
continue;
}
try {
me.f.close();;
} catch (error) {
console.error(error);
}
me.exit(0);
};
'''
[instance-default]
<= dynamic-template-base
extensions = jinja2.ext.do
context =
key slapparameter_dict slap-configuration:configuration
options['flight-script'] = options['slapparameter-dict'].get('flight-script', subscription_script)
[instance-drone]
<= dynamic-template-base
context =
key configuration drone-configuration:output
key user-script user:destination
[js-dynamic-template]
[drone-configuration]
recipe = slapos.recipe.template:jinja2
rendered = $${directory:etc}/$${:_buildout_section_name_}.js
template = ${buildout:directory}/$${:_buildout_section_name_}.js
extra-context =
output = $${directory:etc}/configuration.json
context =
raw qjs_wrapper ${qjs-wrapper:location}/lib/libqjswrapper.so
$${:extra-context}
[main]
<= js-dynamic-template
extra-context =
key autopilot_ip drone:autopilot-ip
key id drone:id
key is_a_simulation drone:is-a-simulation
key is_a_drone drone:is-a-drone
key log_dir directory:log
key pubsub_script pubsub:rendered
key worker_script worker:rendered
[pubsub]
<= js-dynamic-template
extra-context =
key ipv6 drone:multicast-ipv6
key net_if drone:net-if
import json_module json
key slapparameter_dict slap-configuration:configuration
inline = {{ json_module.dumps(slapparameter_dict) }}
[user]
recipe = slapos.recipe.template:jinja2
output = $${directory:etc}/user.js
context =
key script drone:flight-script
inline = {{ script }}
recipe = slapos.recipe.build:download
url = $${slap-configuration:configuration.flightScript}
destination = $${directory:etc}/user.js
offline = false
[worker]
<= js-dynamic-template
extra-context =
key drone_id_list drone:drone-id-list
key id drone:id
key is_a_drone drone:is-a-drone
[directory]
recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory}
etc = $${:home}/etc
/* global console */
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global arm, console, exit, open, scriptArgs, setTimeout, start, stop,
stopPubsub, takeOffAndWait, Worker*/
import {
arm,
start,
stop,
stopPubsub,
takeOffAndWait
} from "{{ qjs_wrapper }}";
} from {{ json_module.dumps(qjs_wrapper) }};
import { setTimeout, Worker } from "os";
import { exit } from "std";
import { open, exit } from "std";
(function (console, setTimeout, Worker) {
(function (arm, console, exit, open, scriptArgs, setTimeout, start, stop,
stopPubsub, takeOffAndWait, Worker) {
"use strict";
const IP = "{{ autopilot_ip }}",
URL = "udp://" + IP + ":7909",
var CONF_PATH = {{ json_module.dumps(configuration) }},
conf_file = open(CONF_PATH, "r"),
configuration = JSON.parse(conf_file.readAsString()),
URL = "udp://" + configuration.autopilotIp + ":7909",
LOG_FILE = "{{ log_dir }}/mavsdk-log",
IS_A_DRONE = {{ 'true' if is_a_drone else 'false' }},
SIMULATION = {{ 'true' if is_a_simulation else 'false' }};
pubsubWorker,
worker,
user_script = scriptArgs[1],
FPS = 50, // Minimum sampling interval for open62541 monitored items
previous_timestamp,
can_update = false;
conf_file.close();
// Use a Worker to ensure the user script
// does not block the main script
......@@ -23,17 +35,14 @@ import { exit } from "std";
// Create the update loop in the main script
// to prevent it to finish (and so, exit the quickjs process)
var pubsubWorker,
worker = new Worker("{{ worker_script }}"),
user_script = scriptArgs[1],
// Use the same FPS than browser's requestAnimationFrame
FPS = 1000 / 60,
previous_timestamp,
can_update = false;
worker = new Worker("{{ worker_script }}");
function connect() {
console.log("Will connect to", URL);
exitOnFail(start(URL, LOG_FILE, 60), "Failed to connect to " + URL);
function quit(is_a_drone, exit_code) {
stopPubsub();
if (is_a_drone) {
stop();
}
exit(exit_code);
}
function exitOnFail(ret, msg) {
......@@ -43,25 +52,22 @@ import { exit } from "std";
}
}
function quit(is_a_drone, exit_code) {
if (is_a_drone) {
stop();
}
stopPubsub();
exit(exit_code);
function connect() {
console.log("Will connect to", URL);
exitOnFail(start(URL, LOG_FILE, 60), "Failed to connect to " + URL);
}
if (IS_A_DRONE) {
if (configuration.isADrone) {
console.log("Connecting to aupilot\n");
connect();
}
pubsubWorker = new Worker("{{ pubsub_script }}");
pubsubWorker.onmessage = function(e) {
pubsubWorker.onmessage = function (e) {
if (!e.data.publishing) {
pubsubWorker.onmessage = null;
}
}
};
worker.postMessage({type: "initPubsub"});
......@@ -71,7 +77,7 @@ import { exit } from "std";
}
function load() {
if (IS_A_DRONE && SIMULATION) {
if (configuration.isADrone && configuration.isASimulation) {
takeOff();
}
......@@ -88,7 +94,7 @@ import { exit } from "std";
}
function loop() {
let timestamp = Date.now(),
var timestamp = Date.now(),
timeout;
if (can_update) {
if (FPS <= (timestamp - previous_timestamp)) {
......@@ -114,13 +120,13 @@ import { exit } from "std";
}
worker.onmessage = function (e) {
let type = e.data.type;
var type = e.data.type;
if (type === 'initialized') {
pubsubWorker.postMessage({
action: "run",
id: {{ id }},
id: configuration.id,
interval: FPS,
publish: IS_A_DRONE
publish: configuration.isADrone
});
load();
} else if (type === 'loaded') {
......@@ -132,10 +138,11 @@ import { exit } from "std";
can_update = true;
} else if (type === 'exited') {
worker.onmessage = null;
quit(IS_A_DRONE, e.data.exit);
quit(configuration.isADrone, e.data.exit);
} else {
console.log('Unsupported message type', type);
quit(IS_A_DRONE, 1);
quit(configuration.isADrone, 1);
}
};
}(console, setTimeout, Worker));
}(arm, console, exit, open, scriptArgs, setTimeout, start, stop, stopPubsub,
takeOffAndWait, Worker));
import {runPubsub} from "{{ qjs_wrapper }}";
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global console, open, runPubsub, Worker*/
import {runPubsub} from {{ json_module.dumps(qjs_wrapper) }};
import {Worker} from "os";
import {open} from "std";
const PORT = "4840";
const IPV6 = "{{ ipv6 }}";
(function (console, open, runPubsub, Worker) {
"use strict";
let parent = Worker.parent;
var CONF_PATH = {{ json_module.dumps(configuration) }},
PORT = "4840",
parent = Worker.parent,
conf_file = open(CONF_PATH, "r"),
configuration = JSON.parse(conf_file.readAsString());
conf_file.close();
function handle_msg(e) {
switch(e.data.action) {
function handle_msg(e) {
switch (e.data.action) {
case "run":
runPubsub(IPV6, PORT, "{{ net_if }}", e.data.id, e.data.interval, e.data.publish);
runPubsub(
configuration.multicastIp,
PORT,
configuration.netIf,
e.data.id,
e.data.interval,
e.data.publish
);
parent.postMessage({running: false});
parent.onmessage = null;
break;
default:
console.log("Undefined action from parent: ", e.data.action);
}
}
}
parent.onmessage = handle_msg;
parent.onmessage = handle_msg;
}(console, open, runPubsub, Worker));
\ No newline at end of file
......@@ -6,21 +6,34 @@ extends =
parts =
instance-profile
instance-default
instance-drone
main
pubsub
worker
slapos-cookbook
[download-file-base]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:filename}
destination = ${buildout:directory}/${:filename}
[instance-profile]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/template.cfg
[jinja-template-base]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:_buildout_section_name_}.cfg
output = ${buildout:directory}/${:_buildout_section_name_}.cfg
[instance-default]
<= jinja-template-base
[instance-drone]
<= jinja-template-base
[download-file-base]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:filename}
destination = ${buildout:directory}/${:filename}
[main]
<= download-file-base
......
{
"name": "JS Drone",
"description": "JS Drone",
"serialisation": "xml",
"serialisation": "json-in-xml",
"software-type": {
"default": {
"title": "Default",
"software-type": "default",
"description": "Default",
"description": "Drone Swarm",
"request": "instance-input-schema.json",
"response": "instance-output-schema.json",
"index": 0
},
"drone": {
"title": "Drone",
"software-type": "drone",
"description": "Drone Instance",
"request": "instance-drone-input-schema.json",
"response": "instance-drone-output-schema.json",
"index": 1
}
}
}
Tests for js-drone Software Release
##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 (at your option) 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from setuptools import setup, find_packages
version = '0.0.1.dev0'
name = 'slapos.test.js_drone'
with open("README.md") as f:
long_description = f.read()
setup(name=name,
version=version,
description="Test for SlapOS' js-drone",
long_description=long_description,
long_description_content_type='text/markdown',
maintainer="Nexedi",
maintainer_email="info@nexedi.com",
url="https://lab.nexedi.com/nexedi/slapos",
packages=find_packages(),
install_requires=[
'slapos.core',
'slapos.libnetworkcache',
'erp5.util'
],
zip_safe=True,
test_suite='test',
)
This diff is collapsed.
/* global console, std */
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global console, getAltitude, getAltitudeRel, getInitialAltitude, getLatitude,
getLongitude, getYaw, execUserScript, initPubsub, isInManualMode, landed,
loiter, setAirspeed, setAltitude, setManualControlInput, setMessage,
setTargetCoordinates, std, triggerParachute, Drone, Worker*/
import {
Drone,
triggerParachute,
getAirspeed,
getAltitude,
getAltitudeRel,
getClimbRate,
getInitialAltitude,
getLatitude,
getLongitude,
......@@ -17,18 +23,22 @@ import {
setManualControlInput,
setMessage,
setTargetCoordinates
} from "{{ qjs_wrapper }}";
} from {{ json_module.dumps(qjs_wrapper) }};
import * as std from "std";
import { Worker } from "os";
(function (console, Worker) {
(function (console, getAltitude, getAltitudeRel, getInitialAltitude,
getLatitude, getLongitude, getYaw, initPubsub, isInManualMode,
landed, loiter, setAirspeed, setAltitude, setManualControlInput,
setMessage, setTargetCoordinates, std, triggerParachute, Drone,
Worker) {
// Every script is evaluated per drone
"use strict";
const drone_dict = {},
drone_id_list = [{{ drone_id_list }}],
IS_A_DRONE = {{ 'true' if is_a_drone else 'false' }};
let parent = Worker.parent,
var CONF_PATH = {{ json_module.dumps(configuration) }},
conf_file = std.open(CONF_PATH, "r"),
configuration = JSON.parse(conf_file.readAsString()),
parent = Worker.parent,
user_me = {
//for debugging purpose
fdopen: std.fdopen,
......@@ -36,12 +46,12 @@ import { Worker } from "os";
//required to fly
triggerParachute: triggerParachute,
drone_dict: {},
exit: function(exit_code) {
exit: function (exit_code) {
parent.postMessage({type: "exited", exit: exit_code});
parent.onmessage = null;
},
getAltitudeAbs: getAltitude,
getCurrentPosition: function() {
getCurrentPosition: function () {
return {
x: getLatitude(),
y: getLongitude(),
......@@ -50,28 +60,30 @@ import { Worker } from "os";
},
getInitialAltitude: getInitialAltitude,
getYaw: getYaw,
id: {{ id }},
getSpeed: getAirspeed,
getClimbRate: getClimbRate,
id: configuration.id,
landed: landed,
loiter: loiter,
sendMsg: function(msg, id = -1) {
sendMsg: function (msg, id) {
if (id === undefined) { id = -1; }
setMessage(JSON.stringify({ content: msg, dest_id: id }));
},
setAirspeed: setAirspeed,
setAltitude: setAltitude,
setTargetCoordinates: setTargetCoordinates
};
conf_file.close();
function loadUserScript(path) {
let script_content = std.loadFile(path);
var script_content = std.loadFile(path);
if (script_content === null) {
console.log("Failed to load user script " + path);
std.exit(1);
}
try {
std.evalScript(
"function execUserScript(from, me) {" +
script_content +
"};"
"function execUserScript(from, me) {" + script_content + "};"
);
} catch (e) {
console.log("Failed to evaluate user script", e);
......@@ -86,38 +98,32 @@ import { Worker } from "os";
}
function handleMainMessage(evt) {
let type = evt.data.type,
message,
drone_id;
var type = evt.data.type, message, drone_id;
if (type === "initPubsub") {
initPubsub(drone_id_list.length);
for (let i = 0; i < drone_id_list.length; i++) {
drone_id = drone_id_list[i];
initPubsub(configuration.numberOfPeers);
for (drone_id = 0; drone_id < configuration.numberOfPeers; drone_id++) {
user_me.drone_dict[drone_id] = new Drone(drone_id);
user_me.drone_dict[drone_id].init(i);
user_me.drone_dict[drone_id].init(drone_id);
}
parent.postMessage({type: "initialized"});
} else if (type === "load") {
loadUserScript(evt.data.path);
parent.postMessage({type: "loaded"});
} else if (type === "update") {
for (const [id, drone] of Object.entries(user_me.drone_dict)) {
message = drone.message
if (message.length > 0) {
Object.entries(user_me.drone_dict).forEach(function ([id, drone]) {
message = drone.message;
if (user_me.id !== Number(id) && message.length > 0) {
message = JSON.parse(message);
if (user_me.id === id) {
continue;
}
if (user_me.hasOwnProperty("onGetMsg") &&
[-1, user_me.id].includes(message.dest_id)) {
user_me.onGetMsg(message.content);
}
}
}
});
// Call the drone onStart function
if (user_me.hasOwnProperty("onUpdate")) {
if (IS_A_DRONE && isInManualMode()) {
if (configuration.isADrone && isInManualMode()) {
setManualControlInput();
}
user_me.onUpdate(evt.data.timestamp);
......@@ -138,4 +144,7 @@ import { Worker } from "os";
std.exit(1);
}
};
}(console, Worker));
}(console, getAltitude, getAltitudeRel, getInitialAltitude, getLatitude,
getLongitude, getYaw, initPubsub, isInManualMode, landed, loiter, setAirspeed,
setAltitude, setManualControlInput, setMessage, setTargetCoordinates, std,
triggerParachute, Drone, Worker));
......@@ -22,7 +22,7 @@ md5sum = 5784bea3bd608913769ff9a8afcccb68
[profile-frontend]
filename = instance-frontend.cfg.in
md5sum = 2f0b4af26c5e947f77cf85ab44e0fe5d
md5sum = f6a7678e8dc18871d6b18e2138e95f7f
[profile-master]
filename = instance-master.cfg.in
......@@ -30,7 +30,7 @@ md5sum = 2aaab85bad51136b38f6a16d662a7b3e
[profile-slave-list]
filename = instance-slave-list.cfg.in
md5sum = b26f4536102ff2cdc1356f6626928975
md5sum = 939f475b5a1a67ade2617802e5dde3a9
[profile-master-publish-slave-information]
filename = instance-master-publish-slave-information.cfg.in
......@@ -102,7 +102,7 @@ md5sum = e82ccdb0b26552a1c88ff523d8fae24a
[profile-kedifa]
filename = instance-kedifa.cfg.in
md5sum = d790e23ebf7b07bb245322629d402551
md5sum = a9854d48e750b3599043715c95138d5d
[template-frontend-haproxy-rsyslogd-conf]
_update_hash_filename_ = templates/frontend-haproxy-rsyslogd.conf.in
......
......@@ -1112,12 +1112,24 @@ rotate-num = ${configuration:rotate-num}
post = kill -USR2 $(cat ${frontend-haproxy-configuration:slave-introspection-pid-file})
delaycompress =
[logrotate-setup-validate]
<= jinja2-template-base
url = {{ software_parameter_dict['template_wrapper'] }}
output = ${directory:scripts}/logrotate-setup-validate
command =
if ${logrotate:wrapper-path} -d > ${:state-file} 2>&1 ; then
cat /dev/null > ${:state-file}
fi
extra-context =
key content :command
state-file = ${directory:run}/logrotate-setup.state
[promise-logrotate-setup]
<= monitor-promise-base
promise = check_command_execute
promise = check_file_state
name = ${:_buildout_section_name_}.py
config-command =
${logrotate:wrapper-path} -d
config-filename = ${logrotate-setup-validate:state-file}
config-state = empty
[configuration]
{%- for key, value in instance_parameter_dict.items() -%}
......
......@@ -324,12 +324,24 @@ kedifa-csr-url = ${expose-csr:url}/${expose-csr-link-csr:filename}
csr-certificate = ${expose-csr-certificate-get:certificate}
monitor-base-url = ${monitor-instance-parameter:monitor-base-url}
[logrotate-setup-validate]
<= jinja2-template-base
url = {{ software_parameter_dict['template_wrapper'] }}
output = ${directory:scripts}/logrotate-setup-validate
command =
if ${logrotate:wrapper-path} -d > ${:state-file} 2>&1 ; then
cat /dev/null > ${:state-file}
fi
extra-context =
key content :command
state-file = ${directory:run}/logrotate-setup.state
[promise-logrotate-setup]
<= monitor-promise-base
promise = check_command_execute
promise = check_file_state
name = ${:_buildout_section_name_}.py
config-command =
${logrotate:wrapper-path} -d
config-filename = ${logrotate-setup-validate:state-file}
config-state = empty
[kedifa-auth-ready]
recipe = plone.recipe.command
......
......@@ -573,7 +573,6 @@ parts +=
kedifa-updater-run
frontend-haproxy-configuration
backend-haproxy-configuration
promise-logrotate-setup
promise-key-download-url-ready
{%- for part in part_list %}
{{ ' %s' % part }}
......@@ -642,13 +641,6 @@ init =
with open(options['certificate-file'], 'r') as fh:
options['certificate'] = fh.read()
[promise-logrotate-setup]
<= monitor-promise-base
promise = check_command_execute
name = ${:_buildout_section_name_}.py
config-command =
${logrotate:wrapper-path} -d
[key-download-url-ready]
recipe = slapos.recipe.build
output = {{ url_ready_file }}
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -13,6 +13,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -28,6 +29,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......@@ -19,5 +21,6 @@ T-3/var/run/backend_haproxy_configuration_last_state
T-3/var/run/backend_haproxy_graceful_configuration_state_signature
T-3/var/run/graceful_configuration_state_signature
T-3/var/run/httpd.pid
T-3/var/run/logrotate-setup.state
T-3/var/run/slave_introspection_configuration_last_state
T-3/var/run/slave_introspection_graceful_configuration_state_signature
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......@@ -49,6 +51,7 @@ T-3:frontend-haproxy-rsyslogd-{hash-generic}-on-watch STOPPED
T-3:frontend-haproxy-safe-graceful EXITED
T-3:kedifa-login-certificate-caucase-updater-on-watch STOPPED
T-3:kedifa-updater-{hash-generic}-on-watch STOPPED
T-3:logrotate-setup-validate EXITED
T-3:monitor-httpd-{hash-generic}-on-watch STOPPED
T-3:monitor-httpd-graceful EXITED
T-3:slave-instrospection-nginx-{hash-generic}-on-watch STOPPED
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
T-0/var/run/monitor-httpd.pid
T-1/var/run/kedifa.pid
T-1/var/run/logrotate-setup.state
T-1/var/run/monitor-httpd.pid
T-2/var/run/backend-haproxy-rsyslogd.pid
T-2/var/run/backend-haproxy.pid
......@@ -10,6 +11,7 @@ T-2/var/run/fhlog.sck
T-2/var/run/frontend-haproxy-rsyslogd.pid
T-2/var/run/graceful_configuration_state_signature
T-2/var/run/httpd.pid
T-2/var/run/logrotate-setup.state
T-2/var/run/monitor-httpd.pid
T-2/var/run/slave-introspection.pid
T-2/var/run/slave_introspection_configuration_last_state
......
......@@ -15,6 +15,7 @@ T-1:crond-{hash-generic}-on-watch RUNNING
T-1:expose-csr-{hash-generic}-on-watch RUNNING
T-1:kedifa-{hash-generic}-on-watch RUNNING
T-1:kedifa-reloader EXITED
T-1:logrotate-setup-validate EXITED
T-1:monitor-httpd-{hash-generic}-on-watch RUNNING
T-1:monitor-httpd-graceful EXITED
T-2:backend-client-login-certificate-caucase-updater-on-watch RUNNING
......@@ -30,6 +31,7 @@ T-2:frontend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-safe-graceful EXITED
T-2:kedifa-login-certificate-caucase-updater-on-watch RUNNING
T-2:kedifa-updater-{hash-generic}-on-watch RUNNING
T-2:logrotate-setup-validate EXITED
T-2:monitor-httpd-{hash-generic}-on-watch RUNNING
T-2:monitor-httpd-graceful EXITED
T-2:slave-instrospection-nginx-{hash-generic}-on-watch RUNNING
......
......@@ -34,7 +34,7 @@ md5sum = 0eeb24c6aa0760f0d33c4cc2828ddf30
[template-mariadb.cfg]
_update_hash_filename_ = instance-mariadb.cfg.jinja2.in
md5sum = bd5b0e72e8b5ddcd691e7f90a2947e50
md5sum = d1efa1148ef1d4fb3f2869f08ce85fd2
[template-my-cnf]
_update_hash_filename_ = templates/my.cnf.in
......
......@@ -209,7 +209,7 @@ wait-for-files =
[{{ section('odbc-ini') }}]
recipe = slapos.recipe.template
output = ${directory:etc}/odbc.ini
inline = {{ dumps(slapparameter_dict.get('odbc-ini', '')) }}
inline = {{ dumps(slapparameter_dict.get('odbc-ini', ' ')) }}
[{{ section('logrotate-entry-mariadb') }}]
< = logrotate-entry-base
......
......@@ -252,6 +252,11 @@ setup = ${slapos-repository:location}/software/mosquitto/test/
egg = slapos.test.peertube
setup = ${slapos-repository:location}/software/peertube/test/
[slapos.test.js-drone-setup]
<= setup-develop-egg
egg = slapos.test.js_drone
setup = ${slapos-repository:location}/software/js-drone/test/
[slapos.core-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.core.git
......@@ -316,6 +321,7 @@ eggs +=
${slapos.test.html5as-setup:egg}
${slapos.test.htmlvalidatorserver-setup:egg}
${slapos.test.hugo-setup:egg}
${slapos.test.js-drone-setup:egg}
${slapos.test.jscrawler-setup:egg}
${slapos.test.jstestnode-setup:egg}
${slapos.test.jupyter-setup:egg}
......@@ -407,6 +413,7 @@ tests =
html5as-base ${slapos.test.html5as-base-setup:setup}
htmlvalidatorserver ${slapos.test.htmlvalidatorserver-setup:setup}
hugo ${slapos.test.hugo-setup:setup}
js-drone ${slapos.test.js-drone-setup:setup}
jscrawler ${slapos.test.jscrawler-setup:setup}
jstestnode ${slapos.test.jstestnode-setup:setup}
jupyter ${slapos.test.jupyter-setup:setup}
......
......@@ -26,7 +26,7 @@ md5sum = d10b8e35b02b5391cf46bf0c7dbb1196
[template-mariadb]
filename = instance-mariadb.cfg.in
md5sum = cee995829fbd138a8c2c9209d72d01a0
md5sum = 93b2277185e4949a3d17be79d3710d2d
[template-kumofs]
filename = instance-kumofs.cfg.in
......
......@@ -193,7 +193,7 @@ environ =
[{{ section('odbc-ini') }}]
recipe = slapos.recipe.template
output = ${directory:etc}/odbc.ini
inline = {{ dumps(slapparameter_dict.get('odbc-ini', '')) }}
inline = {{ dumps(slapparameter_dict.get('odbc-ini', ' ')) }}
[{{ section('logrotate-entry-mariadb') }}]
< = logrotate-entry-base
......
......@@ -297,7 +297,7 @@ slapos.libnetworkcache = 0.25
slapos.rebootstrap = 4.5
slapos.recipe.build = 0.56
slapos.recipe.cmmi = 0.19
slapos.recipe.template = 5.0
slapos.recipe.template = 5.1
slapos.toolbox = 0.129
smmap = 5.0.0
sniffio = 1.3.0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment