Commit 7539a997 authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼

Update Release Candidate

parents 6a4868db 25439fae
...@@ -20,8 +20,8 @@ parts = ...@@ -20,8 +20,8 @@ parts =
[curl] [curl]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
shared = true shared = true
url = https://curl.se/download/curl-8.4.0.tar.bz2 url = https://curl.se/download/curl-8.6.0.tar.xz
md5sum = 1a61fde1fe5c7db5c29c1196435188a5 md5sum = 8f28f7e08c91cc679a45fccf66184fbc
configure-options = configure-options =
--disable-static --disable-static
--disable-ech --disable-ech
...@@ -57,6 +57,7 @@ configure-options = ...@@ -57,6 +57,7 @@ configure-options =
--with-nghttp2=${nghttp2:location} --with-nghttp2=${nghttp2:location}
--without-ngtcp2 --without-ngtcp2
--without-nghttp3 --without-nghttp3
--without-openssl-quic
--without-quiche --without-quiche
--without-zsh-functions-dir --without-zsh-functions-dir
--without-fish-functions-dir --without-fish-functions-dir
......
# simple, standalone, language-agnostic, RFC6455 compliant WebSocket Server, written in C. https://gwsocket.io
[buildout]
parts = gwsocket
[gwsocket]
recipe = slapos.recipe.cmmi
shared = true
url = https://tar.gwsocket.io/gwsocket-0.4.tar.gz
md5sum = 1367e77c47cb6379025e64deb85fb066
...@@ -7,8 +7,8 @@ parts = ...@@ -7,8 +7,8 @@ parts =
[libexpat] [libexpat]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
shared = true shared = true
url = https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.lz url = https://github.com/libexpat/libexpat/releases/download/R_2_6_2/expat-2.6.2.tar.lz
md5sum = 4add8675872d4b923d9b7871dc0f24d3 md5sum = 16ad24a204d5aee5fe8fb19e1a9b4700
configure-options = configure-options =
--disable-static --disable-static
--without-xmlwf --without-xmlwf
......
...@@ -35,9 +35,10 @@ shared = true ...@@ -35,9 +35,10 @@ shared = true
url = https://archive.mariadb.org//mariadb-${:version}/source/mariadb-${:version}.tar.gz url = https://archive.mariadb.org//mariadb-${:version}/source/mariadb-${:version}.tar.gz
pcre-location = ${pcre2:location} pcre-location = ${pcre2:location}
pre-configure = pre-configure =
set '\bSET(PLUGIN_AUTH_PAM YES CACHE BOOL "")' cmake/build_configurations/mysql_release.cmake d() { grep -q "$@"; sed -i "/$1/d" "$2"; }
grep -q "$@" d '\bSET(PLUGIN_AUTH_PAM YES CACHE BOOL "")' cmake/build_configurations/mysql_release.cmake
sed -i "/$1/d" "$2" d 'ADD_SUBDIRECTORY(\(mysql-test\|tests\)\b' CMakeLists.txt
d '\bINSTALL_MYSQL_TEST\b' cmake/plugin.cmake
configure-command = ${cmake:location}/bin/cmake configure-command = ${cmake:location}/bin/cmake
configure-options = configure-options =
-DCMAKE_INSTALL_PREFIX=@@LOCATION@@ -DCMAKE_INSTALL_PREFIX=@@LOCATION@@
......
...@@ -4,37 +4,56 @@ extends = ...@@ -4,37 +4,56 @@ extends =
../curl/buildout.cfg ../curl/buildout.cfg
../git/buildout.cfg ../git/buildout.cfg
../jsoncpp/buildout.cfg ../jsoncpp/buildout.cfg
../lxml-python/buildout.cfg
../macros/macro.pythonpath.eggs.cfg
../tinyxml2/buildout.cfg ../tinyxml2/buildout.cfg
../zlib/buildout.cfg ../zlib/buildout.cfg
parts = parts =
mavsdk mavsdk
[c-astral-headers]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/c-astral-c-library
revision = v1.0
git-executable = ${git:location}/bin/git
[gcc] [gcc]
min_version = 7.1 min_version = 7.1
[c-astral-xml-definition]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/c-astral-c-library.git
revision = v2.1
git-executable = ${git:location}/bin/git
[mavsdk-source] [mavsdk-source]
recipe = slapos.recipe.build:gitclone recipe = slapos.recipe.build:gitclone
repository = https://github.com/mavlink/MAVSDK.git repository = https://github.com/mavlink/MAVSDK.git
revision = v0.39.0 revision = v1.4.13
git-executable = ${git:location}/bin/git git-executable = ${git:location}/bin/git
ignore-cloning-submodules = true ignore-cloning-submodules = true
[future]
recipe = zc.recipe.egg:custom
egg = future
[mavsdk-env]
CMAKE_INCLUDE_PATH=${curl:location}/include:${jsoncpp:location}/include:${tinyxml2:location}/include
CMAKE_LIBRARY_PATH=${curl:location}/lib:${jsoncpp:location}/lib:${tinyxml2:location}/lib:${zlib:location}/lib
CMAKE_PROGRAM_PATH=${cmake:location}/bin
PATH=${pkgconfig:location}/bin/:${git:location}/bin/:%(PATH)s
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
[mavsdk-pythonpath]
<= macro.pythonpath.eggs
environment = mavsdk-env
eggs =
${future:egg}
${lxml-python:egg}
[mavsdk] [mavsdk]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
path = ${mavsdk-source:location} path = ${mavsdk-source:location}
cmake = ${cmake:location}/bin/cmake cmake = ${cmake:location}/bin/cmake
depends = ${mavsdk-pythonpath:recipe}
pre-configure = pre-configure =
${git:location}/bin/git submodule update --init --recursive ${git:location}/bin/git submodule update --init --recursive
cp -r ${c-astral-headers:location}/* ${mavsdk-source:location}/src/third_party/mavlink/include/mavlink/v2.0/ sed -i 's#message_definitions/v1.0#${c-astral-xml-definition:location}#' ${mavsdk-source:location}/third_party/mavlink/CMakeLists.txt
sed -i 's#common/mavlink.h#CAstral/mavlink.h#' ${mavsdk-source:location}/src/core/mavlink_include.h
configure-command = configure-command =
${:cmake} ${:cmake}
configure-options = configure-options =
...@@ -42,19 +61,23 @@ configure-options = ...@@ -42,19 +61,23 @@ configure-options =
-DCMAKE_C_FLAGS="${:CMAKE_CFLAGS}" -DCMAKE_C_FLAGS="${:CMAKE_CFLAGS}"
-DCMAKE_CXX_FLAGS="${:CMAKE_CFLAGS}" -DCMAKE_CXX_FLAGS="${:CMAKE_CFLAGS}"
-DCMAKE_INSTALL_PREFIX=@@LOCATION@@ -DCMAKE_INSTALL_PREFIX=@@LOCATION@@
-DCMAKE_INSTALL_RPATH=${:CMAKE_LIBRARY_PATH}:@@LOCATION@@/lib -DCMAKE_INSTALL_RPATH=${mavsdk-env:CMAKE_LIBRARY_PATH}:@@LOCATION@@/lib
-DPKG_CONFIG_EXECUTABLE=${pkgconfig:location}/bin/pkg-config -DPKG_CONFIG_EXECUTABLE=${pkgconfig:location}/bin/pkg-config
-DSUPERBUILD=OFF -DSUPERBUILD=OFF
-Bbuild/default -Bbuild/default
-H. -H.
-Wno-dev
make-binary = make-binary =
${:cmake} --build build/default --target install ${:cmake} --build build/default --target install
environment = environment = mavsdk-env
CMAKE_INCLUDE_PATH=${curl:location}/include:${jsoncpp:location}/include:${tinyxml2:location}/include
CMAKE_LIBRARY_PATH=${:CMAKE_LIBRARY_PATH}
CMAKE_PROGRAM_PATH=${cmake:location}/bin
PATH=${pkgconfig:location}/bin/:%(PATH)s
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_CFLAGS=-I${tinyxml2:location}/include
CMAKE_LIBRARY_PATH=${curl:location}/lib:${jsoncpp:location}/lib:${tinyxml2:location}/lib:${zlib:location}/lib
[c-astral-wrapper]
recipe = slapos.recipe.cmmi
configure-command = true
url = https://lab.nexedi.com/nexedi/c-astral-wrapper/-/archive/v2.0/c-astral-wrapper-v2.0.tar.gz
md5sum = ee2d05d225a57d17318282ff595fd498
environment =
CPLUS_INCLUDE_PATH=${qjs-wrapper-source:location}/include:${mavsdk:location}/include:${mavsdk:location}/include/mavsdk
LDFLAGS=-L${mavsdk:location}/lib -Wl,-rpath=${mavsdk:location}/lib
# Implementation of OPC UA (OPC Unified Architecture). https://open62541.org/ # Implementation of OPC UA (OPC Unified Architecture). https://open62541.org/
[buildout] [buildout]
parts = open62541 parts =
open62541
gcc-10.2
extends = extends =
../cmake/buildout.cfg ../cmake/buildout.cfg
../patch/buildout.cfg ../patch/buildout.cfg
../python3/buildout.cfg ../python3/buildout.cfg
../gcc/buildout.cfg
../defaults.cfg ../defaults.cfg
[gcc] [gcc]
...@@ -35,4 +38,4 @@ configure-options = ...@@ -35,4 +38,4 @@ configure-options =
post-install = post-install =
cp src/pubsub/*.h deps/open62541_queue.h @@LOCATION@@/include cp src/pubsub/*.h deps/open62541_queue.h @@LOCATION@@/include
environment = environment =
PATH=${python3:location}/bin:${patch:location}/bin:%(PATH)s PATH=${gcc-10.2:location}/bin:${python3:location}/bin:${patch:location}/bin:%(PATH)s
...@@ -16,9 +16,9 @@ parts = ...@@ -16,9 +16,9 @@ parts =
[openssh] [openssh]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
shared = true shared = true
md5sum = 3d29a7394816deeb57186899d7f7662c md5sum = 1100f170ca1bc669038ca3743e074094
location = @@LOCATION@@ location = @@LOCATION@@
url = https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.5p1.tar.gz url = https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.7p1.tar.gz
patch-binary = ${patch:location}/bin/patch patch-binary = ${patch:location}/bin/patch
patch-options = -p1 patch-options = -p1
patches = patches =
......
[buildout] [buildout]
extends = extends =
../git/buildout.cfg
../mavsdk/buildout.cfg ../mavsdk/buildout.cfg
../open62541/buildout.cfg ../open62541/buildout.cfg
../quickjs/buildout.cfg ../quickjs/buildout.cfg
parts = qjs-wrapper parts = qjs-wrapper
[qjs-wrapper-source]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/qjs-wrapper.git
revision = v2.0
git-executable = ${git:location}/bin/git
[qjs-wrapper] [qjs-wrapper]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
configure-command = true configure-command = true
url = https://lab.nexedi.com/nexedi/qjs-wrapper/-/archive/v1.3/qjs-wrapper-v1.3.tar.gz path = ${qjs-wrapper-source:location}
md5sum = 5f63356c6a10bf227e2641ea4f78c7a2
environment = environment =
C_INCLUDE_PATH=include:${open62541:location}/include:${open62541:location}/deps:${open62541:location}/src/pubsub:${quickjs:location}/include 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 LDFLAGS=-L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -L${c-astral-wrapper:location}/lib -Wl,-rpath=${c-astral-wrapper:location}/lib
LDFLAGS=-L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -L${mavsdk:location}/lib -Wl,-rpath=${mavsdk:location}/lib
...@@ -43,7 +43,7 @@ eggs = ...@@ -43,7 +43,7 @@ eggs =
[versions] [versions]
setuptools = 44.1.1 setuptools = 44.1.1
zc.buildout = 2.7.1+slapos019 zc.buildout = 2.7.1+slapos020
zc.recipe.egg = 2.0.3+slapos003 zc.recipe.egg = 2.0.3+slapos003
EOF EOF
......
...@@ -7,8 +7,8 @@ parts = tar ...@@ -7,8 +7,8 @@ parts = tar
[tar] [tar]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
shared = true shared = true
url = http://ftp.gnu.org/gnu/tar/tar-1.29.tar.xz url = http://ftp.gnu.org/gnu/tar/tar-1.35.tar.xz
md5sum = a1802fec550baaeecff6c381629653ef md5sum = a2d8042658cfd8ea939e6d911eaf4152
environment = environment =
FORCE_UNSAFE_CONFIGURE=1 FORCE_UNSAFE_CONFIGURE=1
PATH=${xz-utils:location}/bin:%(PATH)s PATH=${xz-utils:location}/bin:%(PATH)s
...@@ -45,10 +45,11 @@ class CertificateAuthority: ...@@ -45,10 +45,11 @@ class CertificateAuthority:
os.unlink(f) os.unlink(f)
try: try:
# no CA, let us create new one # no CA, let us create new one
popenCommunicate([self.openssl_binary, 'req', '-nodes', '-config', popenCommunicate([self.openssl_binary, 'req', '-utf8', '-nodes',
self.openssl_configuration, '-new', '-x509', '-extensions', '-config', self.openssl_configuration, '-new', '-x509',
'v3_ca', '-keyout', self.key, '-out', self.certificate, '-extensions', 'v3_ca', '-keyout', self.key, '-out',
'-days', '10950'], 'Certificate Authority %s\n' % uuid.uuid1()) self.certificate, '-days', '10950'],
'Certificate Authority %s\n' % uuid.uuid1())
except: except:
try: try:
for f in file_list: for f in file_list:
......
...@@ -45,6 +45,10 @@ def createInstanceParameterSchemaValidatorTest(path): ...@@ -45,6 +45,10 @@ def createInstanceParameterSchemaValidatorTest(path):
"http://json-schema.org/draft-04/schema#": jsonschema.Draft4Validator, "http://json-schema.org/draft-04/schema#": jsonschema.Draft4Validator,
"http://json-schema.org/draft-06/schema#": jsonschema.Draft6Validator, "http://json-schema.org/draft-06/schema#": jsonschema.Draft6Validator,
"http://json-schema.org/draft-07/schema#": jsonschema.Draft7Validator, "http://json-schema.org/draft-07/schema#": jsonschema.Draft7Validator,
"http://json-schema.org/draft/2019-09/schema": jsonschema.Draft201909Validator,
"http://json-schema.org/draft/2019-09/schema#": jsonschema.Draft201909Validator,
"http://json-schema.org/draft/2020-12/schema": jsonschema.Draft202012Validator,
"http://json-schema.org/draft/2020-12/schema#": jsonschema.Draft202012Validator,
} }
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
with open(path, "r") as json_file: with open(path, "r") as json_file:
...@@ -55,7 +59,6 @@ def createInstanceParameterSchemaValidatorTest(path): ...@@ -55,7 +59,6 @@ def createInstanceParameterSchemaValidatorTest(path):
validator.check_schema(json_dict) validator.check_schema(json_dict)
return run return run
def createSoftwareCfgValidatorTest(path, software_cfg_schema): def createSoftwareCfgValidatorTest(path, software_cfg_schema):
# Test that software json follows the schema for softwares json, # Test that software json follows the schema for softwares json,
# which is defined in schema.json in this directory # which is defined in schema.json in this directory
...@@ -64,13 +67,20 @@ def createSoftwareCfgValidatorTest(path, software_cfg_schema): ...@@ -64,13 +67,20 @@ def createSoftwareCfgValidatorTest(path, software_cfg_schema):
schema = json.load(json_file) schema = json.load(json_file)
jsonschema.validate(schema, software_cfg_schema) jsonschema.validate(schema, software_cfg_schema)
_viewed_software_type = []
# also make sure request and response schemas can be resolved # also make sure request and response schemas can be resolved
schema.setdefault('$id', 'file://' + path) schema.setdefault('$id', 'file://' + path)
resolver = jsonschema.RefResolver.from_schema(schema) resolver = jsonschema.RefResolver.from_schema(schema)
for software_type_definition in six.itervalues(schema['software-type']): for key, software_type_definition in six.iteritems(schema['software-type']):
resolver.resolve(software_type_definition['request']) resolver.resolve(software_type_definition['request'])
resolver.resolve(software_type_definition['response']) resolver.resolve(software_type_definition['response'])
# Ensure there inst a duplicated entry.
_software_type_tuple = (
software_type_definition.get("software-type", key),
software_type_definition.get("shared", False))
assert _software_type_tuple not in _viewed_software_type, \
"Duplicated software release on %s, shared: %s" % _software_type_tuple
_viewed_software_type.append(_software_type_tuple)
return run return run
......
[instance-profile] [instance-profile]
filename = instance.cfg.in filename = instance.cfg.in
md5sum = 17004b2adb98b545b16c6be60e8165e8 md5sum = 4b7e36bbb077f91cdde5a4a05502cf71
...@@ -53,9 +53,8 @@ recipe = slapos.cookbook:wrapper ...@@ -53,9 +53,8 @@ recipe = slapos.cookbook:wrapper
# needed libraries and tools inside SlapOS context # needed libraries and tools inside SlapOS context
environment = environment =
BEREMIZPYTHONPATH = {{ buildout['bin-directory'] }}/pythonwitheggs BEREMIZPYTHONPATH = {{ buildout['bin-directory'] }}/pythonwitheggs
PATH=$PATH:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin PATH={{ gcc_location }}/bin
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/x86_64-linux-gnu:/usr/lib:/lib/x86_64-linux-gnu/:/lib:/usr/lib/x86_64-linux-gnu/ LIBRARY_PATH={{ openssl_location }}/lib
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/
command-line = command-line =
{{ buildout['bin-directory'] }}/pythonwitheggs {{ buildout['directory'] }}/parts/beremiz-source/Beremiz_cli.py -k --project-home ${directory:home}/parts/download-plc/ build transfer run {{ buildout['bin-directory'] }}/pythonwitheggs {{ buildout['directory'] }}/parts/beremiz-source/Beremiz_cli.py -k --project-home ${directory:home}/parts/download-plc/ build transfer run
......
...@@ -7,6 +7,7 @@ extends = ...@@ -7,6 +7,7 @@ extends =
../../component/numpy/buildout.cfg ../../component/numpy/buildout.cfg
../../component/lxml-python/buildout.cfg ../../component/lxml-python/buildout.cfg
../../component/python-sslpsk/buildout.cfg ../../component/python-sslpsk/buildout.cfg
../../component/gcc/buildout.cfg
../../stack/monitor/buildout.cfg ../../stack/monitor/buildout.cfg
../../stack/slapos.cfg ../../stack/slapos.cfg
...@@ -17,10 +18,14 @@ parts = ...@@ -17,10 +18,14 @@ parts =
python-interpreter python-interpreter
matiec matiec
open62541 open62541
gcc-10.2
[python] [python]
part = python2.7 part = python2.7
[gcc]
part = gcc-10.2
[open62541] [open62541]
configure-options = configure-options =
-DBUILD_SHARED_LIBS=OFF -DBUILD_SHARED_LIBS=OFF
...@@ -89,8 +94,8 @@ extensions = jinja2.ext.do ...@@ -89,8 +94,8 @@ extensions = jinja2.ext.do
context = context =
section buildout buildout section buildout buildout
raw template_monitor ${monitor2-template:output} raw template_monitor ${monitor2-template:output}
# md5sum is fetched from buildout.hash.cfg and can be recalculated automatically by key openssl_location openssl:location
# calling update-hash key gcc_location gcc-10.2:location
[versions] [versions]
Twisted = 20.3.0 Twisted = 20.3.0
......
...@@ -107,6 +107,18 @@ ...@@ -107,6 +107,18 @@
"description": "Set open file descriptors soft limit to hard limit", "description": "Set open file descriptors soft limit to hard limit",
"type": "boolean" "type": "boolean"
}, },
"python-hash-seed": {
"description": "Sets the value of `PYTHONHASHSEED` environment variable for zope processes and test runner. If not provided, zope processes use python default (`0` for python2, `random` for python3) and test runner choose a different `PYTHONHASHSEED` for each execution.",
"oneOf": [
{
"type": "number"
},
{
"const": "random",
"type": "string"
}
]
},
"family-override": { "family-override": {
"description": "Family-wide options, possibly overriding global options", "description": "Family-wide options, possibly overriding global options",
"default": {}, "default": {},
......
# Javascript drone # # Javascript drone #
## Presentation ## ## Presentation ##
* Deploy `user.js` flight script on a drone swarm * Deploy `user.js` flight script on a drone swarm
* Deploy a GUI on subscribers
* Run the flight script or the GUI as a SlapOS service
* Compile all required libraries to run the flight script
## Parameters ## ## Parameters ##
* autopilot-ip: IPv4 address to identify the autopilot from the companion board * autopilotIp: IPv4 address to identify the autopilot from the companion board
* droneGuidList: List of computer id on which flight script must be deployed
* isASimulation: Must be set to 'true' to automatically take off during simulation
* multicastIp: IPv6 of the multicast group of the swarm
* netIf: Network interface used for multicast traffic
* flightScript: URL of user's script to execute to fly drone swarm
* subscriberGuidList: List of computer id on which a GUI must be deployed
* drone-guid-list: List of computer id on which flight script must be deployed ## How it works ##
* is-a-simulation: Must be set to 'true' to automatically take off during simulation For each computer listed in `droneGuidList` and `subscriberGuidList` the `peer` SR type will be instanciated.
* multicast-ip: IPv6 of the multicast group of the swarm Each instance will return a `instance-path`. Under this path one will find `quickjs binary` in `bin` folder
and `scripts` in `etc` folder. Subcribers also return a `httpd-url` (the GUI address) and a `websocket-url` (used by the
GUI).
* net-if: Network interface used for multicast traffic `quickjs binary location` `scripts location`/main.js `scripts location`/user.js is run as a SlapOS service. This allows
each instance to communicate with the others through OPC-UA pub/sub. For the drones it also establishes a connexion with
the UAV autopilot, for a subscriber it sends the pub/sub messages through the websocket.
* flight-script: URL of user's script to execute to fly drone swarm
* subscriber-guid-list: List of computer id on which subscription script must be deployed ## Web GUI (subcribers)
## How it works ##
For each computer listed in `drone-guid-list` and `subscriber-guid-list` a drone SR will be instanciated. ### Drones informations
Each instance will return a `instance-path`. Under this path one will find `quickjs binary` in `bin` folder
and `scripts` in `etc` folder. For each drone is displayed:
Run `quickjs binary location` `scripts location`/main.js `scripts location`/user.js . * the user script and autopilot logs
* the flight state (ready, flying, landing)
* the latitude in degrees
* the longitude in degrees
* the relative altitude in meters
* the yaw angle in degrees
* the speed (ground speed for multicopters, airspeed for fixed wings) in meters per second
* the climb rate in meters per second
### Buttons
* Start: sends a "start" message to the swarm and changes into a stop button
* Stop: sends a "stop" message to the swarm
* Switch leader: sends a "switch" message to the swarm, it is usually used to change the leader
* Quit: exits (closes websocket and stops pub/sub)
![GUI screenshot](images/js-drone_GUI_screenshot.png)
...@@ -12,26 +12,34 @@ ...@@ -12,26 +12,34 @@
# Substitution (${...:...}), extension ([buildout] extends = ...) and # Substitution (${...:...}), extension ([buildout] extends = ...) and
# section inheritance (< = ...) are NOT supported (but you should really # section inheritance (< = ...) are NOT supported (but you should really
# not need these here). # not need these here).
[index-html]
_update_hash_filename_ = web-gui/index.html.jinja2
md5sum = 1eedc017ecc9d1a6761dc2fff3bbab9b
[instance-profile] [instance-profile]
filename = instance.cfg filename = instance.cfg.in
md5sum = 360b58007c25727b7bd8a9154d5cafd4 md5sum = 80dae3e883663311d9814def78ee875a
[instance-default] [instance-default]
filename = instance-default.cfg filename = instance-default.cfg.jinja2
md5sum = 903939308701b11b1ff751784a9be110 md5sum = 9db922cc0fcaa67006a2d6b9b95b95fe
[instance-drone] [instance-peer]
filename = instance-drone.cfg filename = instance-peer.cfg.jinja2.in
md5sum = 1ff50063f5a54712a0bc0ff38fa74630 md5sum = d12fbb134c587173ddff46ff1bc6ffe7
[main] [main]
filename = main.js _update_hash_filename_ = drone-scripts/main.js.jinja2
md5sum = d0bfcc79cdd7c1e5b8f5d264cc59074e md5sum = 9a8ec8a2778f63789f39291795f47e98
[pubsub] [pubsub]
filename = pubsub.js _update_hash_filename_ = drone-scripts/pubsub.js.jinja2
md5sum = 1555496ad591a31a845f33488d5c335d md5sum = 1555496ad591a31a845f33488d5c335d
[script-js]
_update_hash_filename_ = web-gui/script.js.jinja2
md5sum = e28492276416c2d84e770217ae97a88f
[worker] [worker]
filename = worker.js _update_hash_filename_ = drone-scripts/worker.js.jinja2
md5sum = e4b4ca3bde1a21f1dbfc4ff7fa3b872c md5sum = 48540afedd5437129196d84832d2ed40
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */ /*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global arm, console, exit, open, scriptArgs, setTimeout, start, stop, /*global arm, console, close, dup2, exit, open, scriptArgs, setTimeout, start,
stopPubsub, takeOffAndWait, Worker*/ stop, stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM*/
import { import {
arm, arm,
start, start,
...@@ -8,17 +8,27 @@ import { ...@@ -8,17 +8,27 @@ import {
stopPubsub, stopPubsub,
takeOffAndWait takeOffAndWait
} from {{ json_module.dumps(qjs_wrapper) }}; } from {{ json_module.dumps(qjs_wrapper) }};
import { setTimeout, Worker } from "os"; import {
import { open, exit } from "std"; Worker,
SIGTERM,
(function (arm, console, exit, open, scriptArgs, setTimeout, start, stop, dup2,
stopPubsub, takeOffAndWait, Worker) { setTimeout,
signal
} from "os";
import { err, exit, open, out } from "std";
(function (arm, console, dup2, err, exit, open, out, scriptArgs,
setTimeout, start, stop, stopPubsub, takeOffAndWait, Worker,
SIGTERM) {
"use strict"; "use strict";
var CONF_PATH = {{ json_module.dumps(configuration) }}, var CONF_PATH = {{ json_module.dumps(configuration) }},
conf_file = open(CONF_PATH, "r"), conf_file = open(CONF_PATH, "r"),
configuration = JSON.parse(conf_file.readAsString()), configuration = JSON.parse(conf_file.readAsString()),
LOG_FILE = "{{ log_dir }}/mavsdk-log", MAVSDK_LOG_FILE_PATH =
"{{ log_dir }}/mavsdk_" + new Date().toISOString() + ".log",
LOG_FILE =
open("{{ log_dir }}/quickjs_" + new Date().toISOString() + ".log", "w"),
pubsubWorker, pubsubWorker,
worker, worker,
user_script = scriptArgs[1], user_script = scriptArgs[1],
...@@ -28,6 +38,10 @@ import { open, exit } from "std"; ...@@ -28,6 +38,10 @@ import { open, exit } from "std";
conf_file.close(); conf_file.close();
// redirect stdout and stderr
dup2(LOG_FILE.fileno(), out.fileno());
dup2(LOG_FILE.fileno(), err.fileno());
// Use a Worker to ensure the user script // Use a Worker to ensure the user script
// does not block the main script // does not block the main script
// (preventing it to be stopped for example) // (preventing it to be stopped for example)
...@@ -37,17 +51,28 @@ import { open, exit } from "std"; ...@@ -37,17 +51,28 @@ import { open, exit } from "std";
worker = new Worker("{{ worker_script }}"); worker = new Worker("{{ worker_script }}");
function quit(is_a_drone, exit_code) { function quit(is_a_drone, exit_code) {
worker.onmessage = null;
stopPubsub(); stopPubsub();
if (is_a_drone) { if (is_a_drone) {
stop(); stop();
} }
LOG_FILE.close();
exit(exit_code); exit(exit_code);
} }
function exitWorker(exit_code) {
worker.postMessage({
type: "exit",
code: exit_code
});
}
signal(SIGTERM, exitWorker.bind(null, 0));
function exitOnFail(ret, msg) { function exitOnFail(ret, msg) {
if (ret) { if (ret) {
console.log(msg); console.log(msg);
quit(1); exitWorker(1);
} }
} }
...@@ -55,7 +80,12 @@ import { open, exit } from "std"; ...@@ -55,7 +80,12 @@ import { open, exit } from "std";
var address = configuration.autopilotIp + ":" + configuration.autopilotPort; var address = configuration.autopilotIp + ":" + configuration.autopilotPort;
console.log("Will connect to", address); console.log("Will connect to", address);
exitOnFail( exitOnFail(
start(configuration.autopilotIp, configuration.autopilotPort, LOG_FILE, 60), start(
configuration.autopilotIp,
configuration.autopilotPort,
MAVSDK_LOG_FILE_PATH,
60
),
"Failed to connect to " + address "Failed to connect to " + address
); );
} }
...@@ -87,7 +117,7 @@ import { open, exit } from "std"; ...@@ -87,7 +117,7 @@ import { open, exit } from "std";
// First argument must provide the user script path // First argument must provide the user script path
if (user_script === undefined) { if (user_script === undefined) {
console.log('Please provide the user_script path.'); console.log('Please provide the user_script path.');
quit(1); exitWorker(1);
} }
worker.postMessage({ worker.postMessage({
...@@ -138,14 +168,16 @@ import { open, exit } from "std"; ...@@ -138,14 +168,16 @@ import { open, exit } from "std";
// Start the update loop // Start the update loop
loop(); loop();
} else if (type === 'updated') { } else if (type === 'updated') {
err.flush();
out.flush();
can_update = true; can_update = true;
} else if (type === 'exited') { } else if (type === 'exited') {
worker.onmessage = null; worker.onmessage = null;
quit(configuration.isADrone, e.data.exit); quit(configuration.isADrone, e.data.exit);
} else { } else {
console.log('Unsupported message type', type); console.log('Unsupported message type', type);
quit(configuration.isADrone, 1); exitWorker(1);
} }
}; };
}(arm, console, exit, open, scriptArgs, setTimeout, start, stop, stopPubsub, }(arm, console, dup2, err, exit, open, out, scriptArgs, setTimeout, start, stop,
takeOffAndWait, Worker)); stopPubsub, takeOffAndWait, Worker, SIGTERM));
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global console, getAltitude, getAltitudeRel, getInitialAltitude, gpsIsOk,
getLatitude, getLongitude, getYaw, execUserScript, initPubsub, loiter,
setAirSpeed, setMessage, setTargetCoordinates, std, triggerParachute,
updateLogAndProjection, Drone, Worker*/
import {
Drone,
triggerParachute,
getAirspeed,
getAltitude,
getClimbRate,
getInitialAltitude,
gpsIsOk,
getPosition,
getYaw,
initPubsub,
isLanding,
loiter,
setAirSpeed,
setMessage,
setTargetCoordinates,
updateLogAndProjection
} from {{ json_module.dumps(qjs_wrapper) }};
import {
SIGTERM,
WNOHANG,
Worker,
close,
exec,
kill,
pipe,
setReadHandler,
waitpid
} from "os";
import { evalScript, fdopen, loadFile, open } from "std";
(function (Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
fdopen, getAltitude, getInitialAltitude, gpsIsOk, getPosition,
getYaw, initPubsub, kill, isLanding, loadFile, loiter, open, pipe,
setAirSpeed, setMessage, setReadHandler, setTargetCoordinates,
triggerParachute, updateLogAndProjection, waitpid) {
// Every script is evaluated per drone
"use strict";
var CONF_PATH = {{ json_module.dumps(configuration) }},
conf_file = open(CONF_PATH, "r"),
configuration = JSON.parse(conf_file.readAsString()),
clientId,
drone_dict = {},
gwsocket_pid,
gwsocket_r_pipe_fd,
gwsocket_w_pipe_fd,
handleWebSocketMessage,
last_message_timestamp = 0,
last_log_timestamp = 0,
parent = Worker.parent,
peer_dict = {},
user_me = {
//required to fly
triggerParachute: triggerParachute,
exit: exitWorker,
getDroneDict: function () { return drone_dict; },
getAltitudeAbs: getAltitude,
getCurrentPosition: getPosition,
getInitialAltitude: getInitialAltitude,
gpsIsOk: gpsIsOk,
getYaw: getYaw,
getSpeed: getAirspeed,
getClimbRate: getClimbRate,
id: configuration.id,
isLanding: isLanding,
loiter: loiter,
sendMsg: function (msg, id) {
if (id === undefined) { id = -1; }
setMessage(JSON.stringify({
content: msg,
timestamp: Date.now(),
dest_id: id
}));
},
setAirSpeed: setAirSpeed,
setTargetCoordinates: setTargetCoordinates
};
conf_file.close();
function exitWorker(exit_code) {
if (user_me.hasOwnProperty("onWebSocketMessage")) {
stopGwsocket();
}
parent.postMessage({type: "exited", exit: exit_code});
parent.onmessage = null;
}
function readMessage(rd) {
function read4() {
var b1, b2, b3, b4;
b1 = rd.getByte();
b2 = rd.getByte();
b3 = rd.getByte();
b4 = rd.getByte();
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
}
clientId = read4();
var type = read4();
var len = read4();
var data = new ArrayBuffer(len);
rd.read(data, 0, len);
return {
client: clientId,
type: type,
data: String.fromCharCode.apply(null, new Uint8Array(data)).trim()
};
}
function writeMessage(wr, m) {
function write4(v) {
wr.putByte((v >> 24) & 0xFF);
wr.putByte((v >> 16) & 0xFF);
wr.putByte((v >> 8) & 0xFF);
wr.putByte(v & 0xFF);
}
write4(m.client);
write4(m.type);
write4(m.data.byteLength);
wr.write(m.data, 0, m.data.byteLength);
wr.flush();
}
function runGwsocket(onMessage) {
var gwsocket_w_pipe = pipe(),
gwsocket_r_pipe = pipe();
gwsocket_pid = exec([
"gwsocket",
"--port=" + configuration.websocketPort,
"--addr=" + configuration.websocketIp,
"--std",
"--strict"
], {
block: false,
usePath: false,
file: {{ json_module.dumps(gwsocket_bin) }},
stdin: gwsocket_w_pipe[0],
stdout: gwsocket_r_pipe[1]
});
gwsocket_w_pipe_fd = fdopen(gwsocket_w_pipe[1], "w");
gwsocket_r_pipe_fd = fdopen(gwsocket_r_pipe[0], "r");
handleWebSocketMessage = function () {
var message = readMessage(gwsocket_r_pipe_fd).data;
if (message.includes(configuration.websocketIp)) {
return;
}
onMessage(message);
};
user_me.writeWebsocketMessage = function (message) {
var buf = new ArrayBuffer(message.length);
var bufView = new Uint8Array(buf);
for (var i=0; i<message.length; i++) {
bufView[i] = message.charCodeAt(i);
}
writeMessage(gwsocket_w_pipe_fd, {client: clientId, type: 1, data: buf});
}
setReadHandler(gwsocket_r_pipe[0], handleWebSocketMessage);
}
function stopGwsocket() {
handleWebSocketMessage = null;
close(gwsocket_w_pipe_fd);
close(gwsocket_r_pipe_fd);
kill(gwsocket_pid, SIGTERM);
waitpid(gwsocket_pid, WNOHANG);
}
function loadUserScript(path) {
var script_content = loadFile(path);
if (script_content === null) {
console.log("Failed to load user script " + path);
exitWorker(1);
}
try {
evalScript(
"function execUserScript(from, me) {" + script_content + "};"
);
} catch (e) {
console.log("Failed to evaluate user script", e);
exitWorker(1);
}
execUserScript(null, user_me);
if (user_me.hasOwnProperty("onWebSocketMessage")) {
runGwsocket(user_me.onWebSocketMessage);
}
// Call the drone onStart function
if (user_me.hasOwnProperty("onStart")) {
user_me.onStart();
}
}
function handleMainMessage(evt) {
var type = evt.data.type, message, peer_id;
switch (type) {
case "initPubsub":
initPubsub(configuration.numberOfDrone, configuration.numberOfSubscriber);
for (peer_id = 0; peer_id < configuration.numberOfDrone + configuration.numberOfSubscriber; peer_id++) {
peer_dict[peer_id] = new Drone(peer_id);
peer_dict[peer_id].init(peer_id);
if (peer_id < configuration.numberOfDrone) {
drone_dict[peer_id] = peer_dict[peer_id];
}
}
parent.postMessage({type: "initialized"});
break;
case "load":
loadUserScript(evt.data.path);
parent.postMessage({type: "loaded"});
break;
case "update":
Object.entries(peer_dict).forEach(function ([id, peer]) {
message = peer.message;
if (user_me.id !== Number(id) && message.length > 0) {
message = JSON.parse(message);
if (message.timestamp != last_message_timestamp &&
user_me.hasOwnProperty("onGetMsg") &&
[-1, user_me.id].includes(message.dest_id)) {
last_message_timestamp = message.timestamp;
user_me.onGetMsg(message.content);
}
}
});
// Call the drone onStart function
if (user_me.hasOwnProperty("onUpdate")) {
user_me.onUpdate(evt.data.timestamp);
}
if (evt.data.timestamp - last_log_timestamp >= 1000) {
updateLogAndProjection();
last_log_timestamp = evt.data.timestamp;
}
parent.postMessage({type: "updated"});
break;
case "exit":
exitWorker(evt.data.code);
break;
default:
throw new Error("Unsupported message type", type);
};
}
parent.onmessage = function (evt) {
try {
handleMainMessage(evt);
} catch (error) {
// Catch all potential bug to exit the main process
// if it occurs
console.log(error);
exitWorker(1);
}
};
}(Drone, SIGTERM, WNOHANG, Worker, close, console, evalScript, exec,
fdopen, getAltitude, getInitialAltitude, gpsIsOk, getPosition, getYaw,
initPubsub, isLanding, kill, loadFile, loiter, open, pipe, setAirSpeed,
setMessage, setReadHandler, setTargetCoordinates, triggerParachute,
updateLogAndProjection, waitpid));
{% set autopilot_ip = slapparameter_dict.get('autopilotIp', '192.168.27.1') -%} {% set parameter_dict = dict(default_parameter_dict, **parameter_dict) -%}
{% set autopilot_port = slapparameter_dict.get('autopilotPort', 7909) -%} {% set guid_list = parameter_dict['droneGuidList'] + parameter_dict['subscriberGuidList'] -%}
{% 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 drone_id_list = [] -%}
{% set subscriber_id_list = [] -%} {% set subscriber_id_list = [] -%}
{% set part_list = ['publish-connection-information'] -%} {% set part_list = ['publish-connection-information'] -%}
{% for id, guid in enumerate(guid_list) -%} {% for id, guid in enumerate(guid_list) -%}
{% set request_drone_section_title = 'request-drone' ~ id -%} {% set request_peer_section_title = 'request-peer' ~ id -%}
{% do part_list.append(request_drone_section_title) %} {% do part_list.append(request_peer_section_title) %}
[{{ request_drone_section_title }}] [{{ request_peer_section_title }}]
<= slap-connection <= slap-connection
recipe = slapos.cookbook:request.serialised recipe = slapos.cookbook:request.serialised
name = Drone{{ id }} name = Peer{{ id }}
software-url = $${:software-release-url} software-url = ${:software-release-url}
software-type = drone software-type = peer
return = instance-path return = instance-path
sla-computer_guid = {{ guid }} sla-computer_guid = {{ guid }}
config-autopilotIp = {{ autopilot_ip }} config-autopilotIp = {{ parameter_dict['autopilotIp'] }}
config-autopilotPort = {{ dumps(autopilot_port) }} config-autopilotPort = {{ dumps(parameter_dict['autopilotPort']) }}
config-numberOfPeers = {{ dumps(nb_peer) }} config-numberOfDrone = {{ dumps(len(parameter_dict['droneGuidList'])) }}
config-numberOfSubscriber = {{ dumps(len(parameter_dict['subscriberGuidList'])) }}
config-id = {{ dumps(id) }} config-id = {{ dumps(id) }}
config-isASimulation = {{ dumps(is_a_simulation) }} config-isASimulation = {{ dumps(parameter_dict['isASimulation']) }}
{% if guid in drone_guid_list -%} {% if id < len(parameter_dict['droneGuidList']) -%}
{% do drone_id_list.append(id) %} {% do drone_id_list.append(id) %}
config-isADrone = {{ dumps(True) }} config-isADrone = {{ dumps(True) }}
config-flightScript = {{ flight_script }} config-flightScript = {{ parameter_dict['flightScript'] }}
{% else -%} {% else -%}
{% do subscriber_id_list.append(id) %} {% do subscriber_id_list.append(id) %}
config-isADrone = {{ dumps(False) }} config-isADrone = {{ dumps(False) }}
config-flightScript = https://lab.nexedi.com/nexedi/flight-scripts/raw/master/subscribe.js config-flightScript = https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/subscribe.js
{% endif -%} {% endif -%}
config-multicastIp = {{ multicast_ip }} config-multicastIp = {{ parameter_dict['multicastIp'] }}
config-netIf = {{ net_if }} config-netIf = {{ parameter_dict['netIf'] }}
{% endfor %} {% endfor %}
[publish-connection-information] [publish-connection-information]
...@@ -50,6 +41,6 @@ subscriber-id-list = {{ dumps(subscriber_id_list) }} ...@@ -50,6 +41,6 @@ subscriber-id-list = {{ dumps(subscriber_id_list) }}
[buildout] [buildout]
parts = parts =
{%- for part in part_list %} {% for part in part_list %}
{{ part }} {{ part }}
{%- endfor -%} {% endfor %}
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"title": "Port of the drone's autopilot", "title": "Port of the drone's autopilot",
"description": "Port on which autopilot service is running.", "description": "Port on which autopilot service is running.",
"type": "integer", "type": "integer",
"default": "7909" "default": 7909
}, },
"droneGuidList": { "droneGuidList": {
"title": "List of drones computer ID", "title": "List of drones computer ID",
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
"title": "Script's URL of the flight", "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.", "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", "type": "string",
"default": "https://lab.nexedi.com/nexedi/flight-scripts/raw/master/default.js" "default": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/default.js"
}, },
"subscriberGuidList": { "subscriberGuidList": {
"title": "List of subscribers computer ID", "title": "List of subscribers computer ID",
......
...@@ -14,9 +14,14 @@ ...@@ -14,9 +14,14 @@
"description": "Port on which autopilot service is running.", "description": "Port on which autopilot service is running.",
"type": "integer" "type": "integer"
}, },
"numberOfPeers": { "numberOfDrone": {
"title": "Number of Peers", "title": "Number of drone",
"description": "Number of drones and subscribers in the swarm", "description": "Number of drone in the swarm",
"type": "integer"
},
"numberOfSubscriber": {
"title": "Number of subscriber",
"description": "Number of subscriber of the swarm",
"type": "integer" "type": "integer"
}, },
"id": { "id": {
......
[buildout] [buildout]
parts = parts =
main qjs-launcher
symlink-quickjs-binary
publish-connection-information publish-connection-information
[directory] [directory]
recipe = slapos.cookbook:mkdirectory recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory} home = $${buildout:directory}
bin = $${:home}/bin bin = $${:home}/bin
etc = $${:home}/etc etc = $${:home}/etc
srv = $${:home}/srv
var = $${:home}/var var = $${:home}/var
log = $${:var}/log log = $${:var}/log
public = $${:srv}/public
service = $${:etc}/service
[js-dynamic-template] [js-dynamic-template]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
rendered = $${directory:etc}/$${:_buildout_section_name_}.js rendered = $${directory:etc}/$${:_buildout_section_name_}.js
template = ${buildout:directory}/$${:_buildout_section_name_}.js
extra-context = extra-context =
context = context =
import json_module json import json_module json
raw gwsocket_bin ${gwsocket:location}/bin/gwsocket
raw qjs_wrapper ${qjs-wrapper:location}/lib/libqjswrapper.so raw qjs_wrapper ${qjs-wrapper:location}/lib/libqjswrapper.so
raw configuration {{ configuration }} raw configuration {{ configuration }}
$${:extra-context} $${:extra-context}
[main] [main]
<= js-dynamic-template <= js-dynamic-template
template = ${main:target}
extra-context = extra-context =
key log_dir directory:log key log_dir directory:log
key pubsub_script pubsub:rendered key pubsub_script pubsub:rendered
...@@ -32,19 +37,58 @@ extra-context = ...@@ -32,19 +37,58 @@ extra-context =
[pubsub] [pubsub]
<= js-dynamic-template <= js-dynamic-template
template = ${pubsub:target}
[worker] [worker]
<= js-dynamic-template <= js-dynamic-template
template = ${worker:target}
[user]
recipe = slapos.recipe.build:download
url = {{ parameter_dict['flightScript'] }}
destination = $${directory:etc}/user.js
offline = false
[qjs-launcher]
recipe = slapos.cookbook:wrapper
wrapper-path = $${directory:service}/qjs-launcher
command-line = ${quickjs:location}/bin/qjs $${main:rendered} $${user:target}
[script-js]
recipe = slapos.recipe.template:jinja2
template = ${script-js:target}
rendered = $${directory:public}/script.js
websocket-url = [{{ ipv6 }}]:{{ websocket_port }}
context =
raw websocket_url $${:websocket-url}
[index-html]
recipe = slapos.recipe.template:jinja2
template = ${index-html:target}
rendered = $${directory:public}/index.html
context =
raw nb_drones {{ parameter_dict['numberOfDrone'] }}
[httpd-port]
recipe = slapos.cookbook:free_port
minimum = 8080
maximum = 8090
ip = {{ ipv6 }}
[symlink-quickjs-binary] [httpd]
recipe = slapos.recipe.build recipe = slapos.cookbook:simplehttpserver
binary-path = ${quickjs:location}/bin/qjs host = {{ ipv6 }}
target = $${directory:bin}/qjs port = $${httpd-port:port}
init = base-path = $${directory:public}
import os wrapper = $${directory:service}/http-server
if not os.path.exists(options['target']): log-file = $${directory:log}/httpd.log
os.symlink(options['binary-path'], options['target']) use-hash-url = false
depends = $${index-html:rendered}
[publish-connection-information] [publish-connection-information]
recipe = slapos.cookbook:publish.serialised recipe = slapos.cookbook:publish.serialised
instance-path = $${directory:home} instance-path = $${directory:home}
{% if not parameter_dict['isADrone'] -%}
httpd-url = [$${httpd:host}]:$${httpd:port}
websocket-url = ws://$${script-js:websocket-url}
{% endif -%}
...@@ -9,7 +9,7 @@ offline = true ...@@ -9,7 +9,7 @@ offline = true
[switch-softwaretype] [switch-softwaretype]
recipe = slapos.cookbook:switch-softwaretype recipe = slapos.cookbook:switch-softwaretype
default = instance-default:output default = instance-default:output
drone = instance-drone:output peer = instance-peer:output
RootSoftwareInstance = $${:default} RootSoftwareInstance = $${:default}
[slap-configuration] [slap-configuration]
...@@ -22,36 +22,58 @@ cert = $${slap_connection:cert_file} ...@@ -22,36 +22,58 @@ cert = $${slap_connection:cert_file}
[dynamic-template-base] [dynamic-template-base]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
url = ${buildout:directory}/$${:_buildout_section_name_}.cfg output = $${buildout:directory}/$${:_buildout_section_name_}.cfg
output = $${buildout:directory}/$${:_buildout_section_name_} extra-context =
context =
jsonkey default_parameter_dict :default-parameters
key parameter_dict slap-configuration:configuration
$${:extra-context}
default-parameters =
{
"autopilotIp": "192.168.27.1",
"autopilotPort": 7909,
"flightScript": "https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/default.js",
"isASimulation": false,
"multicastIp": "ff15::1111",
"netIf": "eth0",
"droneGuidList": [],
"subscriberGuidList":[]
}
[instance-default] [instance-default]
<= dynamic-template-base <= dynamic-template-base
url = ${instance-default:target}
extensions = jinja2.ext.do extensions = jinja2.ext.do
context =
key slapparameter_dict slap-configuration:configuration
[instance-drone] [directory]
<= dynamic-template-base recipe = slapos.cookbook:mkdirectory
context = home = $${buildout:directory}
key configuration drone-configuration:output etc = $${:home}/etc
key user-script user:destination
[drone-configuration] [gwsocket-port]
recipe = slapos.cookbook:free_port
minimum = 6789
maximum = 6799
ip = $${slap-configuration:ipv6-random}
[peer-configuration]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
output = $${directory:etc}/configuration.json output = $${directory:etc}/configuration.json
extensions = jinja2.ext.do
context = context =
import json_module json import json_module json
key slapparameter_dict slap-configuration:configuration key websocket_ip gwsocket-port:ip
inline = {{ json_module.dumps(slapparameter_dict) }} key websocket_port gwsocket-port:port
key parameter_dict slap-configuration:configuration
inline =
{% do parameter_dict.__setitem__('websocketIp', websocket_ip) -%}
{% do parameter_dict.__setitem__('websocketPort', websocket_port) -%}
{{ json_module.dumps(parameter_dict) }}
[user] [instance-peer]
recipe = slapos.recipe.build:download <= dynamic-template-base
url = $${slap-configuration:configuration.flightScript} url = ${instance-peer:output}
destination = $${directory:etc}/user.js extra-context =
offline = false key configuration peer-configuration:output
key ipv6 slap-configuration:ipv6-random
[directory] key websocket_port gwsocket-port:port
recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory}
etc = $${:home}/etc
...@@ -5,16 +5,18 @@ extends = ...@@ -5,16 +5,18 @@ extends =
[sqdr-source] [sqdr-source]
recipe = slapos.recipe.build:gitclone recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/slaposdrone/squadrone.git repository = https://lab.nexedi.com/slaposdrone/squadrone.git
revision = v1.0 revision = v2.0
git-executable = ${git:location}/bin/git git-executable = ${git:location}/bin/git
[qjs-wrapper] [sqdr-wrapper]
recipe = slapos.recipe.cmmi recipe = slapos.recipe.cmmi
configure-command = true configure-command = true
url =
path = ${sqdr-source:location} path = ${sqdr-source:location}
md5sum = environment =
CPLUS_INCLUDE_PATH=include:${qjs-wrapper-source:location}/include
LDFLAGS=-L${sqdr-source:location}/lib -Wl,-rpath=${sqdr-source:location}/lib
[qjs-wrapper]
environment = environment =
C_INCLUDE_PATH=include:${open62541:location}/include:${open62541:location}/deps:${open62541:location}/src/pubsub:${quickjs:location}/include C_INCLUDE_PATH=include:${open62541:location}/include:${open62541:location}/deps:${open62541:location}/src/pubsub:${quickjs:location}/include
CPLUS_INCLUDE_PATH=include LDFLAGS=-L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -L${sqdr-wrapper:location}/lib -Wl,-rpath=${sqdr-wrapper:location}/lib
LDFLAGS=-L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -L${sqdr-source:location}/lib -Wl,-rpath=${sqdr-source:location}/lib
...@@ -3,42 +3,43 @@ extends = ...@@ -3,42 +3,43 @@ extends =
buildout.hash.cfg buildout.hash.cfg
../../stack/slapos.cfg ../../stack/slapos.cfg
../../component/qjs-wrapper/buildout.cfg ../../component/qjs-wrapper/buildout.cfg
../../component/gwsocket/buildout.cfg
parts = parts =
instance-profile instance-profile
instance-default
instance-drone
main
pubsub
worker
slapos-cookbook slapos-cookbook
[instance-profile] [instance-default]
recipe = slapos.recipe.template recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:filename} url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/template.cfg
[jinja-template-base] [template-base]
recipe = slapos.recipe.template recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:_buildout_section_name_}.cfg url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/${:_buildout_section_name_}.cfg
[instance-default] [instance-peer]
<= jinja-template-base <= template-base
output = ${buildout:directory}/${:_buildout_section_name_}
[instance-drone] [instance-profile]
<= jinja-template-base <= template-base
output = ${buildout:directory}/template.cfg
[download-file-base] [download]
recipe = slapos.recipe.build:download recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:filename} url = ${:_profile_base_location_}/${:_update_hash_filename_}
destination = ${buildout:directory}/${:filename}
[index-html]
<= download
[main] [main]
<= download-file-base <= download
[pubsub] [pubsub]
<= download-file-base <= download
[script-js]
<= download
[worker] [worker]
<= download-file-base <= download
...@@ -12,11 +12,11 @@ ...@@ -12,11 +12,11 @@
"index": 0 "index": 0
}, },
"drone": { "drone": {
"title": "Drone", "title": "Peer",
"software-type": "drone", "software-type": "peer",
"description": "Drone Instance", "description": "Peer Instance",
"request": "instance-drone-input-schema.json", "request": "instance-peer-input-schema.json",
"response": "instance-drone-output-schema.json", "response": "instance-peer-output-schema.json",
"index": 1 "index": 1
} }
} }
......
...@@ -43,7 +43,8 @@ setup(name=name, ...@@ -43,7 +43,8 @@ setup(name=name,
install_requires=[ install_requires=[
'slapos.core', 'slapos.core',
'slapos.libnetworkcache', 'slapos.libnetworkcache',
'erp5.util' 'erp5.util',
'websocket-client',
], ],
zip_safe=True, zip_safe=True,
test_suite='test', test_suite='test',
......
...@@ -30,12 +30,11 @@ import json ...@@ -30,12 +30,11 @@ import json
import os import os
import socket import socket
import struct import struct
import subprocess
import time import time
import websocket
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
MAIN_SCRIPT_NAME = 'main.js'
''' '''
0. positionArray 0. positionArray
0.1 latitude 0.1 latitude
...@@ -52,7 +51,6 @@ MONITORED_ITEM_NB = 3 ...@@ -52,7 +51,6 @@ MONITORED_ITEM_NB = 3
OPC_UA_PORT = 4840 OPC_UA_PORT = 4840
OPC_UA_NET_IF = 'lo' OPC_UA_NET_IF = 'lo'
MCAST_GRP = 'ff15::1111' MCAST_GRP = 'ff15::1111'
USER_SCRIPT_NAME = 'user.js'
# OPC UA Pub/Sub related constants # OPC UA Pub/Sub related constants
VERSION = 1 VERSION = 1
...@@ -98,63 +96,47 @@ UA_DATETIME_UNIX_EPOCH = 11644473600 * UA_DATETIME_SEC ...@@ -98,63 +96,47 @@ UA_DATETIME_UNIX_EPOCH = 11644473600 * UA_DATETIME_SEC
CONFIG_VERSION_MAJOR_VERSION = 1690792766 CONFIG_VERSION_MAJOR_VERSION = 1690792766
CONFIG_VERSION_MINOR_VERSION = 1690781976 CONFIG_VERSION_MINOR_VERSION = 1690781976
POSITION_ARRAY_TYPE = 11 #double POSITION_ARRAY_TYPE = 8 #int64
POSITION_ARRAY_VALUES = (45.64, 14.25, 686.61, 91.24) POSITION_ARRAY_INPUT_VALUES = (456400000, 142500000, 686000, 91000, 1697878907)
POSITION_ARRAY_OUTPUT_COEFS = (1e7, 1e7, 1000, 1000)
POSITION_ARRAY_OUTPUT_VALUES = tuple(value / coef for value, coef in zip(POSITION_ARRAY_INPUT_VALUES[:-1], POSITION_ARRAY_OUTPUT_COEFS))
SPEED_ARRAY_TYPE = 10 #float SPEED_ARRAY_TYPE = 10 #float
SPEED_ARRAY_VALUES = (-72.419998, 15.93, -0.015) SPEED_ARRAY_VALUES = (-72.419998, 15.93, -0.015)
STRING_TYPE = 12 STRING_TYPE = 12
TEST_MESSAGE = b'{"content":"{\\"next_checkpoint\\":1}","dest_id":-1}' MESSAGE_CONTENT = b'{\\"next_checkpoint\\":1}'
TEST_MESSAGE = b'{"content":"' + MESSAGE_CONTENT + b'","dest_id":-1}'
setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass( setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath( os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'software.cfg'))) os.path.join(os.path.dirname(__file__), '..', 'software.cfg')))
class JSDroneTestCase(SlapOSInstanceTestCase): class SubscriberTestCase(SlapOSInstanceTestCase):
@classmethod @classmethod
def getInstanceParameterDict(cls): def getInstanceParameterDict(cls):
return { return {
'_': json.dumps({ '_': json.dumps({
'droneGuidList': [cls.slap._computer_id],
'netIf': OPC_UA_NET_IF, 'netIf': OPC_UA_NET_IF,
'subscriberGuidList': [cls.slap._computer_id], 'subscriberGuidList': [cls.slap._computer_id],
}) })
} }
def get_partition(self, instance_type): def get_partition(self, partition_id):
software_url = self.getSoftwareURL() software_url = self.getSoftwareURL()
for computer_partition in self.slap.computer.getComputerPartitionList(): for computer_partition in self.slap.computer.getComputerPartitionList():
partition_url = computer_partition.getSoftwareRelease()._software_release if computer_partition.getId() == partition_id:
partition_type = computer_partition.getType()
if partition_url == software_url and partition_type == instance_type:
return computer_partition return computer_partition
raise Exception("JS-drone %s partition not found" % instance_type) raise Exception("Partition %s not found" % partition_id)
def setUp(self): def setUp(self):
super().setUp() super().setUp()
subscriber_partition = self.get_partition('drone') subscriber_partition = self.get_partition('SubscriberTestCase-2')
instance_path = json.loads( self.websocket_server_address = json.loads(
subscriber_partition.getConnectionParameterDict()['_'])['instance-path'] subscriber_partition.getConnectionParameterDict()['_'])['websocket-url']
quickjs_bin = os.path.join(instance_path, 'bin', 'qjs') time.sleep(0.5)
script_dir = os.path.join(instance_path, 'etc')
self.qjs_process = subprocess.Popen(
[
quickjs_bin,
os.path.join(script_dir, MAIN_SCRIPT_NAME),
os.path.join(script_dir, USER_SCRIPT_NAME),
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
time.sleep(0.1)
def tearDown(self):
if self.qjs_process.returncode == None:
self.qjs_process.kill()
self.qjs_process.communicate()
super().tearDown()
def ua_networkMessage_encodeHeader(self): def ua_networkMessage_encodeHeader(self):
ua_byte1 = int(VERSION) ua_byte1 = int(VERSION)
...@@ -224,8 +206,8 @@ class JSDroneTestCase(SlapOSInstanceTestCase): ...@@ -224,8 +206,8 @@ class JSDroneTestCase(SlapOSInstanceTestCase):
data_set_message += struct.pack('H', MONITORED_ITEM_NB) data_set_message += struct.pack('H', MONITORED_ITEM_NB)
data_set_message += self.ua_array_encode( data_set_message += self.ua_array_encode(
POSITION_ARRAY_TYPE, POSITION_ARRAY_TYPE,
'd', 'q',
POSITION_ARRAY_VALUES, POSITION_ARRAY_INPUT_VALUES,
) )
data_set_message += self.ua_array_encode( data_set_message += self.ua_array_encode(
SPEED_ARRAY_TYPE, SPEED_ARRAY_TYPE,
...@@ -244,25 +226,39 @@ class JSDroneTestCase(SlapOSInstanceTestCase): ...@@ -244,25 +226,39 @@ class JSDroneTestCase(SlapOSInstanceTestCase):
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
s.sendto(ua_message, ('::1', OPC_UA_PORT)) s.sendto(ua_message, ('::1', OPC_UA_PORT))
def test_process(self):
expected_process_name_list = [
'qjs-launcher-on-watch',
'http-server-on-watch',
]
with self.slap.instance_supervisor_rpc as supervisor:
process_names = [process['name']
for process in supervisor.getAllProcessInfo()]
for expected_process_name in expected_process_name_list:
self.assertIn(expected_process_name, process_names)
def test_requested_instances(self): def test_requested_instances(self):
connection_parameter_dict = json.loads( connection_parameter_dict = json.loads(
self.computer_partition.getConnectionParameterDict()['_']) self.computer_partition.getConnectionParameterDict()['_'])
self.assertEqual(connection_parameter_dict['drone-id-list'], []) self.assertEqual(connection_parameter_dict['drone-id-list'], [0])
self.assertEqual(connection_parameter_dict['subscriber-id-list'], [0]) self.assertEqual(connection_parameter_dict['subscriber-id-list'], [1])
def test_subscriber_instance_parameter_dict(self): def test_subscriber_instance_parameter_dict(self):
self.assertEqual( self.assertEqual(
json.loads(self.get_partition('drone').getInstanceParameterDict()['_']), json.loads(self.get_partition('SubscriberTestCase-2').getInstanceParameterDict()['_']),
{ {
'autopilotIp': '192.168.27.1', 'autopilotIp': '192.168.27.1',
'autopilotPort': 7909, 'autopilotPort': 7909,
'id': 0, 'numberOfDrone': 1,
'numberOfSubscriber': 1,
'id': 1,
'isASimulation': False, 'isASimulation': False,
'isADrone': False, 'isADrone': False,
'flightScript': 'https://lab.nexedi.com/nexedi/flight-scripts/raw/master/subscribe.js', 'flightScript': 'https://lab.nexedi.com/nexedi/flight-scripts/-/raw/v2.0/subscribe.js',
'multicastIp': MCAST_GRP, 'netIf': OPC_UA_NET_IF,
'numberOfPeers': 1, 'multicastIp': MCAST_GRP
'netIf': OPC_UA_NET_IF
} }
) )
...@@ -281,14 +277,32 @@ class JSDroneTestCase(SlapOSInstanceTestCase): ...@@ -281,14 +277,32 @@ class JSDroneTestCase(SlapOSInstanceTestCase):
self.assertIn(expected_string, f.readlines()) self.assertIn(expected_string, f.readlines())
def test_pubsub_subscription(self): def test_pubsub_subscription(self):
ws = websocket.WebSocket()
ws.connect(self.websocket_server_address, timeout=5)
# Check if first message is 'Unknown instruction IP' where IP is client IPv6 address
self.assertIn(
b'Unknown instruction %s' % ws.sock.getsockname()[0].encode(),
ws.recv_frame().data
)
self.assertEqual(
ws.recv_frame().data,
b''.join((
b'{"drone_dict":{"0":{"latitude":',
b'"%.6f","longitude":"%.6f","altitude":"%.2f",' % (0, 0, 0),
b'"yaw":"%.2f","speed":"%.2f","climbRate":"%.2f",' % (0, 0, 0),
b'"timestamp":%d}}}' % 0,
))
)
self.send_ua_networkMessage() self.send_ua_networkMessage()
time.sleep(0.1) time.sleep(0.1)
outs, _ = self.qjs_process.communicate(b'q\n', timeout=15) self.assertEqual(ws.recv_frame().data, MESSAGE_CONTENT.replace(b'\\', b''))
decoded_out = outs.decode() self.assertEqual(
for line in ( ws.recv_frame().data,
'Subscription 0 | MonitoredItem %s' % MONITORED_ITEM_NB, b''.join((
'Received position of drone 0: %f° %f° %fm %fm' % POSITION_ARRAY_VALUES, b'{"drone_dict":{"0":{"latitude":',
'Received speed of drone 0: %f° %fm/s %fm/s' % SPEED_ARRAY_VALUES, b'"%.6f","longitude":"%.6f","altitude":"%.2f",' % POSITION_ARRAY_OUTPUT_VALUES[:-1],
'Received message for drone 0: %s' % TEST_MESSAGE.decode(), b'"yaw":"%.2f","speed":"%.2f","climbRate":"%.2f",' % SPEED_ARRAY_VALUES,
): b'"timestamp":%d}}}' % POSITION_ARRAY_INPUT_VALUES[-1],
self.assertIn(line, decoded_out) ))
)
ws.close()
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<title>JS-Drone GUI</title>
<script src="script.js"></script>
<style>
button {
padding: 0.5%;
font-size: 24px;
cursor: pointer;
border: none;
border-radius: 10px;
box-shadow: 0 4px #999;
}
button:active {
box-shadow: 0 2px #666;
transform: translateY(4px);
}
div > * {margin: 1%}
label {margin: 2%}
table {width: 30%}
th, td{
padding: 1%;
text-align: center;
vertical-align: middle;
}
.connected {color: green}
.container {
display: flex;
align-items: center;
justify-content: center;
}
.disconnected {color: red}
.gray-button {background-color: lightgray}
.gray-button:hover {background-color: gray}
.green-button {background-color: #4caf50}
.green-button:hover {background-color: #3e8e41}
.red-button {background-color: red}
.red-button {background-color: #e42828}
</style>
</head>
<body>
<header class="container">
<label for="web-socket-status">web socket status:</label>
<output class="disconnected" id="web-socket-status">Disconnected</output>
</header>
<div class="container">
<table>
<tr>
<th></th>
{% for i in range(int(nb_drones)) -%}
<th>Drone {{ i }}</th>
{% endfor %}
</tr>
<tr>
<th>Flight state</th>
{% for i in range(int(nb_drones)) -%}
<td class="disconnected" id="flight_state_{{ i }}">Unknown</td>
{% endfor %}
</tr>
<tr>
<th>Latitude (°)</th>
{% for i in range(int(nb_drones)) -%}
<td id="latitude_{{ i }}"></td>
{% endfor %}
</tr>
<tr>
<th>Longitude (°)</th>
{% for i in range(int(nb_drones)) -%}
<td id="longitude_{{ i }}"></td>
{% endfor %}
</tr>
<tr>
<th>Altitude (m)</th>
{% for i in range(int(nb_drones)) -%}
<td id="altitude_{{ i }}"></td>
{% endfor %}
</tr>
<tr>
<th>Yaw (°)</th>
{% for i in range(int(nb_drones)) -%}
<td id="yaw_{{ i }}"></td>
{% endfor %}
</tr>
<tr>
<th>Speed (m/s)</th>
{% for i in range(int(nb_drones)) -%}
<td id="speed_{{ i }}"></td>
{% endfor %}
</tr>
<tr>
<th>Climb rate (m/s)</th>
{% for i in range(int(nb_drones)) -%}
<td id="climb_rate_{{ i }}"></td>
{% endfor %}
</tr>
</table>
</div>
<div class="container">
<button id="flight-btn" class="green-button" type="button">
Start
</button>
<button id="switch-btn" class="gray-button" type="button">
Switch leader
</button>
<button id="quit-btn" class="red-button" type="button">
Quit
</button>
</div>
</body>
</html>
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function () {
"use strict";
var ALTITUDE_BASE_ID = "altitude_",
SPEED_BASE_ID = "speed_",
CONNECTED_CLASS_NAME = "connected",
CLIMB_RATE_BASE_ID = "climb_rate_",
DISCONNECTED_CLASS_NAME = "disconnected",
FLIGHT_BTN_ID = "flight-btn",
FLIGHT_STATUS_BASE_ID = "flight_state_",
GREEN_BTN_CLASS_NAME = "green-button",
LATITUDE_BASE_ID = "latitude_",
LONGITUDE_BASE_ID = "longitude_",
QUIT_BTN_ID = "quit-btn",
RED_BTN_CLASS_NAME = "red-button",
SWITCH_BTN_ID = "switch-btn",
WEB_SOCKET_STATUS_OUTPUT_ID = "web-socket-status",
YAW_BASE_ID = "yaw_",
socket;
function updateConnexionClass(element, status) {
element.classList.remove(status ? DISCONNECTED_CLASS_NAME : CONNECTED_CLASS_NAME);
element.classList.add(status ? CONNECTED_CLASS_NAME : DISCONNECTED_CLASS_NAME);
}
function setWebSocketStatus(connected, status) {
var status_output = document.getElementById(WEB_SOCKET_STATUS_OUTPUT_ID);
updateConnexionClass(status_output, connected);
status_output.value = status;
}
function stopFlight(event) {
socket.send("stop");
event.target.removeEventListener('click', stopFlight);
}
function startFlight(event) {
var button = event.target;
socket.send("start");
button.removeEventListener('click', startFlight);
button.innerHTML = "Stop";
button.classList.remove(GREEN_BTN_CLASS_NAME);
button.classList.add(RED_BTN_CLASS_NAME);
button.addEventListener('click', stopFlight);
}
socket = new WebSocket('ws://{{ websocket_url }}');
socket.onopen = function(event) {
setWebSocketStatus(true, "Connected");
};
socket.onmessage = function(event) {
var message = JSON.parse(event.data),
flight_state_cell;
if (message.hasOwnProperty("drone_dict")) {
Object.entries(message["drone_dict"]).forEach(function ([id, drone]) {
document.getElementById(LATITUDE_BASE_ID + id).innerHTML = drone["latitude"];
document.getElementById(LONGITUDE_BASE_ID + id).innerHTML = drone["longitude"];
document.getElementById(ALTITUDE_BASE_ID + id).innerHTML = drone["altitude"];
document.getElementById(YAW_BASE_ID + id).innerHTML = drone["yaw"];
document.getElementById(SPEED_BASE_ID + id).innerHTML = drone["speed"];
document.getElementById(CLIMB_RATE_BASE_ID + id).innerHTML = drone["climbRate"];
});
} else if (message.hasOwnProperty("state") && message.hasOwnProperty("id")) {
flight_state_cell = document.getElementById(FLIGHT_STATUS_BASE_ID + message['id']);
flight_state_cell.innerHTML = message['state'];
updateConnexionClass(flight_state_cell, message['inAir']);
} else {
console.info(message);
}
};
socket.onclose = function(event) {
setWebSocketStatus(false, "Closed");
};
socket.onerror = function(event) {
console.error(event.reason);
};
document.addEventListener("DOMContentLoaded", () => {
document.getElementById(FLIGHT_BTN_ID).addEventListener('click', startFlight);
document.getElementById(SWITCH_BTN_ID).addEventListener('click', event => {
socket.send("switch");
});
document.getElementById(QUIT_BTN_ID).addEventListener('click', event => {
socket.send("quit");
});
});
}());
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
/*global console, getAltitude, getAltitudeRel, getInitialAltitude, getLatitude,
getLongitude, getYaw, execUserScript, initPubsub, landed, loiter, setAirspeed,
setMessage, setTargetCoordinates, std, triggerParachute, Drone, Worker*/
import {
Drone,
triggerParachute,
getAirspeed,
getAltitude,
getAltitudeRel,
getClimbRate,
getInitialAltitude,
getLatitude,
getLongitude,
getYaw,
initPubsub,
landed,
loiter,
setAirspeed,
setMessage,
setTargetCoordinates
} from {{ json_module.dumps(qjs_wrapper) }};
import * as std from "std";
import { Worker } from "os";
(function (console, getAltitude, getAltitudeRel, getInitialAltitude,
getLatitude, getLongitude, getYaw, initPubsub, landed, loiter,
setAirspeed, setMessage, setTargetCoordinates, std, triggerParachute,
Drone, Worker) {
// Every script is evaluated per drone
"use strict";
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,
in: std.in,
//required to fly
triggerParachute: triggerParachute,
drone_dict: {},
exit: function (exit_code) {
parent.postMessage({type: "exited", exit: exit_code});
parent.onmessage = null;
},
getAltitudeAbs: getAltitude,
getCurrentPosition: function () {
return {
x: getLatitude(),
y: getLongitude(),
z: getAltitudeRel()
};
},
getInitialAltitude: getInitialAltitude,
getYaw: getYaw,
getSpeed: getAirspeed,
getClimbRate: getClimbRate,
id: configuration.id,
landed: landed,
loiter: loiter,
sendMsg: function (msg, id) {
if (id === undefined) { id = -1; }
setMessage(JSON.stringify({ content: msg, dest_id: id }));
},
setAirspeed: setAirspeed,
setTargetCoordinates: setTargetCoordinates
};
conf_file.close();
function loadUserScript(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 + "};"
);
} catch (e) {
console.log("Failed to evaluate user script", e);
std.exit(1);
}
execUserScript(null, user_me);
// Call the drone onStart function
if (user_me.hasOwnProperty("onStart")) {
user_me.onStart();
}
}
function handleMainMessage(evt) {
var type = evt.data.type, message, drone_id;
if (type === "initPubsub") {
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(drone_id);
}
parent.postMessage({type: "initialized"});
} else if (type === "load") {
loadUserScript(evt.data.path);
parent.postMessage({type: "loaded"});
} else if (type === "update") {
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.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")) {
user_me.onUpdate(evt.data.timestamp);
}
parent.postMessage({type: "updated"});
} else {
throw new Error("Unsupported message type", type);
}
}
parent.onmessage = function (evt) {
try {
handleMainMessage(evt);
} catch (error) {
// Catch all potential bug to exit the main process
// if it occurs
console.log(error);
std.exit(1);
}
};
}(console, getAltitude, getAltitudeRel, getInitialAltitude, getLatitude,
getLongitude, getYaw, initPubsub, landed, loiter, setAirspeed, setMessage,
setTargetCoordinates, std, triggerParachute, Drone, Worker));
...@@ -18,7 +18,7 @@ md5sum = e000e7134113b9d1c63d40861eaf0489 ...@@ -18,7 +18,7 @@ md5sum = e000e7134113b9d1c63d40861eaf0489
[root-common] [root-common]
filename = root-common.cfg.in filename = root-common.cfg.in
md5sum = c91b5540f94ce76af31f84584df7a3ef md5sum = 102a7f1c1bc46a9b3fa5bd9b9a628e1d
[instance-neo-admin] [instance-neo-admin]
filename = instance-neo-admin.cfg.in filename = instance-neo-admin.cfg.in
......
...@@ -107,12 +107,11 @@ config-autostart = {{ dumps(sum(storage_count)) }} ...@@ -107,12 +107,11 @@ config-autostart = {{ dumps(sum(storage_count)) }}
{%- if monitor or node.get('admin') == 0 %} {%- if monitor or node.get('admin') == 0 %}
{%- do node.setdefault('monitor', 0) %} {%- do node.setdefault('monitor', 0) %}
{%- endif %} {%- endif %}
{%- for x in 'admin', 'master', 'storage-count' if node.get(x, 1) %} {%- if node.get('admin', 1) or node.get('master', 1) or node.get('storage-count', 1) %}
{%- do section_id_list.append(section_id) %} {%- do section_id_list.append(section_id) %}
[{{section_id}}] [{{section_id}}]
<= {{ prefix }}request-common <= {{ prefix }}request-common
name = {{ section_id }}
return = return =
master master
admin admin
...@@ -138,8 +137,14 @@ config-{{ k }} = {{ dumps(v) }} ...@@ -138,8 +137,14 @@ config-{{ k }} = {{ dumps(v) }}
{%- endfor %} {%- endfor %}
{{ sla(section_id) }} {{ sla(section_id) }}
{%- break %} {%- else %}
{%- endfor %}
[{{section(section_id)}}]
<= request-common-base
state = destroyed
{%- endif %}
name = {{ section_id }}
{%- endfor %} {%- endfor %}
{%- do assert(len(monitor) == 1, monitor) %} {%- do assert(len(monitor) == 1, monitor) %}
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
[template] [template]
filename = instance.cfg filename = instance.cfg
md5sum = 22e06207fab996bcc1c26992106618ab md5sum = acd9dd8dbe613e7101e62930a8380ef0
[template-ors] [template-ors]
filename = instance-ors.cfg filename = instance-ors.cfg
...@@ -44,7 +44,7 @@ md5sum = b7906ca3a6b17963f78f680fc0842b74 ...@@ -44,7 +44,7 @@ md5sum = b7906ca3a6b17963f78f680fc0842b74
[ru_lopcomm_libinstance.jinja2.cfg] [ru_lopcomm_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/lopcomm/libinstance.jinja2.cfg _update_hash_filename_ = ru/lopcomm/libinstance.jinja2.cfg
md5sum = 41ea7248b54ea893cb83a01655018711 md5sum = 7d05f6a3980a79bfd35677dbb8b988ee
[ru_sunwave_libinstance.jinja2.cfg] [ru_sunwave_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/sunwave/libinstance.jinja2.cfg _update_hash_filename_ = ru/sunwave/libinstance.jinja2.cfg
...@@ -60,7 +60,7 @@ md5sum = b7ec0025a92e0947e4ac6abc4b06bf19 ...@@ -60,7 +60,7 @@ md5sum = b7ec0025a92e0947e4ac6abc4b06bf19
[ru_lopcomm_config.jinja2.py] [ru_lopcomm_config.jinja2.py]
_update_hash_filename_ = ru/lopcomm/config.jinja2.py _update_hash_filename_ = ru/lopcomm/config.jinja2.py
md5sum = 167537a6aa2762355ee703d4c96351ea md5sum = 122726666d147447171dcae9ebf8d093
[ru_lopcomm_reset-info.jinja2.py] [ru_lopcomm_reset-info.jinja2.py]
_update_hash_filename_ = ru/lopcomm/reset-info.jinja2.py _update_hash_filename_ = ru/lopcomm/reset-info.jinja2.py
...@@ -88,7 +88,7 @@ md5sum = 52da9fe3a569199e35ad89ae1a44c30e ...@@ -88,7 +88,7 @@ md5sum = 52da9fe3a569199e35ad89ae1a44c30e
[template-enb] [template-enb]
_update_hash_filename_ = instance-enb.jinja2.cfg _update_hash_filename_ = instance-enb.jinja2.cfg
md5sum = f54bdbb308aee424b07ede8b551cfe5b md5sum = 8b9301f26fc4ffbc7eda9c1ac8da1a46
[template-ors-enb] [template-ors-enb]
_update_hash_filename_ = instance-ors-enb.jinja2.cfg _update_hash_filename_ = instance-ors-enb.jinja2.cfg
...@@ -96,7 +96,7 @@ md5sum = 601d6237059fa665d3f3ffb6a78ad9ca ...@@ -96,7 +96,7 @@ md5sum = 601d6237059fa665d3f3ffb6a78ad9ca
[template-core-network] [template-core-network]
_update_hash_filename_ = instance-core-network.jinja2.cfg _update_hash_filename_ = instance-core-network.jinja2.cfg
md5sum = 9402b750221765b6b124cf5ecb3e520c md5sum = 326e194e9c98d58d926f89521bb95df5
[template-ue] [template-ue]
_update_hash_filename_ = instance-ue.jinja2.cfg _update_hash_filename_ = instance-ue.jinja2.cfg
...@@ -108,11 +108,11 @@ md5sum = c5f581ba01654b2aec46000abf8d0e35 ...@@ -108,11 +108,11 @@ md5sum = c5f581ba01654b2aec46000abf8d0e35
[ue_db.jinja2.cfg] [ue_db.jinja2.cfg]
filename = config/ue_db.jinja2.cfg filename = config/ue_db.jinja2.cfg
md5sum = dcaac06553a3222b14c0013a13f4a149 md5sum = 3b901e8733e6afff8940c6c318da4493
[enb.jinja2.cfg] [enb.jinja2.cfg]
filename = config/enb.jinja2.cfg filename = config/enb.jinja2.cfg
md5sum = 30a26b975100b1af8937dfe3a7f5f496 md5sum = e1c40827e30d6ddcd98be35ec8569af2
[drb_lte.jinja2.cfg] [drb_lte.jinja2.cfg]
filename = config/drb_lte.jinja2.cfg filename = config/drb_lte.jinja2.cfg
...@@ -128,7 +128,7 @@ md5sum = 959523597e29b048e45ebf58f7ea4c5b ...@@ -128,7 +128,7 @@ md5sum = 959523597e29b048e45ebf58f7ea4c5b
[mme.jinja2.cfg] [mme.jinja2.cfg]
filename = config/mme.jinja2.cfg filename = config/mme.jinja2.cfg
md5sum = bee16b3b94fd57f5a19ea7b1f5955533 md5sum = 25ae6b1022548183293f0ef0c54532a7
[dnsmasq-core-network.jinja2.cfg] [dnsmasq-core-network.jinja2.cfg]
filename = config/dnsmasq-core-network.jinja2.cfg filename = config/dnsmasq-core-network.jinja2.cfg
...@@ -154,6 +154,10 @@ md5sum = e435990eb0a0d4be41efa9bd16dce09b ...@@ -154,6 +154,10 @@ md5sum = e435990eb0a0d4be41efa9bd16dce09b
_update_hash_filename_ = ru/lopcomm/cu_config.jinja2.xml _update_hash_filename_ = ru/lopcomm/cu_config.jinja2.xml
md5sum = 346c911e1ac5e5001a39c8926b44c91e md5sum = 346c911e1ac5e5001a39c8926b44c91e
[ru_lopcomm_cu_inactive_config.jinja2.xml]
_update_hash_filename_ = ru/lopcomm/cu_inactive_config.jinja2.xml
md5sum = 9d48c35f9939446ce75ae9f85e44c26a
[software.cfg.html] [software.cfg.html]
_update_hash_filename_ = gadget/software.cfg.html _update_hash_filename_ = gadget/software.cfg.html
md5sum = 61a2f783fbf683a34aed3d13e00baca2 md5sum = 61a2f783fbf683a34aed3d13e00baca2
......
...@@ -149,6 +149,7 @@ ...@@ -149,6 +149,7 @@
com_addr: "[{{ gtp_addr_v6 }}]:{{ slapparameter_dict.com_ws_port }}", com_addr: "[{{ gtp_addr_v6 }}]:{{ slapparameter_dict.com_ws_port }}",
com_auth: { com_auth: {
password: "{{ slapparameter_dict['websocket_password'] }}", password: "{{ slapparameter_dict['websocket_password'] }}",
unsecure: true,
}, },
{%- else %} {%- else %}
com_addr: "{{ slapparameter_dict.com_addr }}:{{ slapparameter_dict.com_ws_port }}", com_addr: "{{ slapparameter_dict.com_addr }}:{{ slapparameter_dict.com_ws_port }}",
...@@ -420,17 +421,24 @@ ...@@ -420,17 +421,24 @@
sr_period: 20, sr_period: 20,
cqi_period: 40, cqi_period: 40,
{%- if ors %}
mac_config: { mac_config: {
ul_max_harq_tx: 5, ul_max_harq_tx: 5,
dl_max_harq_tx: 5, dl_max_harq_tx: 5,
}, },
dpc_pucch_snr_target: 25,
{%- else %}
mac_config: {
ul_max_harq_tx: 28,
dl_max_harq_tx: 28,
},
dpc_pucch_snr_target: 20,
{%- endif %}
pusch_max_its: 6, pusch_max_its: 6,
dpc: true, dpc: true,
dpc_pusch_snr_target: 25, dpc_pusch_snr_target: 25,
dpc_pucch_snr_target: 25,
cipher_algo_pref: [], cipher_algo_pref: [],
integ_algo_pref: [2, 1], integ_algo_pref: [2, 1],
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
first_ip_addr: "{{ netaddr.IPAddress(netaddr.IPNetwork(slap_configuration.get('tun-ipv4-network', '')).first) + 2 }}", first_ip_addr: "{{ netaddr.IPAddress(netaddr.IPNetwork(slap_configuration.get('tun-ipv4-network', '')).first) + 2 }}",
last_ip_addr: "{{ netaddr.IPAddress(netaddr.IPNetwork(slap_configuration.get('tun-ipv4-network', '')).last) - 1 }}", last_ip_addr: "{{ netaddr.IPAddress(netaddr.IPNetwork(slap_configuration.get('tun-ipv4-network', '')).last) - 1 }}",
{% endif %} {% endif %}
ip_addr_shift: 2,
p_cscf_addr: ["{{ slap_configuration.get('tun-ipv4-addr', '') }}"], p_cscf_addr: ["{{ slap_configuration.get('tun-ipv4-addr', '') }}"],
erabs: [ erabs: [
......
{%- set filtered_slave_instance_list = [] %}
{%- for slave_instance in slave_instance_list %}
{%- if slave_instance.get('_', '') != '' %}
{%- set slave = json_module.loads(slave_instance.pop('_')) %}
{%- else %}
{%- set slave = slave_instance %}
{%- endif %}
{%- if slave.get('imsi', '') != '' %}
{%- do filtered_slave_instance_list.append(slave) %}
{%- endif %}
{%- endfor -%}
ue_db: [ ue_db: [
{%- for i, slave in enumerate(filtered_slave_instance_list) %} {%- for i, slave in enumerate(slap_configuration['sim_list']) %}
{%- set s = json_module.loads(slave.pop('_')) %}
{%- if i == 0 -%} {%- if i == 0 -%}
{ {
{%- else -%} {%- else -%}
, { , {
{%- endif %} {%- endif %}
sim_algo: "{{ slave.get('sim_algo', 'milenage') }}", sim_algo: "{{ s.get('sim_algo', 'milenage') }}",
imsi: "{{ slave.get('imsi', '') }}", imsi: "{{ s.get('imsi', '') }}",
opc: "{{ slave.get('opc', '') }}", opc: "{{ s.get('opc', '') }}",
amf: {{ slave.get('amf', '0x9001') }}, amf: {{ s.get('amf', '0x9001') }},
sqn: "{{ slave.get('sqn', '000000000000') }}", sqn: "{{ s.get('sqn', '000000000000') }}",
K: "{{ slave.get('k', '') }}", K: "{{ s.get('k', '') }}",
impu: "{{ slave.get('impu', '') }}", impu: "{{ s.get('impu', '') }}",
impi: "{{ slave.get('impi', '') }}", impi: "{{ s.get('impi', '') }}",
{%- if "ip" in s %}
pdn_list:[{
access_point_name: "internet",
default: true,
ipv4_addr: "{{ s['ip'] }}"
}]
{%- endif %}
} }
{%- endfor -%} {%- endfor -%}
] ]
......
...@@ -44,6 +44,12 @@ ...@@ -44,6 +44,12 @@
"title": "Use IPv4", "title": "Use IPv4",
"description": "Set to true to use IPv4 for AMF / MME addresses", "description": "Set to true to use IPv4 for AMF / MME addresses",
"type": "boolean" "type": "boolean"
},
"fixed_ips": {
"default": false,
"title": "Fixed IP for the UE",
"description": "Set to true to force a static IPv4 for each UE. If true, the number of UE is limited.",
"type": "boolean"
} }
} }
} }
{%- set dns_slave_instance_list = [] %} {%- set dns_slave_instance_list = [] %}
{%- set sim_slave_instance_list = [] %} {%- set sim_slave_instance_list = [] %}
{%- set fixed_ip = slapparameter_dict.get("fixed_ips", False) %}
{%- for slave in slave_instance_list %} {%- for slave in slave_instance_list %}
{%- set slave_parameters = json_module.loads(slave['_']) %} {%- set slave_parameters = json_module.loads(slave['_']) %}
{%- if slave_parameters.get('subdomain', '') != '' %} {%- if slave_parameters.get('subdomain', '') != '' %}
...@@ -19,8 +20,38 @@ ...@@ -19,8 +20,38 @@
recipe = slapos.cookbook:publish.serialised recipe = slapos.cookbook:publish.serialised
-slave-reference = {{ slave_reference }} -slave-reference = {{ slave_reference }}
info = Your SIM card with IMSI {{ slave_parameters.get('imsi', '') }} has been attached to service ${slap-configuration:instance-title}. info = Your SIM card with IMSI {{ slave_parameters.get('imsi', '') }} has been attached to service ${slap-configuration:instance-title}.
{%- if fixed_ip %}
ipv4 = ${sim-ip-configuration:{{slave_reference}}}
{%- endif %}
{%- endfor %} {%- endfor %}
[sim-ip-configuration]
recipe = slapos.recipe.build
sim-slave-instance-list = {{ dumps(sim_slave_instance_list) }}
ipv4-network = {{ slap_configuration.get('tun-ipv4-network', '') }}
init =
import netaddr
import json
network = netaddr.IPNetwork(options['ipv4-network'])
slave_list = options['sim-slave-instance-list']
# if we don't have enough IPv4 addresses in the network, don't force it
# should we make a promise fail ?
if len(slave_list) + 2 > network.size:
for s in slave_list:
options[s['slave_reference']] = "Too many SIM for the IPv4 network"
else:
# calculate the IP addresses of each SIM
sim_list = []
first_addr = netaddr.IPAddress(network.first)
for i, s in enumerate(sorted(slave_list, key=lambda x: json.loads(x['_'])['imsi'])):
ip = str(first_addr + 2 + i)
options[s['slave_reference']] = ip
slave_parameters = json.loads(s['_'])
slave_parameters['ip'] = ip
s['_'] = json.dumps(slave_parameters)
options['sim-with-ip-list'] = slave_list
{%- for slave in dns_slave_instance_list %} {%- for slave in dns_slave_instance_list %}
{%- set slave_parameters = json_module.loads(slave['_']) %} {%- set slave_parameters = json_module.loads(slave['_']) %}
{% set slave_reference = slave.get('slave_reference', '') %} {% set slave_reference = slave.get('slave_reference', '') %}
...@@ -69,7 +100,12 @@ cert = {{ slap_connection['cert-file'] }} ...@@ -69,7 +100,12 @@ cert = {{ slap_connection['cert-file'] }}
configuration.gtp_addr = 127.0.1.100 configuration.gtp_addr = 127.0.1.100
configuration.ims_addr = 127.0.0.1 configuration.ims_addr = 127.0.0.1
configuration.ims_bind = 127.0.0.2 configuration.ims_bind = 127.0.0.2
ue_db_path = {{ ue_db_path }} ue_db_path = ${ue-db-config:output}
{%- if fixed_ip %}
sim_list = ${sim-ip-configuration:sim-with-ip-list}
{%- else %}
sim_list = {{ dumps(sim_slave_instance_list) }}
{%- endif %}
[monitor-httpd-conf-parameter] [monitor-httpd-conf-parameter]
httpd-include-file = {{ buildout_directory }}/etc/httpd-include-file.conf httpd-include-file = {{ buildout_directory }}/etc/httpd-include-file.conf
...@@ -116,7 +152,7 @@ mode = 0775 ...@@ -116,7 +152,7 @@ mode = 0775
pidfile = ${directory:run}/ims.pid pidfile = ${directory:run}/ims.pid
hash-files = hash-files =
${ims-config:output} ${ims-config:output}
{{ ue_db_path }} ${ue-db-config:output}
environment = AMARISOFT_PATH=/opt/amarisoft/.amarisoft environment = AMARISOFT_PATH=/opt/amarisoft/.amarisoft
[mme-sh-wrapper] [mme-sh-wrapper]
...@@ -144,7 +180,7 @@ mode = 0775 ...@@ -144,7 +180,7 @@ mode = 0775
pidfile = ${directory:run}/mme.pid pidfile = ${directory:run}/mme.pid
hash-files = hash-files =
${mme-config:output} ${mme-config:output}
{{ ue_db_path }} ${ue-db-config:output}
${mme-sh-wrapper:output} ${mme-sh-wrapper:output}
environment = environment =
LD_LIBRARY_PATH={{ openssl_location }}/lib:{{ nghttp2_location }}/lib LD_LIBRARY_PATH={{ openssl_location }}/lib:{{ nghttp2_location }}/lib
...@@ -193,6 +229,14 @@ context = ...@@ -193,6 +229,14 @@ context =
url = {{ ims_template }} url = {{ ims_template }}
output = ${directory:etc}/ims.cfg output = ${directory:etc}/ims.cfg
[ue-db-config]
<= config-base
url = {{ ue_db_template }}
output = ${directory:etc}/ue_db.cfg
context =
section slap_configuration slap-configuration
import json_module json
[mme-config] [mme-config]
<= config-base <= config-base
{% if slapparameter_dict.get("mme_config_link", None) %} {% if slapparameter_dict.get("mme_config_link", None) %}
......
...@@ -171,7 +171,9 @@ inline = ...@@ -171,7 +171,9 @@ inline =
{%- if slapparameter_dict.get('xlog_fluentbit_forward_port') %} {%- if slapparameter_dict.get('xlog_fluentbit_forward_port') %}
Port ${:forward-port} Port ${:forward-port}
{%- endif %} {%- endif %}
{%- if slapparameter_dict.get('xlog_fluentbit_forward_shared_key') %}
Shared_Key ${:forward-shared-key} Shared_Key ${:forward-shared-key}
{%- endif %}
Self_Hostname ${:forward-self-hostname} Self_Hostname ${:forward-self-hostname}
tls on tls on
tls.verify off tls.verify off
......
...@@ -165,6 +165,7 @@ extra-context = ...@@ -165,6 +165,7 @@ extra-context =
raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target} raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target}
raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target} raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target}
raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target} raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_inactive_config.jinja2.xml:target}
raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target} raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target}
raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename} raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename}
raw ru_tapsplit ${ru_tapsplit:target} raw ru_tapsplit ${ru_tapsplit:target}
...@@ -189,12 +190,13 @@ extra-context = ...@@ -189,12 +190,13 @@ extra-context =
raw mme_template ${mme.jinja2.cfg:target} raw mme_template ${mme.jinja2.cfg:target}
raw dnsmasq_template ${dnsmasq-core-network.jinja2.cfg:target} raw dnsmasq_template ${dnsmasq-core-network.jinja2.cfg:target}
raw ims_template ${ims.jinja2.cfg:target} raw ims_template ${ims.jinja2.cfg:target}
raw ue_db_template ${ue_db.jinja2.cfg:target}
raw openssl_location ${openssl:location} raw openssl_location ${openssl:location}
raw nghttp2_location ${nghttp2:location} raw nghttp2_location ${nghttp2:location}
raw iperf3_location ${iperf3:location} raw iperf3_location ${iperf3:location}
raw dnsmasq_location ${dnsmasq:location} raw dnsmasq_location ${dnsmasq:location}
key ue_db_path ue-db-config:output
key slave_instance_list slap-configuration:slave-instance-list key slave_instance_list slap-configuration:slave-instance-list
section slap_configuration slap-configuration
[dynamic-template-ue] [dynamic-template-ue]
< = jinja2-template-base < = jinja2-template-base
...@@ -219,6 +221,7 @@ extra-context = ...@@ -219,6 +221,7 @@ extra-context =
raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target} raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target}
raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target} raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target}
raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target} raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_inactive_config.jinja2.xml:target}
raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target} raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target}
raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename} raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename}
raw ru_tapsplit ${ru_tapsplit:target} raw ru_tapsplit ${ru_tapsplit:target}
...@@ -227,13 +230,3 @@ extra-context = ...@@ -227,13 +230,3 @@ extra-context =
raw dnsmasq_location ${dnsmasq:location} raw dnsmasq_location ${dnsmasq:location}
raw openssh_location ${openssh:location} raw openssh_location ${openssh:location}
raw openssh_output_keygen ${openssh-output:keygen} raw openssh_output_keygen ${openssh-output:keygen}
[ue-db-config]
recipe = slapos.recipe.template:jinja2
url = ${ue_db.jinja2.cfg:target}
filename = ue_db.cfg
extensions = jinja2.ext.do
output = $${directory:etc}/$${:filename}
context =
import json_module json
key slave_instance_list slap-configuration:slave-instance-list
...@@ -32,6 +32,9 @@ destination = ${buildout:directory}/ncclient_common.py ...@@ -32,6 +32,9 @@ destination = ${buildout:directory}/ncclient_common.py
[ru_lopcomm_cu_config.jinja2.xml] [ru_lopcomm_cu_config.jinja2.xml]
<= download-base <= download-base
[ru_lopcomm_cu_inactive_config.jinja2.xml]
<= download-base
[ru_lopcomm_firmware-dl] [ru_lopcomm_firmware-dl]
recipe = slapos.recipe.build:download recipe = slapos.recipe.build:download
url = https://lab.nexedi.com/nexedi/ors-utils/raw/master/lopcomm-firmware/${:filename} url = https://lab.nexedi.com/nexedi/ors-utils/raw/master/lopcomm-firmware/${:filename}
......
...@@ -9,7 +9,7 @@ if __name__ == '__main__': ...@@ -9,7 +9,7 @@ if __name__ == '__main__':
while True: while True:
try: try:
nc.connect("{{ netaddr.IPAddress(vtap.gateway) }}", 830, "oranuser", "oranpassword") nc.connect("{{ netaddr.IPAddress(vtap.gateway) }}", 830, "oranuser", "oranpassword")
nc.edit_config(["{{ CreateProcessingEle_template }}", "{{ cu_config_template }}"]) nc.edit_config(["{{ CreateProcessingEle_template }}", "{{ cu_inactive_config_template }}", "{{ cu_config_template }}"])
break break
except Exception as e: except Exception as e:
nc.logger.debug('Got exception, waiting 10 seconds before reconnecting...') nc.logger.debug('Got exception, waiting 10 seconds before reconnecting...')
......
<xc:config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<user-plane-configuration xc:operation="replace" xmlns="urn:o-ran:uplane-conf-option8:1.0">
<!-- TX path: eaxcid → TxEndpoint
mod → static TxEndpoint → TxArray
TxCarrier
(static TxEndpoint, TxArray and their association are defined by RU itself)
-->
{%- set TxCarrier = 'TXA0CC00' %}
{%- for ant in range(ru.n_antenna_dl) %}
{%- set port = ant // 2 %}
{%- set chan = ant % 2 %}
{%- set txep = 'TXA0P%02dC%02d' % (port, chan) %}
<!-- TxAntenna{{ ant }} -->
<tx-endpoints>
<name>{{ txep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ ant }}</eaxc-id>
</e-axcid>
</tx-endpoints>
<tx-links>
<name>{{ txep }}</name>
<processing-element>PE0</processing-element>
<tx-array-carrier>{{ TxCarrier }}</tx-array-carrier>
<tx-endpoint>{{ txep }}</tx-endpoint>
</tx-links>
{%- endfor %}
<!--
RX path: eaxcid ← RxEndpoint
(data ∪ prach)
demod ← static RxEndpoint ← RxArray
RxCarrier
(static RxEndpoint, RxArray and their association are defined by RU itself)
-->
{%- set RxCarrier = 'RXA0CC00' %}
{%- for ant in range(ru.n_antenna_ul) %}
{%- set port = ant // 2 %}
{%- set chan = ant % 2 %}
{%- set rxep = 'RXA0P%02dC%02d' % (port, chan) %}
{%- set prachep = 'PRACH0P%02dC%02d' % (port, chan) %}
<!-- RxAntenna{{ ant }} -->
<rx-endpoints>
<name>{{ rxep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ ant }}</eaxc-id>
</e-axcid>
</rx-endpoints>
<rx-endpoints>
<name>{{ prachep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ 16*chan + 8 + port }}</eaxc-id>
</e-axcid>
</rx-endpoints>
<rx-links>
<name>{{ rxep }}</name>
<processing-element>PE0</processing-element>
<rx-array-carrier>{{ RxCarrier }}</rx-array-carrier>
<rx-endpoint>{{ rxep }}</rx-endpoint>
</rx-links>
<rx-links>
<name>{{ prachep }}</name>
<processing-element>PE0</processing-element>
<rx-array-carrier>{{ RxCarrier }}</rx-array-carrier>
<rx-endpoint>{{ prachep }}</rx-endpoint>
</rx-links>
{%- endfor %}
<!-- TX/RX carriers -->
<!-- TODO support multiple cells over 1 RU -->
{%- if cell.cell_type == 'lte' %}
{%- set dl_arfcn = cell.dl_earfcn %}
{%- set ul_arfcn = cell.ul_earfcn %}
{%- set dl_freq = int(xearfcn_module.frequency(dl_arfcn) * 1e6) %}
{%- set ul_freq = int(xearfcn_module.frequency(ul_arfcn) * 1e6) %}
{%- elif cell.cell_type == 'nr' %}
{%- set dl_arfcn = cell.dl_nr_arfcn %}
{%- set ul_arfcn = cell.ul_nr_arfcn %}
{%- set dl_freq = int(xnrarfcn_module.frequency(dl_arfcn) * 1e6) %}
{%- set ul_freq = int(xnrarfcn_module.frequency(ul_arfcn) * 1e6) %}
{%- else %}
{%- do bug('unreachable') %}
{%- endif %}
{%- set bw = int(cell.bandwidth * 1e6) %}
<tx-array-carriers>
<name>{{ TxCarrier }}</name>
<absolute-frequency-center>{{ dl_arfcn }}</absolute-frequency-center>
<center-of-channel-bandwidth>{{ dl_freq }}</center-of-channel-bandwidth>
<channel-bandwidth>{{ bw }}</channel-bandwidth>
<active>INACTIVE</active>
<rw-type>{{ cell.cell_type | upper }}</rw-type>
<rw-duplex-scheme>{{ cell.rf_mode | upper }}</rw-duplex-scheme>
<gain>{{ ru.tx_gain }}</gain>
<downlink-radio-frame-offset>0</downlink-radio-frame-offset>
<downlink-sfn-offset>0</downlink-sfn-offset>
</tx-array-carriers>
<rx-array-carriers>
<name>{{ RxCarrier }}</name>
<absolute-frequency-center>{{ ul_arfcn }}</absolute-frequency-center>
<center-of-channel-bandwidth>{{ ul_freq }}</center-of-channel-bandwidth>
<channel-bandwidth>{{ bw }}</channel-bandwidth>
<active>INACTIVE</active>
<downlink-radio-frame-offset>0</downlink-radio-frame-offset>
<downlink-sfn-offset>0</downlink-sfn-offset>
<!-- <gain>{{ ru.rx_gain }}</gain> -->
<!-- TODO(lu.xu): clarify with Lopcomm regaring rx gain -->
<gain-correction>0.0</gain-correction>
<n-ta-offset>0</n-ta-offset>
</rx-array-carriers>
</user-plane-configuration>
</xc:config>
...@@ -80,6 +80,18 @@ extra-context = ...@@ -80,6 +80,18 @@ extra-context =
ru = {{ dumps(ru) }} ru = {{ dumps(ru) }}
cell = {{ dumps(cell) }} cell = {{ dumps(cell) }}
[{{ B('%s-cu-inactive-config' % ru_ref) }}]
<= config-base
url = {{ ru_lopcomm_cu_inactive_config_template }}
output = ${directory:etc}/{{B('%s-cu_inactive_config.xml' % ru_ref)}}
extra-context =
import xearfcn_module xlte.earfcn
import xnrarfcn_module xlte.nrarfcn
key ru :ru
key cell :cell
ru = {{ dumps(ru) }}
cell = {{ dumps(cell) }}
[{{ B('%s-config-template' % ru_ref) }}] [{{ B('%s-config-template' % ru_ref) }}]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do extensions = jinja2.ext.do
...@@ -93,6 +105,7 @@ context = ...@@ -93,6 +105,7 @@ context =
raw buildout_directory_path {{ buildout_directory }} raw buildout_directory_path {{ buildout_directory }}
raw CreateProcessingEle_template {{ ru_lopcomm_CreateProcessingEle_template }} raw CreateProcessingEle_template {{ ru_lopcomm_CreateProcessingEle_template }}
key cu_config_template {{B('%s-cu-config' % ru_ref)}}:output key cu_config_template {{B('%s-cu-config' % ru_ref)}}:output
key cu_inactive_config_template {{B('%s-cu-inactive-config' % ru_ref)}}:output
import netaddr netaddr import netaddr netaddr
mode = 0775 mode = 0775
url = {{ ru_lopcomm_config_template }} url = {{ ru_lopcomm_config_template }}
......
...@@ -47,6 +47,7 @@ setup( ...@@ -47,6 +47,7 @@ setup(
'slapos.cookbook', 'slapos.cookbook',
'pcpp', 'pcpp',
'xmltodict', 'xmltodict',
'netaddr'
], ],
zip_safe=True, zip_safe=True,
test_suite='test', test_suite='test',
......
...@@ -600,6 +600,72 @@ class Lopcomm4: ...@@ -600,6 +600,72 @@ class Lopcomm4:
} }
}) })
# RU configuration in cu_inactive_config.xml
def test_ru_cu_inactive_config_xml(t):
def uctx(rf_mode, cell_type, dl_arfcn, ul_arfcn, bw, dl_freq, ul_freq, tx_gain, rx_gain):
return {
'tx-array-carriers': {
'rw-duplex-scheme': rf_mode,
'rw-type': cell_type,
'absolute-frequency-center': '%d' % dl_arfcn,
'center-of-channel-bandwidth': '%d' % dl_freq,
'channel-bandwidth': '%d' % bw,
'gain': '%d' % tx_gain,
'active': 'INACTIVE',
},
'rx-array-carriers': {
'absolute-frequency-center': '%d' % ul_arfcn,
'center-of-channel-bandwidth': '%d' % ul_freq,
'channel-bandwidth': '%d' % bw,
# XXX no rx_gain
'active': 'INACTIVE',
},
}
_ = t._test_ru_cu_inactive_config_xml
# rf_mode ctype dl_arfcn ul_arfcn bw dl_freq ul_freq txg rxg
_(1, uctx('FDD', 'LTE', 100, 18100, 5000000, 2120000000, 1930000000, 11, 21))
_(2, uctx('TDD', 'LTE', 40200, 40200, 10000000, 2551000000, 2551000000, 12, 22))
_(3, uctx('FDD', 'NR', 300300, 290700, 15000000, 1501500000, 1453500000, 13, 23))
_(4, uctx('TDD', 'NR', 470400, 470400, 20000000, 2352000000, 2352000000, 14, 24))
def _test_ru_cu_inactive_config_xml(t, i, uctx):
cu_xml = t.ipath('etc/%s' % xbuildout.encode('%s-cu_inactive_config.xml' % t.ref('RU%d' % i)))
with open(cu_xml, 'r') as f:
cu = f.read()
cu = xmltodict.parse(cu)
assertMatch(t, cu, {
'xc:config': {
'user-plane-configuration': {
'tx-endpoints': [
{'name': 'TXA0P00C00', 'e-axcid': {'eaxc-id': '0'}},
{'name': 'TXA0P00C01', 'e-axcid': {'eaxc-id': '1'}},
{'name': 'TXA0P01C00', 'e-axcid': {'eaxc-id': '2'}},
{'name': 'TXA0P01C01', 'e-axcid': {'eaxc-id': '3'}},
],
'tx-links': [
{'name': 'TXA0P00C00', 'tx-endpoint': 'TXA0P00C00'},
{'name': 'TXA0P00C01', 'tx-endpoint': 'TXA0P00C01'},
{'name': 'TXA0P01C00', 'tx-endpoint': 'TXA0P01C00'},
{'name': 'TXA0P01C01', 'tx-endpoint': 'TXA0P01C01'},
],
'rx-endpoints': [
{'name': 'RXA0P00C00', 'e-axcid': {'eaxc-id': '0'}},
{'name': 'PRACH0P00C00', 'e-axcid': {'eaxc-id': '8'}},
{'name': 'RXA0P00C01', 'e-axcid': {'eaxc-id': '1'}},
{'name': 'PRACH0P00C01', 'e-axcid': {'eaxc-id': '24'}},
],
'rx-links': [
{'name': 'RXA0P00C00', 'rx-endpoint': 'RXA0P00C00'},
{'name': 'PRACH0P00C00', 'rx-endpoint': 'PRACH0P00C00'},
{'name': 'RXA0P00C01', 'rx-endpoint': 'RXA0P00C01'},
{'name': 'PRACH0P00C01', 'rx-endpoint': 'PRACH0P00C01'},
],
} | uctx
}
})
# Sunwave4 is mixin to verify Sunwave driver wrt all LTE/NR x FDD/TDD modes. # Sunwave4 is mixin to verify Sunwave driver wrt all LTE/NR x FDD/TDD modes.
class Sunwave4: class Sunwave4:
......
...@@ -29,6 +29,7 @@ import os ...@@ -29,6 +29,7 @@ import os
import json import json
import glob import glob
import requests import requests
import netaddr
from test import yamlpp_load from test import yamlpp_load
...@@ -40,14 +41,6 @@ setUpModule, ORSTestCase = makeModuleSetUpAndTestCaseClass( ...@@ -40,14 +41,6 @@ setUpModule, ORSTestCase = makeModuleSetUpAndTestCaseClass(
param_dict = { param_dict = {
'testing': True, 'testing': True,
'sim_algo': 'milenage',
'imsi': '001010000000331',
'opc': '000102030405060708090A0B0C0D0E0F',
'amf': '0x9001',
'sqn': '000000000000',
'k': '00112233445566778899AABBCCDDEEFF',
'impu': 'impu331',
'impi': 'impi331@amarisoft.com',
'tx_gain': 17, 'tx_gain': 17,
'rx_gain': 17, 'rx_gain': 17,
'dl_earfcn': 36100, 'dl_earfcn': 36100,
...@@ -232,20 +225,47 @@ def test_mme_conf(self): ...@@ -232,20 +225,47 @@ def test_mme_conf(self):
conf = yamlpp_load(conf_file) conf = yamlpp_load(conf_file)
self.assertEqual(conf['plmn'], param_dict['core_network_plmn']) self.assertEqual(conf['plmn'], param_dict['core_network_plmn'])
def test_sim_card(self): def getSimParam(id=0):
return {
'sim_algo': 'milenage',
'imsi': '{0:015}'.format(1010000000000 + id),
'opc': '000102030405060708090A0B0C0D0E0F',
'amf': '0x9001',
'sqn': '000000000000',
'k': '00112233445566778899AABBCCDDEEFF',
'impu': 'impu%s' % '{0:03}'.format(id),
'impi': 'impi%s@amarisoft.com' % '{0:03}'.format(id)
}
def test_sim_card(self, nb_sim_cards, fixed_ips, tun_network):
conf_file = glob.glob(os.path.join( conf_file = glob.glob(os.path.join(
self.slap.instance_directory, '*', 'etc', 'ue_db.cfg'))[0] self.slap.instance_directory, '*', 'etc', 'ue_db.cfg'))[0]
conf = yamlpp_load(conf_file) conf = yamlpp_load(conf_file)
first_ip = netaddr.IPAddress(tun_network.first)
for i in range(nb_sim_cards):
params = getSimParam(i)
for n in "sim_algo imsi opc sqn impu impi".split(): for n in "sim_algo imsi opc sqn impu impi".split():
self.assertEqual(conf['ue_db'][0][n], param_dict[n]) self.assertEqual(conf['ue_db'][i][n], params[n], "%s doesn't match" % n)
self.assertEqual(conf['ue_db'][0]['K'], param_dict['k']) self.assertEqual(conf['ue_db'][i]['K'], params['k'])
self.assertEqual(conf['ue_db'][0]['amf'], int(param_dict['amf'], 16)) self.assertEqual(conf['ue_db'][i]['amf'], int(params['amf'], 16))
p = self.requestSlaveInstance().getConnectionParameterDict() p = self.requestSlaveInstanceWithId(i).getConnectionParameterDict()
p = p['_'] if '_' in p else p p = json.loads(p['_'])
self.assertIn('info', p) self.assertIn('info', p)
if fixed_ips:
self.assertIn('ipv4', p)
if nb_sim_cards + 2 > tun_network.size:
self.assertEqual(p['ipv4'], "Too many SIM for the IPv4 network")
else:
ip = str(first_ip + 2 + i)
self.assertEqual(p['ipv4'], ip)
self.assertEqual(conf['ue_db'][i]['pdn_list'][0]['access_point_name'], "internet")
self.assertTrue(conf['ue_db'][i]['pdn_list'][0]['default'])
self.assertEqual(conf['ue_db'][i]['pdn_list'][0]['ipv4_addr'], ip)
def test_monitor_gadget_url(self): def test_monitor_gadget_url(self):
parameters = json.loads(self.computer_partition.getConnectionParameterDict()['_']) parameters = json.loads(self.computer_partition.getConnectionParameterDict()['_'])
...@@ -304,16 +324,6 @@ class TestCoreNetworkParameters(ORSTestCase): ...@@ -304,16 +324,6 @@ class TestCoreNetworkParameters(ORSTestCase):
def test_mme_conf(self): def test_mme_conf(self):
test_mme_conf(self) test_mme_conf(self)
def requestSlaveInstance(cls):
software_url = cls.getSoftwareURL()
return cls.slap.request(
software_release=software_url,
partition_reference="SIM-CARD",
partition_parameter_kw={'_': json.dumps(param_dict)},
shared=True,
software_type='core-network',
)
class TestENBMonitorGadgetUrl(ORSTestCase): class TestENBMonitorGadgetUrl(ORSTestCase):
@classmethod @classmethod
def getInstanceParameterDict(cls): def getInstanceParameterDict(cls):
...@@ -351,20 +361,66 @@ class TestCoreNetworkMonitorGadgetUrl(ORSTestCase): ...@@ -351,20 +361,66 @@ class TestCoreNetworkMonitorGadgetUrl(ORSTestCase):
test_monitor_gadget_url(self) test_monitor_gadget_url(self)
class TestSimCard(ORSTestCase): class TestSimCard(ORSTestCase):
nb_sim_cards = 1
fixed_ips = False
tun_network = netaddr.IPNetwork('192.168.10.0/24')
@classmethod @classmethod
def requestDefaultInstance(cls, state='started'): def requestDefaultInstance(cls, state='started'):
default_instance = super( default_instance = super(
ORSTestCase, cls).requestDefaultInstance(state=state) ORSTestCase, cls).requestDefaultInstance(state=state)
cls._updateSlaposResource(
os.path.join(
cls.slap._instance_root, default_instance.getId()),
tun={"ipv4_network": str(cls.tun_network)}
)
cls.requestSlaveInstance() cls.requestSlaveInstance()
return default_instance return default_instance
@classmethod @classmethod
def requestSlaveInstance(cls): def requestSlaveInstance(cls):
return requestSlaveInstance(cls) for i in range(cls.nb_sim_cards):
cls.requestSlaveInstanceWithId(i)
@classmethod @classmethod
def getInstanceParameterDict(cls): def getInstanceParameterDict(cls):
return {'_': json.dumps({'testing': True})} return {'_': json.dumps({'testing': True, 'fixed_ips': cls.fixed_ips})}
@classmethod @classmethod
def getInstanceSoftwareType(cls): def getInstanceSoftwareType(cls):
return "core-network" return "core-network"
def test_sim_card(self): @classmethod
test_sim_card(self) def requestSlaveInstanceWithId(cls, id=0):
software_url = cls.getSoftwareURL()
param_dict = getSimParam(id)
return cls.slap.request(
software_release=software_url,
partition_reference="SIM-CARD-%s" % id,
partition_parameter_kw={'_': json.dumps(param_dict)},
shared=True,
software_type='core-network',
)
@classmethod
def _updateSlaposResource(cls, partition_path, **kw):
# we can update the .slapos-resourcefile from top partition because buildout
# will search for a .slapos-resource in upper directories until it finds one
with open(os.path.join(partition_path, '.slapos-resource'), 'r+') as f:
resource = json.load(f)
resource.update(kw)
f.seek(0)
f.truncate()
json.dump(resource, f, indent=2)
def test_sim_card(cls):
test_sim_card(cls, cls.nb_sim_cards, cls.fixed_ips, cls.tun_network)
class TestSimCardManySim(TestSimCard):
nb_sim_cards = 10
class TestSimCardFixedIps(TestSimCard):
fixed_ips = True
class TestSimCardManySimFixedIps(TestSimCard):
nb_sim_cards = 10
fixed_ips = True
class TestSimCardTooManySimFixedIps(TestSimCard):
nb_sim_cards = 10
fixed_ips = True
tun_network = netaddr.IPNetwork("192.168.10.0/29")
[buildout] [buildout]
parts = parts =
gcc-10.2
open62541 open62541
compile-coupler compile-coupler
slapos-cookbook slapos-cookbook
...@@ -11,6 +12,7 @@ extends = ...@@ -11,6 +12,7 @@ extends =
../../component/open62541/buildout.cfg ../../component/open62541/buildout.cfg
../../stack/monitor/buildout.cfg ../../stack/monitor/buildout.cfg
../../stack/slapos.cfg ../../stack/slapos.cfg
../../component/gcc/buildout.cfg
# disable warning for time_t type structure after 2038 # disable warning for time_t type structure after 2038
# https://www.gnu.org/software/gnulib/manual/html_node/Avoiding-the-year-2038-problem.html # https://www.gnu.org/software/gnulib/manual/html_node/Avoiding-the-year-2038-problem.html
...@@ -60,6 +62,7 @@ recipe = slapos.recipe.cmmi ...@@ -60,6 +62,7 @@ recipe = slapos.recipe.cmmi
path = ${osie-repository:location}/coupler path = ${osie-repository:location}/coupler
bin_dir = ${:path}/bin/ bin_dir = ${:path}/bin/
environment = environment =
PATH=${gcc-10.2:location}/bin:/usr/bin
OPEN62541_HOME = ${open62541:location} OPEN62541_HOME = ${open62541:location}
OPEN62541_SOURCE_HOME = ${open62541-source:location} OPEN62541_SOURCE_HOME = ${open62541-source:location}
C_COMPILER_EXTRA_FLAGS = -L ${mbedtls:location}/lib -Wl,-rpath=${mbedtls:location}/lib -l:libopen62541.so -L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -I${open62541:location}/include -I${open62541-source:location}/src/pubsub/ -I${open62541-source:location}/deps C_COMPILER_EXTRA_FLAGS = -L ${mbedtls:location}/lib -Wl,-rpath=${mbedtls:location}/lib -l:libopen62541.so -L${open62541:location}/lib -Wl,-rpath=${open62541:location}/lib -I${open62541:location}/include -I${open62541-source:location}/src/pubsub/ -I${open62541-source:location}/deps
......
...@@ -87,9 +87,6 @@ KEDIFA_PORT = '15080' ...@@ -87,9 +87,6 @@ KEDIFA_PORT = '15080'
SOURCE_IP = '127.0.0.1' SOURCE_IP = '127.0.0.1'
SOURCE_IPV6 = '::1' SOURCE_IPV6 = '::1'
# URL used to check for network connectivity
RE6ST_URL = 'http://[2001:67c:1254:4::1]/index.html'
# IP on which test run, in order to mimic HTTP[s] access # IP on which test run, in order to mimic HTTP[s] access
TEST_IP = os.environ['SLAPOS_TEST_IPV4'] TEST_IP = os.environ['SLAPOS_TEST_IPV4']
...@@ -2095,9 +2092,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin, AtsMixin): ...@@ -2095,9 +2092,8 @@ class TestSlave(SlaveHttpFrontendTestCase, TestDataMixin, AtsMixin):
self.assertEqual(0, result) self.assertEqual(0, result)
self.assertEqual( self.assertTrue(old_file_name + '.xz' in os.listdir(ats_logrotate_dir))
set(['log-old.old.xz', 'log-older.old.xz']), self.assertTrue(older_file_name + '.xz' in os.listdir(ats_logrotate_dir))
set(os.listdir(ats_logrotate_dir)))
self.assertFalse(old_file_name + '.xz' in os.listdir(ats_log_dir)) self.assertFalse(old_file_name + '.xz' in os.listdir(ats_log_dir))
self.assertFalse(older_file_name + '.xz' in os.listdir(ats_log_dir)) self.assertFalse(older_file_name + '.xz' in os.listdir(ats_log_dir))
...@@ -5076,17 +5072,17 @@ class TestReplicateSlaveOtherDestroyed( ...@@ -5076,17 +5072,17 @@ class TestReplicateSlaveOtherDestroyed(
self.assertFalse(node_2_present) self.assertFalse(node_2_present)
class TestRe6stVerificationUrlDefaultSlave(SlaveHttpFrontendTestCase, class TestRe6stVerificationUrlSlave(SlaveHttpFrontendTestCase, TestDataMixin):
TestDataMixin):
@classmethod @classmethod
def getInstanceParameterDict(cls): def getInstanceParameterDict(cls):
cls.re6st_test_url = '%sre6st.html' % (cls.backend_url,)
return { return {
'domain': 'example.com', 'domain': 'example.com',
'port': HTTPS_PORT, 'port': HTTPS_PORT,
'plain_http_port': HTTP_PORT, 'plain_http_port': HTTP_PORT,
'kedifa_port': KEDIFA_PORT, 'kedifa_port': KEDIFA_PORT,
'caucase_port': CAUCASE_PORT, 'caucase_port': CAUCASE_PORT,
're6st-verification-url': RE6ST_URL, 're6st-verification-url': cls.re6st_test_url,
} }
@classmethod @classmethod
...@@ -5117,61 +5113,7 @@ class TestRe6stVerificationUrlDefaultSlave(SlaveHttpFrontendTestCase, ...@@ -5117,61 +5113,7 @@ class TestRe6stVerificationUrlDefaultSlave(SlaveHttpFrontendTestCase,
self.assertEqual( self.assertEqual(
getPromisePluginParameterDict(re6st_connectivity_promise_file), getPromisePluginParameterDict(re6st_connectivity_promise_file),
{ {
'url': RE6ST_URL, 'url': self.re6st_test_url,
}
)
class TestRe6stVerificationUrlSlave(SlaveHttpFrontendTestCase,
TestDataMixin):
instance_parameter_dict = {
'port': HTTPS_PORT,
'domain': 'example.com',
'plain_http_port': HTTP_PORT,
'kedifa_port': KEDIFA_PORT,
'caucase_port': CAUCASE_PORT,
're6st-verification-url': RE6ST_URL,
}
@classmethod
def getInstanceParameterDict(cls):
return cls.instance_parameter_dict
@classmethod
def getSlaveParameterDictDict(cls):
return {
'default': {
'url': cls.backend_url,
'enable_cache': True,
},
}
def test_default(self):
self.instance_parameter_dict[
're6st-verification-url'] = 'some-re6st-verification-url'
# re-request instance with updated parameters
self.requestDefaultInstance()
# run once instance, it's only needed for later checks
try:
self.slap.waitForInstance()
except Exception:
pass
self.assertSlaveBase('default')
re6st_connectivity_promise_list = glob.glob(
os.path.join(
self.instance_path, '*', 'etc', 'plugin',
're6st-connectivity.py'))
self.assertEqual(1, len(re6st_connectivity_promise_list))
re6st_connectivity_promise_file = re6st_connectivity_promise_list[0]
self.assertEqual(
getPromisePluginParameterDict(re6st_connectivity_promise_file),
{
'url': 'some-re6st-verification-url',
} }
) )
......
[
{
"caucase_port": "15090",
"domain": "example.com",
"full_address_list": [],
"instance_title": "testing partition 0",
"kedifa_port": "15080",
"plain_http_port": "11080",
"port": "11443",
"re6st-verification-url": "http://[2001:67c:1254:4::1]/index.html",
"root_instance_title": "testing partition 0",
"slap_computer_id": "local",
"slap_computer_partition_id": "T-0",
"slap_software_release_url": "@@00getSoftwareURL@@",
"slap_software_type": "RootSoftwareInstance",
"slave_instance_list": [
{
"enable_cache": true,
"slap_software_type": "RootSoftwareInstance",
"slave_reference": "_default",
"slave_title": "_default",
"url": "http://@@_ipv4_address@@:@@_server_http_port@@/"
}
],
"timestamp": "@@TIMESTAMP@@"
},
{
"_": {
"caucase_port": "15090",
"cluster-identification": "testing partition 0",
"kedifa_port": "15080",
"monitor-cors-domains": "monitor.app.officejs.com",
"monitor-httpd-port": "8402",
"monitor-password": "@@monitor-password@@",
"monitor-username": "admin",
"slave-list": [
{
"enable_cache": true,
"slave_reference": "_default",
"url": "http://@@_ipv4_address@@:@@_server_http_port@@/"
}
]
},
"full_address_list": [],
"instance_title": "kedifa",
"root_instance_title": "testing partition 0",
"slap_computer_id": "local",
"slap_computer_partition_id": "T-1",
"slap_software_release_url": "@@00getSoftwareURL@@",
"slap_software_type": "kedifa",
"slave_instance_list": [],
"timestamp": "@@TIMESTAMP@@"
},
{
"_": {
"backend-client-caucase-url": "http://[@@_ipv6_address@@]:8990",
"cluster-identification": "testing partition 0",
"domain": "example.com",
"enable-http3": "false",
"extra_slave_instance_list": "[{\"enable_cache\": true, \"slave_reference\": \"_default\", \"url\": \"http://@@_ipv4_address@@:@@_server_http_port@@/\"}]",
"frontend-name": "caddy-frontend-1",
"http3-port": "443",
"kedifa-caucase-url": "http://[@@_ipv6_address@@]:15090",
"master-key-download-url": "https://[@@_ipv6_address@@]:15080/@@master-key-download-url_endpoint@@",
"monitor-cors-domains": "monitor.app.officejs.com",
"monitor-httpd-port": 8411,
"monitor-password": "@@monitor-password@@",
"monitor-username": "admin",
"plain_http_port": "11080",
"port": "11443",
"re6st-verification-url": "http://[2001:67c:1254:4::1]/index.html",
"slave-kedifa-information": "{\"_default\": {\"kedifa-caucase-url\": \"http://[@@_ipv6_address@@]:15090\", \"key-download-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@\", \"key-generate-auth-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@/@@default_key-upload-url@@\", \"key-upload-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@?auth=\"}}"
},
"full_address_list": [],
"instance_title": "caddy-frontend-1",
"root_instance_title": "testing partition 0",
"slap_computer_id": "local",
"slap_computer_partition_id": "T-2",
"slap_software_release_url": "@@00getSoftwareURL@@",
"slap_software_type": "single-custom-personal",
"slave_instance_list": [],
"timestamp": "@@TIMESTAMP@@"
}
]
T-0/var/log/monitor-httpd-access.log
T-0/var/log/monitor-httpd-error.log
T-0/var/log/slapgrid-T-0-error.log
T-1/var/log/expose-csr.log
T-1/var/log/kedifa.log
T-1/var/log/monitor-httpd-access.log
T-1/var/log/monitor-httpd-error.log
T-2/var/log/backend-haproxy.log
T-2/var/log/expose-csr.log
T-2/var/log/frontend-haproxy.log
T-2/var/log/monitor-httpd-access.log
T-2/var/log/monitor-httpd-error.log
T-2/var/log/slave-introspection-access.log
T-2/var/log/slave-introspection-error.log
T-2/var/log/trafficserver/manager.log
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
T-2/var/run/backend_haproxy_configuration_last_state
T-2/var/run/backend_haproxy_graceful_configuration_state_signature
T-2/var/run/bhlog.sck
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
T-2/var/run/slave_introspection_graceful_configuration_state_signature
T-0:aibcc-user-caucase-updater-on-watch RUNNING
T-0:aikc-user-caucase-updater-on-watch RUNNING
T-0:bootstrap-monitor EXITED
T-0:caucased-backend-client-{hash-generic}-on-watch RUNNING
T-0:certificate_authority-{hash-generic}-on-watch RUNNING
T-0:crond-{hash-generic}-on-watch RUNNING
T-0:master-introspection-server-{hash-master-introspection}-on-watch RUNNING
T-0:monitor-httpd-{hash-generic}-on-watch RUNNING
T-0:monitor-httpd-graceful EXITED
T-1:bootstrap-monitor EXITED
T-1:caucase-updater-on-watch RUNNING
T-1:caucased-{hash-generic}-on-watch RUNNING
T-1:certificate_authority-{hash-generic}-on-watch RUNNING
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
T-2:backend-haproxy-{hash-generic}-on-watch RUNNING
T-2:backend-haproxy-rsyslogd-{hash-generic}-on-watch RUNNING
T-2:backend-haproxy-safe-graceful EXITED
T-2:bootstrap-monitor EXITED
T-2:certificate_authority-{hash-generic}-on-watch RUNNING
T-2:crond-{hash-generic}-on-watch RUNNING
T-2:expose-csr-{hash-generic}-on-watch RUNNING
T-2:frontend-haproxy-{hash-generic}-on-watch RUNNING
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-2:slave-introspection-safe-graceful EXITED
T-2:trafficserver-{hash-generic}-on-watch RUNNING
T-2:trafficserver-reload EXITED
T-0/etc/cron.d/logrotate
T-0/etc/cron.d/monitor-configurator
T-0/etc/cron.d/monitor-globalstate
T-0/etc/cron.d/monitor_collect
T-1/etc/cron.d/logrotate
T-1/etc/cron.d/monitor-configurator
T-1/etc/cron.d/monitor-globalstate
T-1/etc/cron.d/monitor_collect
T-2/etc/cron.d/logrotate
T-2/etc/cron.d/monitor-configurator
T-2/etc/cron.d/monitor-globalstate
T-2/etc/cron.d/monitor_collect
T-2/etc/cron.d/trafficserver-logrotate
T-0/etc/plugin/__init__.py
T-0/etc/plugin/aibcc-sign-promise.py
T-0/etc/plugin/aibcc-user-caucase-updater.py
T-0/etc/plugin/aikc-sign-promise.py
T-0/etc/plugin/aikc-user-caucase-updater.py
T-0/etc/plugin/buildout-T-0-status.py
T-0/etc/plugin/caucased-backend-client.py
T-0/etc/plugin/check-backend-haproxy-statistic-url-frontend-node-1.py
T-0/etc/plugin/check-free-disk-space.py
T-0/etc/plugin/check-monitor-frontend-password.py
T-0/etc/plugin/master-introspection-server-ip-port-listening.py
T-0/etc/plugin/master-key-download-url-ready-promise.py
T-0/etc/plugin/master-key-generate-auth-url-ready-promise.py
T-0/etc/plugin/master-key-upload-url-ready-promise.py
T-0/etc/plugin/monitor-bootstrap-status.py
T-0/etc/plugin/monitor-http-frontend.py
T-0/etc/plugin/monitor-httpd-listening-on-tcp.py
T-0/etc/plugin/publish-failsafe-error.py
T-0/etc/plugin/rejected-slave.py
T-1/etc/plugin/__init__.py
T-1/etc/plugin/buildout-T-1-status.py
T-1/etc/plugin/caucased.py
T-1/etc/plugin/check-free-disk-space.py
T-1/etc/plugin/check-monitor-frontend-password.py
T-1/etc/plugin/expose-csr-ip-port-listening.py
T-1/etc/plugin/kedifa-http-reply.py
T-1/etc/plugin/monitor-bootstrap-status.py
T-1/etc/plugin/monitor-http-frontend.py
T-1/etc/plugin/monitor-httpd-listening-on-tcp.py
T-1/etc/plugin/promise-kedifa-auth-ready.py
T-1/etc/plugin/promise-logrotate-setup.py
T-2/etc/plugin/__init__.py
T-2/etc/plugin/backend-client-caucase-updater.py
T-2/etc/plugin/backend-haproxy-configuration.py
T-2/etc/plugin/backend-haproxy-statistic-frontend.py
T-2/etc/plugin/backend_haproxy_http.py
T-2/etc/plugin/backend_haproxy_https.py
T-2/etc/plugin/buildout-T-2-status.py
T-2/etc/plugin/caucase-updater.py
T-2/etc/plugin/check-free-disk-space.py
T-2/etc/plugin/check-monitor-frontend-password.py
T-2/etc/plugin/expose-csr-ip-port-listening.py
T-2/etc/plugin/frontend-frontend-haproxy-configuration-promise.py
T-2/etc/plugin/frontend_haproxy_ipv4_http.py
T-2/etc/plugin/frontend_haproxy_ipv4_https.py
T-2/etc/plugin/frontend_haproxy_ipv6_http.py
T-2/etc/plugin/frontend_haproxy_ipv6_https.py
T-2/etc/plugin/monitor-bootstrap-status.py
T-2/etc/plugin/monitor-http-frontend.py
T-2/etc/plugin/monitor-httpd-listening-on-tcp.py
T-2/etc/plugin/promise-key-download-url-ready.py
T-2/etc/plugin/promise-logrotate-setup.py
T-2/etc/plugin/re6st-connectivity.py
T-2/etc/plugin/slave-introspection-configuration.py
T-2/etc/plugin/slave_introspection_https.py
T-2/etc/plugin/trafficserver-cache-availability.py
T-2/etc/plugin/trafficserver-port-listening.py
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"kedifa_port": "15080", "kedifa_port": "15080",
"plain_http_port": "11080", "plain_http_port": "11080",
"port": "11443", "port": "11443",
"re6st-verification-url": "http://[2001:67c:1254:4::1]/index.html", "re6st-verification-url": "http://@@_ipv4_address@@:@@_server_http_port@@/re6st.html",
"root_instance_title": "testing partition 0", "root_instance_title": "testing partition 0",
"slap_computer_id": "local", "slap_computer_id": "local",
"slap_computer_partition_id": "T-0", "slap_computer_partition_id": "T-0",
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
"monitor-username": "admin", "monitor-username": "admin",
"plain_http_port": "11080", "plain_http_port": "11080",
"port": "11443", "port": "11443",
"re6st-verification-url": "http://[2001:67c:1254:4::1]/index.html", "re6st-verification-url": "http://@@_ipv4_address@@:@@_server_http_port@@/re6st.html",
"slave-kedifa-information": "{\"_default\": {\"kedifa-caucase-url\": \"http://[@@_ipv6_address@@]:15090\", \"key-download-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@\", \"key-generate-auth-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@/@@default_key-upload-url@@\", \"key-upload-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@?auth=\"}}" "slave-kedifa-information": "{\"_default\": {\"kedifa-caucase-url\": \"http://[@@_ipv6_address@@]:15090\", \"key-download-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@\", \"key-generate-auth-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@/@@default_key-upload-url@@\", \"key-upload-url\": \"https://[@@_ipv6_address@@]:15080/@@default_key-generate-auth-url@@?auth=\"}}"
}, },
"full_address_list": [], "full_address_list": [],
......
...@@ -8,9 +8,6 @@ T-1/var/log/monitor-httpd-error.log ...@@ -8,9 +8,6 @@ T-1/var/log/monitor-httpd-error.log
T-2/var/log/backend-haproxy.log T-2/var/log/backend-haproxy.log
T-2/var/log/expose-csr.log T-2/var/log/expose-csr.log
T-2/var/log/frontend-haproxy.log T-2/var/log/frontend-haproxy.log
T-2/var/log/httpd/_default_access_log
T-2/var/log/httpd/_default_backend_log
T-2/var/log/httpd/_default_frontend_log
T-2/var/log/monitor-httpd-access.log T-2/var/log/monitor-httpd-access.log
T-2/var/log/monitor-httpd-error.log T-2/var/log/monitor-httpd-error.log
T-2/var/log/slave-introspection-access.log T-2/var/log/slave-introspection-access.log
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# not need these here). # not need these here).
[template-erp5] [template-erp5]
filename = instance-erp5.cfg.in filename = instance-erp5.cfg.in
md5sum = 620912069597ea748d55cb68790fa270 md5sum = 38eab3283d175230231c998fa4a3416e
[template-balancer] [template-balancer]
filename = instance-balancer.cfg.in filename = instance-balancer.cfg.in
......
...@@ -253,6 +253,7 @@ config-memcached-url = ${request-memcached-volatile:connection-url} ...@@ -253,6 +253,7 @@ config-memcached-url = ${request-memcached-volatile:connection-url}
config-monitor-passwd = ${monitor-htpasswd:passwd} config-monitor-passwd = ${monitor-htpasswd:passwd}
config-mysql-test-url-list = ${request-mariadb:connection-test-database-list} config-mysql-test-url-list = ${request-mariadb:connection-test-database-list}
config-mysql-url-list = ${request-mariadb:connection-database-list} config-mysql-url-list = ${request-mariadb:connection-database-list}
config-python-hash-seed = {{ dumps(slapparameter_dict.get('python-hash-seed', '')) }}
config-site-id = {{ dumps(site_id) }} config-site-id = {{ dumps(site_id) }}
config-smtp-url = ${request-smtp:connection-url} config-smtp-url = ${request-smtp:connection-url}
config-timezone = {{ dumps(slapparameter_dict.get('timezone', 'UTC')) }} config-timezone = {{ dumps(slapparameter_dict.get('timezone', 'UTC')) }}
......
...@@ -59,6 +59,11 @@ initialization += ...@@ -59,6 +59,11 @@ initialization +=
[erp5_repository_list] [erp5_repository_list]
repository_id_list += wendelin repository_id_list += wendelin
[default-bt5]
list =
erp5_full_text_mroonga_catalog
erp5_wendelin_configurator
[local-bt5-repository] [local-bt5-repository]
list += ${wendelin:location}/bt5 list += ${wendelin:location}/bt5
......
...@@ -273,6 +273,7 @@ link-binary = ...@@ -273,6 +273,7 @@ link-binary =
${sed:location}/bin/sed ${sed:location}/bin/sed
${tesseract:location}/bin/tesseract ${tesseract:location}/bin/tesseract
${w3m:location}/bin/w3m ${w3m:location}/bin/w3m
${coreutils:location}/bin/shuf
fonts = fonts =
${liberation-fonts:location} ${liberation-fonts:location}
${ipaex-fonts:location} ${ipaex-fonts:location}
......
...@@ -74,7 +74,7 @@ md5sum = ca0cb83950dd9079cc289891cce08e76 ...@@ -74,7 +74,7 @@ md5sum = ca0cb83950dd9079cc289891cce08e76
[template-erp5] [template-erp5]
filename = instance-erp5.cfg.in filename = instance-erp5.cfg.in
md5sum = 7318eb93f1abf4c07a54b89e4a710fa6 md5sum = 6f57c834eb3f774d265c3fd6661429d8
[template-zeo] [template-zeo]
filename = instance-zeo.cfg.in filename = instance-zeo.cfg.in
...@@ -86,7 +86,7 @@ md5sum = 0ac4b74436f554cd677f19275d18d880 ...@@ -86,7 +86,7 @@ md5sum = 0ac4b74436f554cd677f19275d18d880
[template-zope] [template-zope]
filename = instance-zope.cfg.in filename = instance-zope.cfg.in
md5sum = 6178ba7b42848f9e2412ab898a7b026c md5sum = 8725a6b42de735b64b51d9bac598f94b
[template-balancer] [template-balancer]
filename = instance-balancer.cfg.in filename = instance-balancer.cfg.in
......
...@@ -247,6 +247,7 @@ config-memcached-url = ${request-memcached-volatile:connection-url} ...@@ -247,6 +247,7 @@ config-memcached-url = ${request-memcached-volatile:connection-url}
config-monitor-passwd = ${monitor-htpasswd:passwd} config-monitor-passwd = ${monitor-htpasswd:passwd}
config-mysql-test-url-list = ${request-mariadb:connection-test-database-list} config-mysql-test-url-list = ${request-mariadb:connection-test-database-list}
config-mysql-url-list = ${request-mariadb:connection-database-list} config-mysql-url-list = ${request-mariadb:connection-database-list}
config-python-hash-seed = {{ dumps(slapparameter_dict.get('python-hash-seed', '')) }}
config-site-id = {{ dumps(site_id) }} config-site-id = {{ dumps(site_id) }}
config-smtp-url = ${request-smtp:connection-url} config-smtp-url = ${request-smtp:connection-url}
config-timezone = {{ dumps(slapparameter_dict.get('timezone', 'UTC')) }} config-timezone = {{ dumps(slapparameter_dict.get('timezone', 'UTC')) }}
......
...@@ -85,6 +85,9 @@ environment += ...@@ -85,6 +85,9 @@ environment +=
JUPYTER_PATH=${directory:jupyter-dir} JUPYTER_PATH=${directory:jupyter-dir}
JUPYTER_CONFIG_DIR=${directory:jupyter-config-dir} JUPYTER_CONFIG_DIR=${directory:jupyter-config-dir}
JUPYTER_RUNTIME_DIR=${directory:jupyter-runtime-dir} JUPYTER_RUNTIME_DIR=${directory:jupyter-runtime-dir}
{% if slapparameter_dict.get('python-hash-seed') %}
PYTHONHASHSEED={{ slapparameter_dict['python-hash-seed'] }}
{% endif %}
{% if slapparameter_dict.get('wendelin-core-zblk-fmt') %} {% if slapparameter_dict.get('wendelin-core-zblk-fmt') %}
WENDELIN_CORE_ZBLK_FMT={{ slapparameter_dict['wendelin-core-zblk-fmt'] }} WENDELIN_CORE_ZBLK_FMT={{ slapparameter_dict['wendelin-core-zblk-fmt'] }}
{% endif %} {% endif %}
...@@ -469,9 +472,26 @@ context = ...@@ -469,9 +472,26 @@ context =
{% else -%} {% else -%}
[{{ section('run-unit-test-userhosts-wrapper') }}] [{{ section('run-unit-test-userhosts-wrapper') }}]
<= userhosts-wrapper-base <= userhosts-wrapper-base
wrapped-command-line = ${runUnitTest:wrapper-path} wrapped-command-line = ${run-unit-test-python-hash-seed-wrapper:output}
wrapper-path = ${buildout:bin-directory}/runUnitTest wrapper-path = ${buildout:bin-directory}/runUnitTest
[{{ section('run-unit-test-python-hash-seed-wrapper') }}]
recipe = slapos.recipe.template
inline =
#!/bin/sh
{% if slapparameter_dict.get('python-hash-seed') %}
PYTHONHASHSEED={{ slapparameter_dict['python-hash-seed'] }}
{% endif %}
if [ -z "$PYTHONHASHSEED" ]; then
PYTHONHASHSEED=$(${buildout:bin-directory}/shuf -i 1-1024 -n 1)
echo "Generated PYTHONHASHSEED: $PYTHONHASHSEED"
else
echo "Using PYTHONHASHSEED: $PYTHONHASHSEED"
fi
export PYTHONHASHSEED
exec ${runUnitTest:wrapper-path} "$@"
output = ${buildout:bin-directory}/runUnitTest.python-hash-seed
[{{ section('run-test-suite-userhosts-wrapper') }}] [{{ section('run-test-suite-userhosts-wrapper') }}]
<= userhosts-wrapper-base <= userhosts-wrapper-base
wrapped-command-line = ${runTestSuite:wrapper-path} wrapped-command-line = ${runTestSuite:wrapper-path}
......
...@@ -137,7 +137,7 @@ eggs = ...@@ -137,7 +137,7 @@ eggs =
[versions] [versions]
setuptools = 44.1.1 setuptools = 44.1.1
# Use SlapOS patched zc.buildout # Use SlapOS patched zc.buildout
zc.buildout = 2.7.1+slapos019 zc.buildout = 2.7.1+slapos020
# Use SlapOS patched zc.recipe.egg (zc.recipe.egg 2.x is for Buildout 2) # Use SlapOS patched zc.recipe.egg (zc.recipe.egg 2.x is for Buildout 2)
zc.recipe.egg = 2.0.3+slapos003 zc.recipe.egg = 2.0.3+slapos003
...@@ -194,6 +194,7 @@ Flask = 3.0.0:whl ...@@ -194,6 +194,7 @@ Flask = 3.0.0:whl
frozenlist = 1.4.0:whl frozenlist = 1.4.0:whl
funcsigs = 1.0.2 funcsigs = 1.0.2
functools32 = 3.2.3.post2 functools32 = 3.2.3.post2
future = 0.18.3
gevent = 23.9.1 gevent = 23.9.1
geventmp = 0.0.1 geventmp = 0.0.1
gitdb = 4.0.10 gitdb = 4.0.10
...@@ -295,7 +296,7 @@ random2 = 1.0.1 ...@@ -295,7 +296,7 @@ random2 = 1.0.1
regex = 2020.9.27 regex = 2020.9.27
requests = 2.31.0 requests = 2.31.0
rpdb = 0.1.5 rpdb = 0.1.5
rubygemsrecipe = 0.4.3 rubygemsrecipe = 0.4.4
scandir = 1.10.0 scandir = 1.10.0
scikit-learn = 0.20.4 scikit-learn = 0.20.4
seaborn = 0.7.1 seaborn = 0.7.1
...@@ -311,8 +312,8 @@ slapos.core = 1.11.0 ...@@ -311,8 +312,8 @@ slapos.core = 1.11.0
slapos.extension.shared = 1.0 slapos.extension.shared = 1.0
slapos.libnetworkcache = 0.25 slapos.libnetworkcache = 0.25
slapos.rebootstrap = 4.5 slapos.rebootstrap = 4.5
slapos.recipe.build = 0.56 slapos.recipe.build = 0.57
slapos.recipe.cmmi = 0.19 slapos.recipe.cmmi = 0.20
slapos.recipe.template = 5.1 slapos.recipe.template = 5.1
slapos.toolbox = 0.142 slapos.toolbox = 0.142
smmap = 5.0.0 smmap = 5.0.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