diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4ef881b9cf542860d8c92d2e90abccbb07d7eff
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,2 @@
+include:
+ - local: '/Reservoir DMS Suite/open-etp-server/.gitlab-ci.yml'
\ No newline at end of file
diff --git a/Reservoir DMS Suite/README.md b/Reservoir DMS Suite/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bb4809be2af550cf1493bf679110295b8b33eea5
--- /dev/null
+++ b/Reservoir DMS Suite/README.md
@@ -0,0 +1,14 @@
+# Reservoir Domain Data Management Service Suite
+
+The Reservoir Domain Data Management Service Suite (Reservoir-DDMS) is a group of services that provides storage for associated with seismic and well interpretation, structural modeling, geological modeling and reservoir modeling including reservoir simulation input.
+
+
+
+The main service [open-etp-server](open-etp-server/README.md), provides storage server on top of a POSTGRESQL compatible database, it is written in C++ and exposes the content using the Energistics Transfer Protocol API, allowing the efficient discovery and transfer of both metadata and binary data. Part of the server can also be used as as simple client to manipulate data partition, import and export RESQML files.
+
+Other services are participating in the integration of the main server with the OSDU ecosystem or providing alternative outside access:
+
+- A typescript client library (Being finalized), allows nodejs application to communicate with the server, and expose a REST API
+
+
+
diff --git a/Reservoir DMS Suite/img/RDDMS-Components.svg b/Reservoir DMS Suite/img/RDDMS-Components.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8546d101efafb3d2cbe11d817d132643fa8ed794
--- /dev/null
+++ b/Reservoir DMS Suite/img/RDDMS-Components.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/.dockerignore b/Reservoir DMS Suite/open-etp-server/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..e741e6d59dc3900dc877497de80688c39a19407c
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/.dockerignore
@@ -0,0 +1,8 @@
+# Exclude everything
+**
+
+# Allow using dist for runtime image
+!/build/using_docker/dist/**
+
+# allow using scripts for build
+!/scripts/**
diff --git a/Reservoir DMS Suite/open-etp-server/.gitignore b/Reservoir DMS Suite/open-etp-server/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7b5ba340b295145336ff4b16508757d70983713a
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/.gitignore
@@ -0,0 +1,14 @@
+build/**
+Testing/Temporary/**
+
+*~
+*.swp
+*.pyo
+*.pyc
+
+# IDE folders
+/.vscode/*
+!/.vscode/tasks.json
+!/.vscode/launch.json
+/*.kdev4
+/.idea
diff --git a/Reservoir DMS Suite/open-etp-server/.gitlab-ci.yml b/Reservoir DMS Suite/open-etp-server/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9e2b071c48b1ed12eca650f33b6f9b8c4f49da5a
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/.gitlab-ci.yml
@@ -0,0 +1,136 @@
+stages:
+ - build
+ - package
+ - test
+ - deploy
+
+
+# .pre stage: running pipeline setup stuff
+# Docker Image for build creation using docker-compose
+DC-Build-stage:
+ stage: .pre
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - docker info
+ - echo "Running docker-compose build"
+ - docker-compose build
+
+
+# Actually build OpenTPServer
+OES-Build-stage:
+ stage: build
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Building binaries for OpenETPServer"
+ - echo "Give rights to posgres user to run init script:"
+ - chmod o+rwx -R scripts/init-db
+ - export DOCKER_GATEWAY_HOST=172.17.0.1
+ - echo "Looking for host path for bind mounts"
+ - export CONTAINER_ID=$(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build")
+ - export MOUNT_NAME=$(docker inspect $CONTAINER_ID -f "{{ range .Mounts }}{{ if eq .Destination \"/builds\" }}{{ .Source }}{{end}}{{end}}")
+ - export SOURCE_HOST_DIR=$MOUNT_NAME/osdu/platform/domain-data-mgmt-services/reservoir/reservoir-ddms/Reservoir\ DMS\ Suite/open-etp-server
+ # --force recreate to ensure containers are new ones
+ # --renew-anon-volumes to ensure no previous data is used
+ # --exit-code-from to exit as soon as build is finished and use it exit code (failure reporting)
+ - docker-compose up --force-recreate --renew-anon-volumes --exit-code-from open_etp_server_build
+ cache:
+ # First cache for build speed up of all stuff!
+ key: build-cache
+ paths:
+ - Reservoir\ DMS\ Suite/open-etp-server/build/
+ # Use cache from previous runs and push it to update it
+ policy: pull-push
+ # Generate a GitLab artifact of code coverage repots.
+ coverage: /^\s*lines:\s*\d+.\d+\%/
+ artifacts:
+ name: openETPServer_coverage
+ reports:
+ cobertura: Reservoir\ DMS\ Suite/open-etp-server/build/using_docker_debug/gcovr/sonarqube/coverage.xml
+ paths:
+ - Reservoir\ DMS\ Suite/open-etp-server/build/using_docker_debug/gcovr/
+
+
+# Generate packages
+OES-Packaging-stage:
+ stage: package
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Looking for host path for bind mounts"
+ - export CONTAINER_ID=$(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build")
+ - export MOUNT_NAME=$(docker inspect $CONTAINER_ID -f "{{ range .Mounts }}{{ if eq .Destination \"/builds\" }}{{ .Source }}{{end}}{{end}}")
+ - export SOURCE_HOST_DIR=$MOUNT_NAME/osdu/platform/domain-data-mgmt-services/reservoir/reservoir-ddms/Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Create a runtime image"
+ - docker-compose build open_etp_server_runtime
+ cache:
+ key: build-cache
+ paths:
+ - Reservoir\ DMS\ Suite/open-etp-server/build/
+ # Only pull cache, no need to update afterward
+ policy: pull
+ # Generate a GitLab artifact of the package.
+ artifacts:
+ name: openETPServer
+ paths:
+ - Reservoir\ DMS\ Suite/open-etp-server/build/using_docker/dist.tgz
+
+
+# Run tests
+OES-Tests-stage:
+ stage: test
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Looking for host path for bind mounts"
+ - export CONTAINER_ID=$(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build")
+ - export MOUNT_NAME=$(docker inspect $CONTAINER_ID -f "{{ range .Mounts }}{{ if eq .Destination \"/builds\" }}{{ .Source }}{{end}}{{end}}")
+ - export SOURCE_HOST_DIR=$MOUNT_NAME/osdu/platform/domain-data-mgmt-services/reservoir/reservoir-ddms/Reservoir\ DMS\ Suite/open-etp-server
+ - echo " --- See Build stage for Unit Tests ---"
+ - echo "Checking runtime image is running"
+ - docker-compose run open_etp_server_runtime
+
+
+# Deploy
+OES-Deployment-stage:
+ stage: deploy
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Looking for host path for bind mounts"
+ - export CONTAINER_ID=$(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build")
+ - export MOUNT_NAME=$(docker inspect $CONTAINER_ID -f "{{ range .Mounts }}{{ if eq .Destination \"/builds\" }}{{ .Source }}{{end}}{{end}}")
+ - export SOURCE_HOST_DIR=$MOUNT_NAME/osdu/platform/domain-data-mgmt-services/reservoir/reservoir-ddms/Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Running deployment"
+ - echo " --- NOT YET IMPLEMENTED ---"
+ - echo "Listing files:"
+ - ls -al build/using_docker/dist
+ cache:
+ key: build-cache
+ paths:
+ - Reservoir\ DMS\ Suite/open-etp-server/build/
+ # Only pull cache, no need to update afterward
+ policy: pull
+
+
+# .post stage: running pipeline stopping stuff
+# Docker Image for build creation using docker-compose
+DC-Poweroff-stage:
+ stage: .post
+ image: docker/compose:1.29.2
+ tags: ['osdu-large']
+ script:
+ - cd Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Looking for host path for bind mounts"
+ - export CONTAINER_ID=$(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build")
+ - export MOUNT_NAME=$(docker inspect $CONTAINER_ID -f "{{ range .Mounts }}{{ if eq .Destination \"/builds\" }}{{ .Source }}{{end}}{{end}}")
+ - export SOURCE_HOST_DIR=$MOUNT_NAME/osdu/platform/domain-data-mgmt-services/reservoir/reservoir-ddms/Reservoir\ DMS\ Suite/open-etp-server
+ - echo "Running docker-compose down"
+ - docker-compose down
+ allow_failure: true
diff --git a/Reservoir DMS Suite/open-etp-server/.vscode/launch.json b/Reservoir DMS Suite/open-etp-server/.vscode/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..2e402d9a09ead193adcc4f3195fad8140d006e42
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/.vscode/launch.json
@@ -0,0 +1,140 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "(gdb) Linux Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/build/using_docker_debug/openETPServer",
+ "args": ["server","--start","-N","none","-M", "2GB"],
+ "stopAtEntry": true,
+ "cwd": "${workspaceFolder}",
+ "environment": [],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+ {
+ "name": "(gdb) Docker Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/build/using_docker_debug/openETPServer",
+ "args": ["server","--start","-N","none","-M", "2GB"],
+ "stopAtEntry": true,
+ "cwd": "/source",
+ "environment": [],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "sourceFileMap":{
+ "/source":"${workspaceFolder}"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+
+ {
+ "name": "(gdb) Docker Attach",
+ "type": "cppdbg",
+ "request": "attach",
+ "program": "${workspaceFolder}/build/using_docker/openETPServer",
+ "processId": "${command:pickProcess}",
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "sourceFileMap":{
+ "/source":"${workspaceFolder}"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+ {
+ "name": "(gdb) Launch Tests EML",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/build/using_docker_debug/tests/run_test_eml",
+ "args":[],
+ "stopAtEntry": true,
+ "cwd": "/source",
+ "environment": [
+ {"name": "UNIT_TESTS_DATA_PATH","value": "${workspaceFolder}/build/using_docker_debug/tests"},
+ ],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "sourceFileMap":{
+ "/source":"${workspaceFolder}"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+ {
+ "name": "(gdb) Launch Tests PostgreSQL",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/build/using_docker_debug/tests/run_test_postgresql",
+ "args":[],
+ "stopAtEntry": true,
+ "cwd": "/source",
+ "environment": [
+ {"name": "UNIT_TESTS_DATA_PATH","value": "${workspaceFolder}/build/using_docker_debug/tests"},
+ ],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "sourceFileMap":{
+ "/source":"${workspaceFolder}"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+ {
+ "name": "(gdb) Attach",
+ "type": "cppdbg",
+ "request": "attach",
+ "program": "${workspaceFolder}/build/using_docker/openETPServer",
+ "processId": "${command:pickProcess}",
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "sourceFileMap":{
+ "/source":"${workspaceFolder}"
+ },
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/.vscode/tasks.json b/Reservoir DMS Suite/open-etp-server/.vscode/tasks.json
new file mode 100644
index 0000000000000000000000000000000000000000..be316fde56f079babbb34b0930becbaff9b821db
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/.vscode/tasks.json
@@ -0,0 +1,51 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "shell",
+ "label": "Build release and debug using Docker",
+ "command": "bash",
+ "args": ["scripts/build_inside_docker.sh"],
+ "problemMatcher": {
+ "base": "$gcc",
+ "owner": "cpp",
+ "fileLocation": ["relative", "${workspaceRoot}"],
+ "pattern": {
+ "regexp": "^\\.\\.\/\\.\\.\/(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ },
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ },
+ {
+ "type": "shell",
+ "label": "Build debug in system",
+ "command": "bash",
+ "args": ["scripts/build.sh", "--debug", "build/using_docker_debug"],
+ "problemMatcher": {
+ "base": "$gcc",
+ "owner": "cpp",
+ "fileLocation": ["relative", "${workspaceRoot}"],
+ "pattern": {
+ "regexp": "^\\.\\.\/\\.\\.\/(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ },
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/.gitignore b/Reservoir DMS Suite/open-etp-server/3rdParties/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..72e8ffc0db8aad71a934dd11e5968bd5109e54b4
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/3rdParties/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/AvroCPP/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/AvroCPP/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/Boost/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/Boost/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/Curl/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/Curl/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/HDF5/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/HDF5/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/Nlohmann/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/Nlohmann/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/OpenSSL/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/OpenSSL/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/PostgreSQL/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/PostgreSQL/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/3rdParties/WebSocketPP/.gitkeep b/Reservoir DMS Suite/open-etp-server/3rdParties/WebSocketPP/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Reservoir DMS Suite/open-etp-server/CMakeLists.txt b/Reservoir DMS Suite/open-etp-server/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..224356e6e6c4630de4a9fdf630569e9f7cb15ff8
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/CMakeLists.txt
@@ -0,0 +1,144 @@
+# cmake_minimum_required() also set the newly-introduced policies to NEW
+# See: https://cmake.org/cmake/help/latest/manual/cmake-policies.7.html
+cmake_minimum_required(VERSION 3.15)
+
+if( POLICY CMP0074 )
+ # New policy for detecting packages libraries was introduced in CMake 3.12
+ # See https://cmake.org/cmake/help/v3.12/policy/CMP0074.html
+ cmake_policy(SET CMP0074 "NEW")
+endif()
+
+if( POLICY CMP0077 )
+ # New policy for option() behavior is to honor already existing cache entry
+ # See https://cmake.org/cmake/help/v3.13/policy/CMP0077.html
+ cmake_policy(SET CMP0077 "NEW")
+endif()
+
+# Organize projects into folders
+# See: http://athile.net/library/blog/?p=288
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+
+
+# Main Project for the whole repo
+project( OpenETPServer )
+
+
+################################################################################
+# Options for tuning build rules
+
+# Location for custom 3rd parties:
+set( OPEN_ETP_SERVER_3RDPARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdParties/" CACHE PATH "Directory where to find 3rd parties libraries" )
+# Choose ABI policy
+# see https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
+option( OLD_CXX11_ABI "Force old ABI policy for glibcxx" )
+
+option( CODE_COVERAGE "Enable code coverage" )
+
+################################################################################
+# Define C/CXX standards
+
+# Defines the C++ Standard to use (for GCC, equivalent to -std=c++17)
+set( CMAKE_CXX_STANDARD 17 )
+
+# Defines the C Standard to use (for GCC, equivalent to -std=c90 or -ansi)
+set( CMAKE_C_STANDARD 90 )
+
+set( CMAKE_CXX_STANDARD_REQUIRED ON )
+# CMake uses extended variants of language dialects by default, such as -std=gnu++14 instead of -std=c++14
+# Disable that.
+set( CMAKE_CXX_EXTENSIONS OFF )
+
+
+################################################################################
+# Select the output directries for binaries
+
+# Generate all binaries at the root of the build tree:
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+set( RUNTIME_OUTPUT_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+# put libraries in a dedicated "lib" directory
+if( WIN32 )
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+ set( LIBRARY_OUTPUT_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
+ set( ARCHIVE_OUTPUT_PATH ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
+else()
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
+ set( LIBRARY_OUTPUT_PATH ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
+endif()
+
+
+set( HELPER_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake" CACHE INTERNAL "Where to find CMake helper scripts." FORCE )
+# Base definitions for 3rd parties:
+include(${HELPER_LIBRARY_DIR}/3rd_parties.cmake)
+
+
+################################################################################
+# OS/Compiler options:
+if( UNIX )
+ if( CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ add_definitions(
+ "-fdiagnostics-color=always" # To use color in messages with Ninja
+ "-pipe" # Use pipes instead of creating temporary files (improves performance)
+ "-Wall" "-Wextra" # Output warnings
+ )
+ add_definitions( -fPIC )
+
+ if( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "7" AND OLD_CXX11_ABI)
+ add_definitions("-D_GLIBCXX_USE_CXX11_ABI=0")
+ endif()
+
+ # Ensure GOLD linker is used
+ if( NOT CMAKE_SHARED_LINKER_FLAGS MATCHES "-fuse-ld=gold")
+ set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold" )
+ set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold" )
+ endif()
+
+ # Do not allow unresolved symbols for neither library nor executable.
+ if( NOT CMAKE_SHARED_LINKER_FLAGS MATCHES "--no-undefined")
+ set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" )
+ set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined" )
+ endif()
+
+ # Ease GDB's life for faster debugging
+ if( NOT CMAKE_SHARED_LINKER_FLAGS MATCHES "--gdb-index")
+ set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gdb-index" )
+ set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gdb-index" )
+ endif()
+
+ # enable code covering
+ if( CODE_COVERAGE )
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
+ endif()
+ endif()
+else()
+ add_compile_definitions(NOMINMAX _WIN32_WINNT=0x0601)
+endif()
+
+
+################################################################################
+# Enable testing via CTest for the project
+enable_testing()
+
+
+################################################################################
+# Custom definitions for project
+add_definitions(
+ -DOES_EML_AUTH_NO_CRYPTO
+ # By commenting next line lots of "not supported in this build" issues in UT were resolved:
+ #-DOES_EML_ETP12_NO_AVRO_JSON
+ -DOES_CORE_UTILS_AVOID_BOOST
+)
+
+IF(CMAKE_BUILD_TYPE MATCHES "Debug")
+ ADD_DEFINITIONS(-DOES_DEBUG_BUILD)
+ENDIF()
+
+set( USE_POSTGRESQL_ETP ON )
+if(USE_POSTGRESQL_ETP)
+ ADD_DEFINITIONS(-DETP_POSTGRESQL)
+endif()
+
+add_subdirectory(src)
diff --git a/Reservoir DMS Suite/open-etp-server/Dockerfile.build b/Reservoir DMS Suite/open-etp-server/Dockerfile.build
new file mode 100644
index 0000000000000000000000000000000000000000..6931e93cb3b1be96b6926457f5605d5db19db752
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/Dockerfile.build
@@ -0,0 +1,82 @@
+FROM ubuntu:21.04
+
+ARG VERSION=0.1
+
+LABEL version="${VERSION}"
+LABEL vendor="OSDU Reservoir DDMS"
+LABEL description="Open ETP Server Building Image"
+
+VOLUME \
+ # Source directory for OpenETPServer
+ /source
+
+ARG BOOST_VER="1.74*"
+ARG CURL_VER="7.74*"
+ARG POSTGRESQL_VER="13"
+
+RUN apt-get update && \
+# Define DEBIAN_FRONTEND to allow non interactive installation of some packages
+ DEBIAN_FRONTEND=noninteractive \
+ apt-get install -y --no-install-recommends \
+ ca-certificates \
+# Build system packages
+ # build essential brings too much stuff
+ #build-essential=12.8* \
+ # This subset is enough to build
+ libc6-dev=2.33* gcc=4:10.3* g++=4:10.3* \
+ ninja-build=1.10* \
+ libgomp1=11* \
+ cmake=3.* \
+ gdb \
+# 3rd parties
+ # For downloading AvroCPP package
+ wget \
+ # For AvroCPP runtime
+ libboost-iostreams-dev=${BOOST_VER} \
+ libboost-program-options-dev=${BOOST_VER} \
+ libsnappy-dev=1.1* \
+ \
+ libboost-filesystem-dev=${BOOST_VER} \
+ libboost-regex-dev=${BOOST_VER} \
+ libboost-system-dev=${BOOST_VER} \
+ libboost-thread-dev=${BOOST_VER} \
+ libboost-test-dev=${BOOST_VER} \
+ \
+ libcurl4-openssl-dev=${CURL_VER} \
+ \
+# doxygen=1.8* \
+ \
+ libhdf5-dev=1.10.6* \
+ \
+ nlohmann-json3-dev=3.9* \
+ \
+ libssl-dev=1.1.1* \
+ openssl=1.1.1* \
+ \
+ libwebsocketpp-dev=0.8* \
+ \
+# PostgreSQL required libraries
+ postgresql-server-dev-${POSTGRESQL_VER} \
+ \
+# For debugging purposes
+# strace=5.5* \
+ \
+# For code coverage
+ gcovr \
+ \
+# For the beauty of the output
+ figlet \
+ \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Unfortunately Avrocpp is not available as is (avro-c is but not our need)
+ARG AVROCPP_VER="1.10.2"
+COPY scripts/build_and_install_avrocpp.sh /tmp/avro.sh
+RUN /tmp/avro.sh && rm /tmp/avro.sh
+
+####################################################
+
+WORKDIR /source
+EXPOSE 9002
+
+CMD [ "bash", "-c", "scripts/build_inside_docker.sh" ]
diff --git a/Reservoir DMS Suite/open-etp-server/Dockerfile.runtime b/Reservoir DMS Suite/open-etp-server/Dockerfile.runtime
new file mode 100644
index 0000000000000000000000000000000000000000..5609fd38d8c9dc8ad625a6dd0a7b72af25a81c2f
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/Dockerfile.runtime
@@ -0,0 +1,13 @@
+FROM ubuntu:21.04
+
+ARG VERSION=0.1
+
+LABEL version="${VERSION}"
+LABEL vendor="OSDU Reservoir DDMS"
+LABEL description="Open ETP Server Runtime Image"
+
+COPY ./build/using_docker/dist/ /usr/
+
+EXPOSE 9002
+
+CMD [ "openETPServer", "server", "-h" ]
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/LICENSE b/Reservoir DMS Suite/open-etp-server/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..5266644a0b9f19dfd7ec746e38bec66c782d3937
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/LICENSE
@@ -0,0 +1,232 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2021 Open Subsurface Data Universe Forum
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+==========================================================
+
+ Use of Third Parties
+----------------------------------------------------------
+ FESAPI
+
+This software uses part of [FESAPI] (https://github.com/F2I-Consulting/fesapi), by F2I consulting which is available under Apache License, License 2.0.
+
+----------------------------------------------------------
+ Minizip
+
+This software uses part of minizip by Gilles Vollant
+Mini zip and unzip based on zlib
+Includes Zip64 support by Mathias Svensson
+See http://www.winimage.com/zLibDll/minizip.html
+
+----------------------------------------------------------
+ MoodyCamel
+
+This software uses part of [MoodyCamel] (https://github.com/cameron314/concurrentqueue), which is available under a [Simplified BSD LIcense](https://github.com/cameron314/concurrentqueue/blob/master/LICENSE.md)
+
+----------------------------------------------------------
+ SQLITE third party
+
+Some algorithms are based on [SQLITE] which is publicly (https://sqlite.org/copyright.html) available.
+
+----------------------------------------------------------
+ C++ Formatting library
+
+This software use part of [Formatting library](https://github.com/fmtlib/fmt) for C++ by Victor Zverovich, publicly available under [License](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/README.md b/Reservoir DMS Suite/open-etp-server/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bad10b15fbc6406c9a60607fa228ed67ad1bb281
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/README.md
@@ -0,0 +1,180 @@
+# Reservoir DDMS - OpenETPServer
+
+OpenETPServer is the open source implementation of the Reservoir Domain Data Management Services (Reservoir-DDMS) which is one of the backend services part of Open Subsurface Data Universe (OSDU) software ecosystem. OpenETPServer is a single, containerized service written in C++ which stores reservoir data in RESQML format inside a PostgreSQL database.
+
+The service API is based on [Energistics Transfer Protocol version 1.2](https://public.3.basecamp.com/p/pNHktDcKVBGENgAbTu57YAeU), and allows the exchange of data based on [RESQML version 2.0.1+](http://docs.energistics.org/#RESQML/RESQML_TOPICS/RESQML-000-000-titlepage.html) format.
+
+## Build
+
+### Requirements
+
+- A Linux based system (or Windows Subsystem for Linux)
+- Docker (which now comes with docker-compose)
+- For older Docker versions: docker-compose
+
+### Build process
+
+The solution is based on two docker images:
+
+- open_etp_server_build is used to build the code both in debug and release, and run the unit tests
+- open_etp_server_runtime is an image created with the result from open_etp_server_build and is packaging a server ready to run
+
+The solution is also using a postgres docker image, for the unit tests and in the default runtime mode.
+To use an alternative postgres server you need to provide a postgres connection string in the variable POSTGRESQL_CONN_STRING with a format like "host=172.0.0.1 port=5432 dbname=postkv user=tester password=tester"
+
+### Creating Docker Image (Golden Path)
+
+To run docker container to build OpenETPServer and run unit tests:
+
+```bash
+docker-compose up --abort-on-container-exit
+```
+
+Note that --abort-on-container-exit is used to remove the postgresql container required by unit tests
+
+To create and test the runtime image
+
+```bash
+docker-compose up --abort-on-container-exit open_etp_server_runtime
+```
+
+This should display the help information about the ETP server options.
+
+In order to remove the running postgresql container:
+
+```bash
+docker-compose down
+```
+
+### Using custom build
+
+If you prefer you can build inside a more conventional build environment.
+To do so, simply create a build directory (for instance build/custom_build) and run
+```bash
+cmake SOURCE_DIRECTORY -GNinja
+ninja
+```
+
+This can be adapted for your needs with normal CMake options.
+
+### Custom third-party libraries
+
+It is possible to use custom 3rd parties instead of system ones by filling the `3rdParties` directory.
+It is also possible to keep 3rd parties inside a dedicated directory and provide it using the following option:
+```bash
+cmake SOURCE_DIR -GNinja -DOPEN_ETP_SERVER_3RDPARTY_DIR="PATH_TO_3RDPARTIES"
+```
+
+The provided directory must have the same structure than the `3rdParties` directory inside source tree.
+
+
+## Usage
+
+### Starting an ETP API server
+
+The default configuration use the open_etp_postgresql container(see docker-compose.yml) from which the open_etp_server_build container depends, unless POSTGRESQL_CONN_STRING is redefining this behavior, see [Actual Build](#actualBuild).
+The PostgreSQL "admin" schema is automatically created, checked or upgraded when the openETPServer is
+starting. It is also possible to just do the initialization part using:
+
+```bash
+# Initialize the database
+docker-compose run open_etp_server_runtime openETPServer server --init
+```
+
+To start the etp server on its default port 9002, and using its default authentication mode:
+(One user allowed with name=foo password=bar):
+
+```bash
+# Start the ETP server
+docker-compose run -p 9002:9002 open_etp_server_runtime openETPServer server --start
+
+# Server with default authentication with one credential: User: foo, Passwd: bar
+
+# Known issue: Since docker-compose does not wait for postgres to be ready, before starting the
+# server, you may need to use this command twice
+
+# List all server options available
+docker-compose up open_etp_server_runtime
+```
+
+### Interacting with the server data spaces
+
+The server executable is also implementing some client-side functionalities that allows content management.
+
+Once a container open_etp_server_runtime is running in server mode, we need to retrieve its name in order to fulfill the ETP server URL connection option (-S).
+You can find its name using the following command.
+
+```bash
+# Find the name of the container running the ETP server
+docker container list
+
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
+0c66a13d35e9 open-etp-server_open_etp_server_build "/source/build/using…" About a minute ago Up About a minute 0.0.0.0:9002->9002/tcp, :::9002->9002/tcp open-etp-server_open_etp_server_build_run_6ccce8b7967e
+```
+
+The ETP server can be accessed on your local machine using the full container name: **ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002**
+
+Storage is organized in data spaces (See Etp 1.2 documentation), each data space defines a scope for unique identifiers of contained resources.
+Consequently, two identical data can be ingested inside the solution without conflict when assigned to different data spaces.
+Although ETP supports the concept of default data space, there is no default space in the RDDMS, and currently the RDDMS enforces that data space paths are composed of two parts, example 'project/study'
+
+The following script creates a new data space on the server, imports a RESQML file, checks its content and delete it.
+Note that the EPC document and its HDF5 companion storing binary data must already be copied in the ./data directory so that it can be seen from inside the container.
+
+```bash
+# Create a new data space named 'demo/Volve'
+docker-compose run open_etp_server_runtime openETPServer space -S ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002 -u foo -p bar --new -s demo/Volve
+
+# Make a list of all data spaces on the server
+docker-compose run open_etp_server_runtime openETPServer space -S ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002 -u foo -p bar -l
+
+# Import a RESQML file in the Volve data space
+# This assumes that the EPC and HDF5 companion are available in ./data directory
+docker-compose run open_etp_server_build openETPServer space -S ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002 -u foo -p bar -s demo/Volve --import-epc /data/Volve_Demo_Reservoir_Grid_Depth.epc
+
+# Check the content
+docker-compose run open_etp_server_build openETPServer space -S ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002 -u foo -p bar -s demo/Volve --stats
+
+# Delete an existing data space named 'demo/Volve'
+docker-compose run open_etp_server_build openETPServer space --delete -S ws://open-etp-server_open_etp_server_build_run_6ccce8b7967e:9002 -s demo/Volve
+```
+
+## Additional Notes for developers
+
+## Developing inside the build container using VSCode
+
+Required Visual Studio extension:
+
+- C++: ms-vscode.cpptools
+- Remote container support: ms-vscode-remote.remote-containers
+
+Create the container image using the instruction in [Actual Build](#actualBuild) section
+
+In order to debug you can use:
+
+```bash
+docker-compose run open_etp_server_build bash
+```
+
+It will launch a batch in the build container, allowing to interact with it, or attach VSCode to the container.
+
+Open the open-etp-server directory in Visual Studio Code(View->Command Palette Ctrl+P). Then choose: Remote container/Attach to running container...
+Sometimes you may need to reopen the folder in /source
+The first time in the container, you may have to (re)enabled the C++ extension locally.
+This create a new Visual Studio Code window that you can use to build (Ctrl B) and to debug (Ctrl D)
+
+### Selecting PostgreSQL Server
+
+docker-compose will also start a container of a PostgreSQL instance.
+
+Running:
+
+```bash
+docker-compose run open_etp_server_build bash
+```
+
+will use the docker postgres image so that PostgreSQL dependent unit test can run.
+
+It is also possible to work with a pre-existing PostgreSQL server and not using the container.
+You simply need to update the POSTGRESQL_CONN_STRING in the launch.json for the Unit tests and
+in the docker-compose.yml for the server usage.
diff --git a/Reservoir DMS Suite/open-etp-server/azure-pipelines.yml b/Reservoir DMS Suite/open-etp-server/azure-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3192238358c9ad03d6a3e3a3db87816f4593277c
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/azure-pipelines.yml
@@ -0,0 +1,118 @@
+# Starter pipeline
+# Start with a minimal pipeline that you can customize to build and deploy your code.
+# Add steps that build, run tests, deploy, and more:
+# https://aka.ms/yaml
+
+name: Fy-$(Build.DefinitionName)-$(Build.SourceBranchName)-$(date:yyyyMMdd)$(rev:.r)
+
+trigger:
+ - main
+ - master
+
+variables:
+ - name: ForceClean
+ # Must define a pipeline variable named FORCE_CLEAN exposed to user for behavior modification
+ value: $(FORCE_CLEAN)
+
+ - name: isMain
+ value: ${{ or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}
+
+ - name: Node.Version
+ value: 14.x
+
+ # defines secret vars
+ - group: general-build-vars
+
+ # Define Registry Variables
+ - name: RegistryURL
+ value: "portalvsts.azurecr.io"
+ - name: RegistryServiceConnection
+ value: "portalvsts"
+
+ - name: RepositoryName
+ value: open-etp-server
+
+ # Images names:
+ - name: ComposeServiceNameBuild
+ value: "open_etp_server_build"
+ - name: ComposeServiceNameRuntime
+ value: "open_etp_server_runtime"
+
+pool:
+ name: pdgmDockerLinux
+ # Temporarilly set to agent 4 for stability
+ demands: Agent.Name -equals af-ncy-ub20-4
+
+steps:
+## Do not clean the workspace unless user chose it.
+- checkout: self
+ clean: $(ForceClean)
+## Clean workspace EXCEPT build directory
+- script: |
+ git clean -fdx -e build/
+
+## Build Image
+- task: DockerCompose@0
+ displayName: "Build openETPServer build image"
+ inputs:
+ containerregistrytype: "Container Registry"
+ dockerRegistryEndpoint: "$(RegistryServiceConnection)"
+ dockerComposeFile: "docker-compose.yml"
+ action: "Run a Docker Compose command"
+ dockerComposeCommand: "build"
+ arguments: "$(ComposeServiceNameBuild)"
+- task: DockerCompose@0
+ displayName: "Compile openETPServer executable"
+ inputs:
+ containerregistrytype: "Container Registry"
+ dockerRegistryEndpoint: "$(RegistryServiceConnection)"
+ dockerComposeFile: "docker-compose.yml"
+ action: "Run a Docker Compose command"
+ dockerComposeCommand: "up"
+ arguments: "$(ComposeServiceNameBuild)"
+
+## Runtime Image
+- task: DockerCompose@0
+ displayName: "Build openETPServer runtime image"
+ inputs:
+ containerregistrytype: "Container Registry"
+ dockerRegistryEndpoint: "$(RegistryServiceConnection)"
+ dockerComposeFile: "docker-compose.yml"
+ action: "Run a Docker Compose command"
+ dockerComposeCommand: "build"
+ arguments: "$(ComposeServiceNameRuntime)"
+- task: DockerCompose@0
+ displayName: "Verify openETPServer runtime image"
+ inputs:
+ containerregistrytype: "Container Registry"
+ dockerRegistryEndpoint: "$(RegistryServiceConnection)"
+ dockerComposeFile: "docker-compose.yml"
+ action: "Run a Docker Compose command"
+ dockerComposeCommand: "run"
+ arguments: "$(ComposeServiceNameRuntime)"
+
+- task: Docker@2
+ displayName: "Tag openETPServer runtime image: $(Build.BuildNumber)"
+ inputs:
+ command: "tag"
+ arguments: "$(RegistryURL)/$(ComposeServiceNameRuntime) $(RegistryURL)/$(RepositoryName):$(Build.BuildNumber)"
+
+- task: Docker@2
+ displayName: "Tag openETPServer runtime image: latest"
+ inputs:
+ command: "tag"
+ arguments: "$(RegistryURL)/$(ComposeServiceNameRuntime) $(RegistryURL)/$(RepositoryName):latest"
+ condition: eq(variables.isMain, true)
+
+- task: Docker@2
+ displayName: "Push openETPServer runtime image"
+ inputs:
+ containerRegistry: "$(RegistryServiceConnection)"
+ repository: "$(RepositoryName)"
+ command: "push"
+ ${{ if eq(variables.isMain, true) }}:
+ tags: |
+ $(Build.BuildNumber)
+ latest
+ ${{ if eq(variables.isMain, false) }}:
+ tags: $(Build.BuildNumber)
diff --git a/Reservoir DMS Suite/open-etp-server/cmake/3rd_parties.cmake b/Reservoir DMS Suite/open-etp-server/cmake/3rd_parties.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..aa4cded75c6745a2c8e84e84bc4a448392d72cab
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/cmake/3rd_parties.cmake
@@ -0,0 +1,311 @@
+
+# 3rd parties:
+# Comment following line for getting more information when 3rd party is searched
+set( THIRDPARTY_VERBOSITY QUIET )
+
+# 3rd parties minimal version required:
+set( MINIMAL_AVROCPP_VERSION 1.9.2 )
+set( MINIMAL_BOOST_VERSION 1.71.0 )
+set( MINIMAL_CURL_VERSION 7.68.0 )
+set( MINIMAL_HDF5_VERSION 1.10.4 )
+set( MINIMAL_OPENSSL_VERSION 1.0.2 )
+set( MINIMAL_POSTGRESQL_VERSION 12.7 )
+set( MINIMAL_ZLIB_VERSION 1.2.11 )
+
+# Enable Open MP in code.
+macro( USE_OPENMP )
+ # Allow OpenMP usage:
+ find_package(OpenMP REQUIRED)
+ if (OPENMP_FOUND)
+ if( NOT "${CMAKE_C_FLAGS}" MATCHES "${OpenMP_C_FLAGS}" )
+ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
+ endif()
+ if( NOT "${CMAKE_CXX_FLAGS}" MATCHES "${OpenMP_CXX_FLAGS}" )
+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
+ endif()
+
+ # Uniformize 3rd parties variables
+ set(OPENMP_LIBRARIES "${OpenMP_CXX_LIBRARIES}")
+ endif()
+endmacro()
+
+
+# Macro for using AvroCPP inside a binary
+macro( USE_AVROCPP )
+ # AvroCPP is not part of standard linux paquages.
+ # It needs to be build and installed.
+
+ # Search AvroCPP includes on system:
+ find_path(AVROCPP_INCLUDE_DIRS
+ NAMES
+ avro/Encoder.hh
+ HINTS # Use 'HINTS' instead of 'PATHS' to precede system search
+ "${OPEN_ETP_SERVER_3RDPARTY_DIR}/AvroCPP/include"
+ )
+ if( NOT AVROCPP_INCLUDE_DIRS )
+ message( FATAL_ERROR
+ "AvroCPP include directory not found!"
+ " Try to build and install it,"
+ " or use a pre-build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/AvroCPP"
+ )
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${AVROCPP_INCLUDE_DIRS}" )
+
+ # Search libs in the system
+ find_library(AVROCPP_LIBRARIES
+ NAMES
+ avrocpp
+ HINTS # Use 'HINTS' instead of 'PATHS' to precede system search
+ "${OPEN_ETP_SERVER_3RDPARTY_DIR}/AvroCPP/lib" # Local toolkits
+ )
+ if( NOT AVROCPP_LIBRARIES )
+ message( FATAL_ERROR
+ "AvroCPP libraries not found!"
+ " Try to build and install it,"
+ " or use a pre build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/AvroCPP"
+ )
+ endif()
+endmacro()
+
+
+# Macro for using BOOST inside a binary
+macro( USE_BOOST )
+ if( IS_DIRECTORY "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Boost/include" )
+ # From Boost 1.70.0, there can be some cases where boost detection conflicts
+ # due to a cmake-built boost on the system.
+ # So we disable the default Boost CMake detection script.
+ # See https://cmake.org/cmake/help/v3.16/module/FindBoost.html#boost-cmake
+ set(Boost_NO_BOOST_CMAKE TRUE)
+
+ set(BOOST_ROOT "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Boost")
+ set(Boost_NO_SYSTEM_PATH TRUE)
+ else()
+ unset(Boost_NO_BOOST_CMAKE)
+ unset(BOOST_ROOT)
+ set(Boost_NO_SYSTEM_PATH FALSE)
+ endif()
+
+ set(BOOST_MODULES date_time filesystem iostreams program_options regex system thread unit_test_framework)
+ if( WIN32 )
+ list(APPEND BOOST_MODULES random )
+ endif()
+ find_package(
+ Boost ${MINIMAL_BOOST_VERSION}
+ COMPONENTS
+ ${BOOST_MODULES}
+ REQUIRED
+ ${THIRDPARTY_VERBOSITY}
+ )
+
+ if( NOT Boost_FOUND )
+ message( FATAL_ERROR
+ "Boost libraries not found!"
+ " Try to build and install it,"
+ " or use a pre build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/Boost"
+ )
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM ${Boost_INCLUDE_DIRS} )
+
+ # Uniformize 3rd parties variables
+ set(BOOST_LIBRARIES ${Boost_LIBRARIES})
+
+ # Extra Windows specific compilation flags
+ if(WIN32)
+ # Boost.Process 1.71+ fails without windows.h.
+ # WIN32_LEAN_AND_MEAN avoids issues with asio.
+ # https://github.com/boostorg/process/issues/96
+ add_compile_definitions(BOOST_ALL_NO_LIB BOOST_ALL_DYN_LINK BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN)
+ endif()
+endmacro()
+
+
+# Macro for using cURL inside a binary
+macro( USE_CURL )
+ if( IS_DIRECTORY "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Curl/include" )
+ # Use Curl from local 3rd parties:
+ set( CURL_INCLUDE_DIRS "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Curl/include")
+ if( UNIX )
+ set( CURL_LIBRARIES curl)
+ else()
+ set( CURL_LIBRARIES libcurl)
+ endif()
+ link_directories( "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Curl/lib" )
+ else()
+ find_package(
+ CURL ${MINIMAL_CURL_VERSION}
+ REQUIRED
+ ${THIRDPARTY_VERBOSITY}
+ )
+
+ if( NOT CURL_FOUND )
+ message ( FATAL_ERROR "cURL not found!" )
+ endif()
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${CURL_INCLUDE_DIRS}" )
+endmacro()
+
+
+# Macro for using HDF5 inside a binary
+macro( USE_HDF5 )
+ if( IS_DIRECTORY "${OPEN_ETP_SERVER_3RDPARTY_DIR}/HDF5/include" )
+ set(HDF5_ROOT "${OPEN_ETP_SERVER_3RDPARTY_DIR}/HDF5")
+ set(HDF5_NO_FIND_PACKAGE_CONFIG_FILE true)
+
+ set(HDF5_INCLUDE_DIRS "${OPEN_ETP_SERVER_3RDPARTY_DIR}/HDF5/include")
+ set( HDF5_LIBRARIES
+ hdf5_cpp
+ hdf5
+ hdf5_hl
+ )
+ if( UNIX )
+ set( HDF5_LIBRARIES ${HDF5_LIBRARIES} "szip")
+ else()
+ set( HDF5_LIBRARIES ${HDF5_LIBRARIES} "zlib")
+ endif()
+ link_directories("${OPEN_ETP_SERVER_3RDPARTY_DIR}/HDF5/lib/")
+ else()
+ unset(HDF5_ROOT)
+ unset(HDF5_NO_FIND_PACKAGE_CONFIG_FILE)
+ find_package(
+ HDF5 ${MINIMAL_HDF5_VERSION}
+ COMPONENTS
+ CXX C HL
+ ${THIRDPARTY_VERBOSITY}
+ )
+
+ if( NOT HDF5_FOUND )
+ message( FATAL_ERROR
+ "HDF5 library not found!"
+ " Try to build and install it,"
+ " or use a pre build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/HDF5"
+ )
+ endif()
+
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${HDF5_INCLUDE_DIRS}" )
+
+ # As default is to use static libraries,
+ # force HDF5 t be dynamically loaded
+ add_definitions(-DH5_BUILT_AS_DYNAMIC_LIB)
+
+endmacro()
+
+
+macro( USE_NLOHMANN )
+ find_path(NLOHMANN_INCLUDE_DIRS
+ NAMES
+ nlohmann/json.hpp
+ HINTS # Use 'HINTS' instead of 'PATHS' to precede system search
+ "${OPEN_ETP_SERVER_3RDPARTY_DIR}/Nlohmann/include"
+ )
+
+ if( NOT NLOHMANN_INCLUDE_DIRS )
+ message( FATAL_ERROR
+ "Nlohmann library not found!"
+ " Try to install it,"
+ " or use a custom version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/Nlohmann"
+ )
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${NLOHMANN_INCLUDE_DIRS}" )
+
+ # No .so for Header only library
+ set(NLOHMANN_LIBRARIES "")
+endmacro()
+
+
+# Macro for using OpenSSL inside a binary
+macro( USE_OPENSSL )
+ if( IS_DIRECTORY "${OPEN_ETP_SERVER_3RDPARTY_DIR}/OpenSSL/include" )
+ set(OPENSSL_ROOT_DIR "${OPEN_ETP_SERVER_3RDPARTY_DIR}/OpenSSL")
+ else()
+ unset(OPENSSL_ROOT_DIR)
+ endif()
+
+ # Find OpenSSL on system:
+ find_package(
+ OpenSSL ${MINIMAL_OPENSSL_VERSION}
+ REQUIRED
+ ${THIRDPARTY_VERBOSITY}
+ )
+
+ if( NOT OPENSSL_FOUND )
+ message( FATAL_ERROR
+ "OpenSSL library not found!"
+ " Try to build and install it,"
+ " or use a pre build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/OpenSSL"
+ )
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}" )
+endmacro()
+
+
+# Macro for using PostgreSQL inside a binary
+macro( USE_POSTGRESQL )
+ if( IS_DIRECTORY "${OPEN_ETP_SERVER_3RDPARTY_DIR}/PostgreSQL/include" )
+ # Use PostgreSQL from local 3rd parties:
+ set( PostgreSQL_INCLUDE_DIRS "${OPEN_ETP_SERVER_3RDPARTY_DIR}/PostgreSQL/include")
+ if( UNIX )
+ set( PostgreSQL_LIBRARIES pq pgtypes)
+ else()
+ set( PostgreSQL_LIBRARIES libpq libpgtypes)
+ endif()
+ link_directories( "${OPEN_ETP_SERVER_3RDPARTY_DIR}/PostgreSQL/lib" )
+ else()
+ # Find PostgreSQL on system:
+ find_package(
+ PostgreSQL ${MINIMAL_POSTGRESQL_VERSION}
+ REQUIRED
+ ${THIRDPARTY_VERBOSITY}
+ )
+
+ if( NOT PostgreSQL_FOUND )
+ message( FATAL_ERROR
+ "PostgreSQL libraries not found!"
+ " Try to build and install it,"
+ " or use a pre build version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/PostgreSQL"
+ )
+ endif()
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${PostgreSQL_INCLUDE_DIRS}" )
+
+ # Uniformize 3rd parties variables
+ set(POSTGRESQL_LIBRARIES "${PostgreSQL_LIBRARIES}")
+endmacro()
+
+
+macro( USE_WEBSOCKETPP )
+ find_path(WEBSOCKETPP_INCLUDE_DIRS
+ NAMES
+ websocketpp/version.hpp
+ HINTS # Use 'HINTS' instead of 'PATHS' to precede system search
+ "${OPEN_ETP_SERVER_3RDPARTY_DIR}/WebSocketPP/include"
+ )
+
+ if( NOT WEBSOCKETPP_INCLUDE_DIRS )
+ message( FATAL_ERROR
+ "WebSocketPP libraries not found!"
+ " Try to install it,"
+ " or use a custom version in ${OPEN_ETP_SERVER_3RDPARTY_DIR}/WebSocketPP"
+ )
+ endif()
+
+ # Add include library for headers resolving
+ include_directories(SYSTEM "${WEBSOCKETPP_INCLUDE_DIRS}" )
+
+ # No .so for Header only library
+ set(WEBSOCKETPP_LIBRARIES "")
+endmacro()
\ No newline at end of file
diff --git a/Reservoir DMS Suite/open-etp-server/cmake/embed_resource.cmake b/Reservoir DMS Suite/open-etp-server/cmake/embed_resource.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..e2ba65a128b30f0caa75f3948cba9f2a83f6003a
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/cmake/embed_resource.cmake
@@ -0,0 +1,34 @@
+####################################################################################################
+# This function converts any file into C/C++ source code.
+# Example:
+# - input file: data.dat
+# - output file: data.h
+# - variable name declared in output file: DATA
+# - data length: sizeof(DATA)
+# embed_resource("data.dat" "data.h" "DATA")
+####################################################################################################
+
+function(embed_resource resource_file_name source_file_name variable_name)
+
+ set( output_file "${CMAKE_CURRENT_BINARY_DIR}/${source_file_name}")
+ set(input_file "${CMAKE_CURRENT_SOURCE_DIR}/${resource_file_name}")
+
+ # Only generate file if destination does not exists or is older than input file
+ if( "${input_file}" IS_NEWER_THAN "${output_file}" )
+ file(READ ${input_file} hex_content HEX)
+
+ string(REPEAT "[0-9a-f]" 32 column_pattern)
+ string(REGEX REPLACE "(${column_pattern})" "\\1\n" content "${hex_content}")
+
+ string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " content "${content}")
+
+ string(REGEX REPLACE ", $" "" content "${content}")
+
+ set(array_definition "static const unsigned char ${variable_name}[] =\n{\n${content}\n};")
+
+ set(source "// Auto generated file.\n${array_definition}\n")
+
+ file(WRITE "${output_file}" "${source}")
+ endif()
+
+endfunction()
diff --git a/Reservoir DMS Suite/open-etp-server/cmake/extract_dependencies.cmake.in b/Reservoir DMS Suite/open-etp-server/cmake/extract_dependencies.cmake.in
new file mode 100644
index 0000000000000000000000000000000000000000..766791337224520f9e3bacbe10e02ce5f9d916d3
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/cmake/extract_dependencies.cmake.in
@@ -0,0 +1,20 @@
+
+message("Recursively install libraries for run time.")
+execute_process(
+ COMMAND "@CMAKE_SOURCE_DIR@/scripts/extract_dependencies.py"
+ --include-system-libs
+ --outdir "."
+ --quiet
+ "bin/@PROJECT_NAME@"
+ WORKING_DIRECTORY @CMAKE_INSTALL_PREFIX@
+ RESULT_VARIABLE extract_dependencies_result
+ #COMMAND_ECHO STDOUT
+ OUTPUT_VARIABLE extract_dependencies_stdout
+ ERROR_VARIABLE extract_dependencies_stdout
+)
+
+message("${extract_dependencies_stdout}")
+
+if( extract_dependencies_result )
+ message( FATAL_ERROR "Command failed with error: '${extract_dependencies_result}'")
+endif()
diff --git a/Reservoir DMS Suite/open-etp-server/create_demo_space.sh b/Reservoir DMS Suite/open-etp-server/create_demo_space.sh
new file mode 100644
index 0000000000000000000000000000000000000000..cfda05ece7110e01807a05ba796d10fc082748d4
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/create_demo_space.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar --new -s demo/Volve
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./Volve_Demo_Fault_Horizon_TIME.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./Volve_Demo_Horizons_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./Volve_Demo_Reservoir_Grid_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./Volve_Demo_Wells_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --stats
+# openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --delete
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.epc b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.epc
new file mode 100644
index 0000000000000000000000000000000000000000..35fe99473b72eba5d030766d23c983d96c66e3bc
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.epc differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.h5 b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.h5
new file mode 100644
index 0000000000000000000000000000000000000000..e39c4abc30c0d9322bc3f2abcd76928e5842b814
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Fault_Horizon_TIME.h5 differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.epc b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.epc
new file mode 100644
index 0000000000000000000000000000000000000000..4d78c06d22ab435199245ae4ccd385e86bca01da
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.epc differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.h5 b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.h5
new file mode 100644
index 0000000000000000000000000000000000000000..91531c8f5fc588c038ffdf393a519063f5868962
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Horizons_Depth.h5 differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.epc b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.epc
new file mode 100644
index 0000000000000000000000000000000000000000..286e8df5221cf1a67cd7a6ef1a10ca624088b26f
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.epc differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.h5 b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.h5
new file mode 100644
index 0000000000000000000000000000000000000000..51bae8d49bad4db1fd2920aaefa18926e9917357
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Reservoir_Grid_Depth.h5 differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.epc b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.epc
new file mode 100644
index 0000000000000000000000000000000000000000..9f9a5e062c28c2e387ffd1c10fda036c5fd7be6b
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.epc differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.h5 b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.h5
new file mode 100644
index 0000000000000000000000000000000000000000..c6712ea3a171c2755139a6887d9d47a79a46f5f8
Binary files /dev/null and b/Reservoir DMS Suite/open-etp-server/data/Volve_Demo_Wells_Depth.h5 differ
diff --git a/Reservoir DMS Suite/open-etp-server/data/create_demo_space.sh b/Reservoir DMS Suite/open-etp-server/data/create_demo_space.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d6040862f5fdc875949966133952d86b85d0995e
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/data/create_demo_space.sh
@@ -0,0 +1,8 @@
+export SERVER_CONTAINER=open-etp-server_open_etp_server_runtime_run_5981f91fc84f
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar --new -s demo/Volve
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./data/Volve_Demo_Fault_Horizon_TIME.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./data/Volve_Demo_Horizons_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./data/Volve_Demo_Reservoir_Grid_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --import-epc ./data/Volve_Demo_Wells_Depth.epc
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --stats
+openETPServer space -S ws://$SERVER_CONTAINER:9002 -u foo -p bar -s demo/Volve --delete
diff --git a/Reservoir DMS Suite/open-etp-server/docker-compose.yml b/Reservoir DMS Suite/open-etp-server/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..55863fc51ac3f3b5bed8d3cfc16ed6925e3ce45b
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/docker-compose.yml
@@ -0,0 +1,62 @@
+version: '3'
+
+services:
+ open-etp-postgresql:
+ image: postgres
+ container_name: open-etp-postgresql
+ ports:
+ - "5432:5432"
+ environment:
+ - POSTGRES_PASSWORD=tester
+ - POSTGRES_USER=tester
+ - POSTGRES_DB=postkv
+ volumes:
+ - ${SOURCE_HOST_DIR:-.}/scripts/init-db:/docker-entrypoint-initdb.d
+ networks:
+ - web
+
+ open_etp_server_build:
+ build:
+ context: .
+ dockerfile: Dockerfile.build
+ args:
+ - VERSION=0.1
+ image: open_etp_server_build
+ labels:
+ OpenETPServer.service: build
+ logging:
+ driver: json-file
+ depends_on:
+ - open-etp-postgresql
+ environment:
+ - POSTGRESQL_CONN_STRING=host=open-etp-postgresql port=5432 dbname=postkv user=tester password=tester
+ volumes:
+ - ${SOURCE_HOST_DIR:-.}:/source
+ networks:
+ - web
+
+ open_etp_server_runtime:
+ profiles: ["cli-only"]
+ depends_on:
+ - open-etp-postgresql
+ build:
+ context: .
+ dockerfile: Dockerfile.runtime
+ args:
+ - VERSION=0.1
+ image: open_etp_server_runtime
+ volumes:
+ - ${SOURCE_HOST_DIR:-.}/data:/data
+ ports:
+ - "9002:9002"
+ labels:
+ OpenETPServer.service: runtime
+ logging:
+ driver: json-file
+ environment:
+ - POSTGRESQL_CONN_STRING=${POSTGRESQL_CONN_STRING:-host=open-etp-postgresql port=5432 dbname=postkv user=tester password=tester}
+ networks:
+ - web
+
+networks:
+ web:
diff --git a/Reservoir DMS Suite/open-etp-server/list_unused_files.py b/Reservoir DMS Suite/open-etp-server/list_unused_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..c31922fc445bbb3bf86cb1416f4d29fbb33198bf
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/list_unused_files.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import pathlib
+import re
+import os
+
+
+ignored_files_re = re.compile(r"(CMakeLists.txt|files.cmake)$")
+files_cmake_re = re.compile(r"(.*\.(?:h|hpp|hh|c|cc|cpp))$", re.IGNORECASE)
+
+
+def handle_files_cmake(files_cmake):
+ print(f"handling {files_cmake}")
+ files_cmake_dir = files_cmake.parent
+ used_files = set()
+ with open(files_cmake, "rt") as fd:
+ for line in fd:
+ # cleanup line from comments
+ line = line.split("#")[0].strip()
+ if m:=files_cmake_re.match(line): # noqa: E231 E701
+ used_files.add(files_cmake_dir / m.group(1))
+ # list all files in dir and subdirs:
+ full_list = set()
+ for f in files_cmake_dir.rglob("*"):
+ if f.is_dir():
+ continue
+ elif ignored_files_re.match(str(f.name)):
+ continue
+ elif files_cmake_re.match(str(f)):
+ full_list.add(f)
+ else:
+ # Uncomment next line for detecting unhandeled files
+ # print(" ", f)
+ pass
+ unused_files = full_list - used_files
+ if unused_files:
+ print(" Files CMake is not aware of:")
+ for f in sorted(unused_files):
+ print(f" {f.relative_to(os.getcwd())}")
+ print(f" Files used: {len(used_files)}/{len(full_list)}")
+ else:
+ print(f" All {len(full_list)} files are well used")
+ return True
+ return False
+
+
+def main():
+ root_dir = pathlib.Path(__file__).parent.absolute()
+ print(root_dir)
+ result = True
+ for item in sorted(root_dir.rglob("files.cmake")):
+ result = handle_files_cmake(item) and result
+ return 0
+
+
+if __name__ == '__main__':
+ exit(main())
diff --git a/Reservoir DMS Suite/open-etp-server/scripts/build.sh b/Reservoir DMS Suite/open-etp-server/scripts/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e33c2da7f01f42f70b34b8650184ede9dbf8fd11
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/scripts/build.sh
@@ -0,0 +1,204 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Set value to 0 to force PostgreSQL unit tests execution
+NO_POSTGRESQL_UNIT_TESTS=1
+
+if [[ $# -gt 0 && "$1" == "--debug" ]]; then
+ BUILD_TYPE=Debug
+ CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCODE_COVERAGE=ON"
+ # remove the parameter
+ shift
+else
+ BUILD_TYPE=Release
+ CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=${BUILD_TYPE}"
+fi
+
+if [[ $# -lt 1 ]]; then
+ echo -e "$0 [--debug] BUILD_DIR [ninja_args]..."
+ echo -e "\--debug\n\t\tPerform Debug build (Default: Release build)"
+ echo -e "\tBUILD_DIR\n\t\tPath to the build directory to use"
+ echo -e "\tninja_args\n\t\tAny argument that will be forwarded to ninja"
+ exit 1
+fi
+
+# Coloration codes
+Black='\033[0;30m'
+DarkGray='\033[1;30m'
+Red='\033[0;31m'
+LightRed='\033[1;31m'
+Green='\033[0;32m'
+LightGreen='\033[1;32m'
+Orange='\033[0;33m'
+Yellow='\033[1;33m'
+Blue='\033[0;34m'
+LightBlue='\033[1;34m'
+Purple='\033[0;35m'
+LightPurple='\033[1;35m'
+Cyan='\033[0;36m'
+LightCyan='\033[1;36m'
+LightGray='\033[0;37m'
+White='\033[1;37m'
+NC='\033[0m' # No Color
+
+# Adding error reporting
+err_report() {
+ echo ""
+ echo -e "${Red}Error on $0:$1${NC}: stop execution."
+}
+trap 'err_report $LINENO' ERR
+
+
+# Actual build
+SOURCE_TREE="${PWD}"
+BUILD_TREE="${SOURCE_TREE}/${1}"
+
+# Removing first mandatory parameter
+shift
+
+# To know the global execution time:
+main_start=`date +%s`
+
+if [ ! -d "$BUILD_TREE" ]; then
+ echo "Creating build directory"
+ mkdir -p "${BUILD_TREE}" && echo -e "\tOK"
+fi
+
+cd "${BUILD_TREE}"
+
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+echo -e "${LightBlue}Cleanup UnitTest directory${NC}"
+rm -rf tests
+
+echo -e "${LightBlue}Cleanup Install directory${NC}"
+rm -rf dist
+
+echo -e "${LightBlue}Cleanup CodeCoverage directory${NC}"
+rm -rf gcovr
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+echo -e "${LightBlue}Configuring build tree using CMake and Ninja for ${BUILD_TYPE} build${NC}"
+
+echo -e "Running (in ${Green}$PWD${NC}): ${Yellow}cmake -GNinja ${CMAKE_OPTIONS} \"${SOURCE_TREE}\" -DCMAKE_INSTALL_PREFIX=\"./dist\"${NC}"
+start=`date +%s`
+cmake -GNinja ${CMAKE_OPTIONS} "${SOURCE_TREE}" -DCMAKE_INSTALL_PREFIX="./dist"
+end=`date +%s`
+echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+
+
+# Actual build:
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+echo -e "${LightBlue}Building${NC}"
+echo -e "Running (in ${Green}$PWD${NC}): ${Yellow}ninja $*${NC}"
+start=`date +%s`
+ninja "$@"
+end=`date +%s`
+echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+
+# Remove local temporary file
+rm -f a.out
+
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+for filename in openETPServer; do
+ if [[ -f ${filename} && -x ${filename} ]]; then
+ echo -e "${LightBlue}Testing that ${filename} can be run${NC}"
+ echo -e "Running (in ${Green}$PWD${NC}) (log written in ${Cyan}${filename}.log${NC}): ${Yellow}./${filename} -h${NC}"
+ start=`date +%s`
+ if (./${filename} -h > ./${filename}.log 2>&1); then
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+ else
+ echo -e "\t${Red}FAILED with return code: $?${NC}"
+ echo -e "${Purple}<<<<< STDOUT <<<<<${NC}"
+ cat ./${filename}.log
+ echo -e "${Purple}>>>>> STDOUT >>>>>${NC}"
+ exit 1
+ fi
+ fi
+done
+
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+echo -e "${LightBlue}Running UnitTests:${NC}"
+echo -e "Running (in ${Green}$PWD${NC}) (log written in ${Cyan}${PWD}/Testing/Temporary/LastTest.log${NC}): ${Yellow}ctest . -v -j4${NC}"
+start=`date +%s`
+if (ctest . -v -j4); then
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+else
+ echo -e "\t${Red}FAILED with return code: $?${NC}"
+ echo -e "${Purple}<<<<< logging file <<<<<${NC}"
+ cat ./Testing/Temporary/LastTest.log
+ echo -e "${Purple}>>>>> logging file >>>>>${NC}"
+ echo -e "You can read this content in file: ${Yellow}${PWD}/Testing/Temporary/LastTest.log${NC}"
+ exit 1
+fi
+
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+if [ ${BUILD_TYPE} == "Debug" ]; then
+ echo -e "${LightBlue}Computing Code Coverage reports (can take some time):${NC}"
+ echo -e "Running (in ${Green}$PWD${NC}): ${Yellow}gcovr -r ../.. -j 8 -s --delete (...) -o gcovr.log .${NC}"
+ start=`date +%s`
+ mkdir -p gcovr/html
+ mkdir -p gcovr/sonarqube
+ if (gcovr -r ../.. -j 8 -s --delete --html "gcovr/html/index.html" --html-details --sonarqube "gcovr/sonarqube/openetpserver.xml" -o "gcovr.log" .); then
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+ else
+ echo -e "\t${Red}FAILED with return code: $?${NC}"
+ echo -e "${Purple}<<<<< logging file <<<<<${NC}"
+ cat ./gcovr.log
+ echo -e "${Purple}>>>>> logging file >>>>>${NC}"
+ echo -e "You can read this content in file: ${Yellow}${PWD}/Testing/Temporary/LastTest.log${NC}"
+ # Cleanup GCOV files if not done by gcov
+ find "${SOURCE_TREE}" -name *.gcov -exec rm -r {} \;
+ exit 1
+ fi
+else
+ echo -e "${LightBlue}Packaging:${NC}"
+ echo -e "Running (in ${Green}$PWD${NC}) (log written in ${Cyan}${filename}.log${NC}): ${Yellow}ninja install${NC}"
+ start=`date +%s`
+ ninja install
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+
+ echo -e "\n${LightBlue}Checking dist program can start:${NC}"
+ echo -e "Running (in ${Green}$PWD${NC}) (log written in ${Cyan}${PWD}/start_dist.log${NC}): ${Yellow}./dist/bin/openETPServer -h${NC}"
+ start=`date +%s`
+ if (LD_LIBRARY_PATH=./dist/lib ./dist/bin/openETPServer -h > start_dist.log 2>&1); then
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+ else
+ echo -e "\t${Red}FAILED with return code: $?${NC}"
+ echo -e "${Purple}<<<<< logging file <<<<<${NC}"
+ cat ./start_dist.log
+ echo -e "${Purple}>>>>> logging file >>>>>${NC}"
+ echo -e "You can read this content in file: ${Yellow}${PWD}/Testing/Temporary/LastTest.log${NC}"
+ exit 1
+ fi
+
+ echo -e "\n${LightBlue}Compressing dist program:${NC}"
+ cd dist
+ echo -e "Running (in ${Green}$PWD${NC}) (log written in ${Cyan}${PWD}/../compress_dist.log${NC}): ${Yellow}tar -czvf ../dist.tgz .${NC}"
+ start=`date +%s`
+ if (tar -czvf ../dist.tgz .> ../compress_dist.log 2>&1); then
+ end=`date +%s`
+ echo -e "\t${Green}OK (`expr $end - $start`s)${NC}"
+ else
+ echo -e "\t${Red}FAILED with return code: $?${NC}"
+ echo -e "${Purple}<<<<< logging file <<<<<${NC}"
+ cat ../compress_dist.log
+ echo -e "${Purple}>>>>> logging file >>>>>${NC}"
+ echo -e "You can read this content in file: ${Yellow}${PWD}/Testing/Temporary/LastTest.log${NC}"
+ exit 1
+ fi
+ cd ..
+fi
+
+echo -e "${LightCyan}-------------------------------------------------------------------------${NC}"
+end=`date +%s`
+echo -e "Build and tests are ${Green}OK (`expr $end - $main_start`s)${NC}"
diff --git a/Reservoir DMS Suite/open-etp-server/scripts/build_and_install_avrocpp.sh b/Reservoir DMS Suite/open-etp-server/scripts/build_and_install_avrocpp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..80d15515300d75a60de636abe8addaa00c910a63
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/scripts/build_and_install_avrocpp.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Note: AVROCPP_VER comes from Docker environment when built using Docker/DockerCompose
+echo "Building and Installing AvroCPP version ${AVROCPP_VER}"
+
+avro_cpp_build_dir=/tmp/avro_build
+
+mkdir -p "${avro_cpp_build_dir}"
+cd "${avro_cpp_build_dir}"
+wget https://github.com/apache/avro/archive/refs/tags/release-${AVROCPP_VER}.tar.gz -O - | tar -xz
+
+mkdir -p "${avro_cpp_build_dir}/avro-release-${AVROCPP_VER}/lang/c++/build"
+cd "${avro_cpp_build_dir}/avro-release-${AVROCPP_VER}/lang/c++/build"
+
+cmake -GNinja -DCMAKE_BUILD_TYPE=Release ..
+
+ninja install
+
+rm -rf "${avro_cpp_build_dir}"
diff --git a/Reservoir DMS Suite/open-etp-server/scripts/build_inside_docker.sh b/Reservoir DMS Suite/open-etp-server/scripts/build_inside_docker.sh
new file mode 100755
index 0000000000000000000000000000000000000000..41cc603cec38ebc19c53390b013c08d6a3a92ad8
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/scripts/build_inside_docker.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Create group and user identically to running user.
+if [ ! "$(getent group running_user_group)" ]; then
+ echo "Creating running user group"
+ SOURCE_TREE_GID=$(stat -c '%g' .)
+ if [ "$SOURCE_TREE_GID" -eq "0" ]; then
+ SOURCE_TREE_GID=12345
+ fi
+ groupadd -r running_user_group -g "${SOURCE_TREE_GID}" && echo -e "\tOK"
+fi
+if [ ! "$(getent passwd running_user)" ]; then
+ echo "Creating running user"
+ SOURCE_TREE_UID=$(stat -c '%u' .)
+ if [ "$SOURCE_TREE_UID" -eq "0" ]; then
+ SOURCE_TREE_UID=12345
+ fi
+ useradd --no-log-init -r -u "${SOURCE_TREE_UID}" -g running_user_group running_user && echo -e "\tOK"
+fi
+
+echo "#########################################################################"
+figlet -c "Debug"
+su -m running_user -c "bash scripts/build.sh --debug build/using_docker_debug"
+
+echo "#########################################################################"
+figlet -c "Release"
+su -m running_user -c "bash scripts/build.sh build/using_docker"
diff --git a/Reservoir DMS Suite/open-etp-server/scripts/extract_dependencies.py b/Reservoir DMS Suite/open-etp-server/scripts/extract_dependencies.py
new file mode 100755
index 0000000000000000000000000000000000000000..ddcce2546703cf341d4df011d7eb06008d194720
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/scripts/extract_dependencies.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+import subprocess
+import re
+import pathlib
+import os
+import sys
+import argparse
+import shutil
+import textwrap
+
+
+DEBUG = 0
+QUIET = 0
+LISTED_FILES = set()
+
+
+def parse_cli() -> argparse.Namespace:
+ """Reads command arguments.
+
+ Returns:
+ argparse.Namespace: options given by user in command line.
+ """
+ global DEBUG, QUIET
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent("""
+ Given a binary, lists all its dependencies, optionally copy it to the given output directory.
+ It is recommended to ran `setvars.sh` first to get needed environment variables.
+ """).strip()
+ )
+ parser.add_argument(
+ "FILES", nargs="+",
+ help="list of binaries to get/copy dependencies from",
+ )
+ parser.add_argument(
+ "-d", "--debug",
+ dest="debug", action="store_true",
+ help="Print more information about what is done",
+ )
+ parser.add_argument(
+ "-q", "--quiet",
+ dest="quiet", action="store_true",
+ help="Print as few info as possible",
+ )
+ parser.add_argument(
+ "-s", "--system", "--include-system-libs",
+ dest="include_system_libs", action="store_true",
+ help="Copy system libraries to destination.",
+ )
+ parser.add_argument(
+ "-o", "--outdir",
+ dest="output_directory", default=None, action="store",
+ help="Directory where to put shared libs and binaries to (if not given, lists only)",
+ )
+ options = parser.parse_args()
+ DEBUG = DEBUG or options.debug
+ QUIET = options.quiet
+ return options
+
+
+def is_lib(item: pathlib.Path) -> bool:
+ """Detects if a file is a shared library based on filename parsing.
+
+ Args:
+ item (pathlib.Path): item to check.
+
+ Returns:
+ bool: True if `item` is a shared library.
+ """
+ return ".so" in f".{item.name.split('.', 1)[-1]}"
+
+
+def handle_file(item: pathlib.Path, base_dest: pathlib.Path = None) -> None:
+ """Prints and eventually copies given `item`.
+
+ Args:
+ item (pathlib.Path): Item to print and copy.
+ base_dest (pathlib.Path): Optional directory where to copy `item`. Defaults to None.
+ """
+ if base_dest:
+ # Handle Copy
+ if is_lib(item):
+ dest = base_dest / "lib" / item.name
+ else:
+ dest = base_dest / "bin" / item.name
+ dest.parent.mkdir(parents=True, exist_ok=True)
+ if DEBUG:
+ print(f" Copy '{item}' to '{dest}'")
+ elif not QUIET:
+ print(f" --> Installing: {dest.absolute()}")
+ if item.absolute() == dest.absolute():
+ return
+ if item.is_symlink():
+ target = (item.parent / os.readlink(item)).absolute()
+ try:
+ target = target.relative_to(item.parent)
+ except ValueError:
+ pass # Not relative, copy it as is
+ dest.symlink_to(target=target)
+ else:
+ shutil.copy2(item, dest)
+ else:
+ if DEBUG:
+ print(item.is_symlink(), item)
+ else:
+ print(item)
+
+
+def handle_all_links(item: pathlib.Path, base_dest: pathlib.Path = None) -> None:
+ """Given `item` file, prints and eventually copies it and its symlinks targets.
+
+ Args:
+ item (pathlib.Path): Item to handle (if it is a symlink, then will handle target too).
+ base_dest (pathlib.Path): Optional directory where to copy `item`. Defaults to None.
+ """
+ global LISTED_FILES
+ if item not in LISTED_FILES:
+ handle_file(item, base_dest)
+ LISTED_FILES.add(item)
+ else:
+ # if item is already listed all link are too.
+ return
+
+ # If symlinks, handle target too.
+ if item.is_symlink():
+ handle_all_links((item.parent / os.readlink(item)).absolute(), base_dest)
+
+
+def print_all_dependencies(item: pathlib.Path, base_dest: pathlib.Path = None, include_system_libs: bool = False) -> int:
+ """Given a binary (executable or library) runs `ldd` to list all its dependencies.
+ It eventually copies all items if `base_dest` is given.
+ It uses both OES_PROJECT_BUILD_DIR and OES_TOOLKITS_DIR environment variables
+ to detect dependencies.
+
+ Args:
+ item (pathlib.Path): Path to the binary to analyze.
+ base_dest (pathlib.Path): Optional directory where to copy `item`. Defaults to None.
+ include_system_libs (bool): Copy system libraries too. Defaults to False.
+
+ Returns:
+ int: 0 if all was OK; 1 if anything went wrong.
+ """
+ base_dir = pathlib.Path(
+ os.environ.get(
+ "OES_PROJECT_BUILD_DIR",
+ os.getcwd() # Default for current working directory
+ )
+ ).absolute()
+
+ home_toolworks = pathlib.Path(
+ os.environ.get(
+ "OES_TOOLKITS_DIR",
+ "/home/toolworks" # default to /home/toolworks
+ )
+ ).absolute()
+
+ if DEBUG:
+ print(dir(base_dir))
+
+ possible_files = (
+ pathlib.Path(item),
+ base_dir / item,
+ base_dir / "bin" / item,
+ base_dir / "lib" / item,
+ )
+ for app_file in possible_files:
+ if app_file.is_file():
+ break
+ if not app_file.is_file():
+ print(f"Error: cannot find file '{item}'")
+ return 1
+
+ # Input must be part of the package too.
+ handle_all_links(app_file, base_dest)
+
+ output = subprocess.check_output(
+ f'ldd {app_file}',
+ stderr=subprocess.STDOUT,
+ shell=True
+ ).decode()
+
+ found_re = re.compile(r"\s*(.*)\s=>\s(.+)\s\(.*\)")
+ not_found_re = re.compile(r"\s*(.*)\s=>\snot\sfound")
+
+ for line in output.splitlines():
+ if m := not_found_re.search(line):
+ print("Not found", m.group(1))
+ return 1
+ if m := found_re.search(line):
+ lib_path = pathlib.Path(m.group(2))
+ lib_path_abs = lib_path.absolute()
+ common_paths = {
+ os.path.commonpath((base_dir, lib_path_abs)),
+ os.path.commonpath((home_toolworks, lib_path_abs))
+ }
+ if common_paths == {"/"} and not include_system_libs:
+ continue
+ handle_all_links(lib_path_abs, base_dest)
+
+ return 0
+
+
+def main() -> int:
+ """Parses CLI options and list/copy all dependencies to user's options.
+
+ Returns:
+ int: 0 if all was OK; 1 if anything went wrong.
+ """
+ options = parse_cli()
+ if options.output_directory:
+ base_path = pathlib.Path(options.output_directory)
+ base_path.mkdir(parents=True, exist_ok=True)
+ else:
+ base_path = None
+
+ os.environ["LD_LIBRARY_PATH"] = ":".join([os.path.join(".", "lib"), "/usr/local/lib"] + os.environ.get("LD_LIBRARY_PATH", []))
+ for item in options.FILES:
+ if print_all_dependencies(item, base_path, options.include_system_libs):
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/Reservoir DMS Suite/open-etp-server/scripts/init-db/init.sh b/Reservoir DMS Suite/open-etp-server/scripts/init-db/init.sh
new file mode 100644
index 0000000000000000000000000000000000000000..945ae3f9db25a895015cb6f5a5f2f600ef7048fe
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/scripts/init-db/init.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+ CREATE USER UTEST;
+ CREATE EXTENSION lo;
+EOSQL
+
+
diff --git a/Reservoir DMS Suite/open-etp-server/src/CMakeLists.txt b/Reservoir DMS Suite/open-etp-server/src/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1fe71d0ab6ff33b48bd787ccd436d7a8266f17af
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/CMakeLists.txt
@@ -0,0 +1,7 @@
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)
+
+add_subdirectory(lib)
+add_subdirectory(bin)
+
+add_subdirectory(tests)
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/CMakeLists.txt b/Reservoir DMS Suite/open-etp-server/src/bin/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a93282215c90fb27cc9e27e6c6133bf79dc313c3
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(openETPServer)
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/CMakeLists.txt b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3eb732e773374687527f53f2442d589f07fa4ccd
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/CMakeLists.txt
@@ -0,0 +1,75 @@
+project(openETPServer)
+
+# 3rd parties:
+ USE_AVROCPP()
+ USE_BOOST()
+ USE_HDF5()
+ if(USE_POSTGRESQL_ETP)
+ USE_POSTGRESQL()
+ endif()
+ USE_WEBSOCKETPP()
+
+
+# files needed for build
+ include(files.cmake)
+
+add_executable(
+ openETPServer
+ ${openETPServer_HEADERS}
+ ${openETPServer_SOURCES}
+)
+
+if(USE_POSTGRESQL_ETP)
+ target_link_libraries(
+ openETPServer
+ # Internal library that are explicitelly inside header files
+ PUBLIC
+
+ # Internal library that are only in source files
+ PRIVATE
+ oes_utils
+ oes_http
+ oes_eml
+ oes_postgresql
+ # 3rd Parties:
+ PRIVATE
+ ${AVROCPP_LIBRARIES}
+ ${BOOST_LIBRARIES}
+ ${HDF5_LIBRARIES}
+ ${WEBSOCKETPP_LIBRARIES}
+ ${POSTGRESQL_LIBRARIES}
+ )
+else()
+ target_link_libraries(
+ openETPServer
+ # Internal library that are explicitelly inside header files
+ PUBLIC
+
+ # Internal library that are only in source files
+ PRIVATE
+ oes_utils
+ oes_http
+ oes_eml
+ # 3rd Parties:
+ PRIVATE
+ ${AVROCPP_LIBRARIES}
+ ${BOOST_LIBRARIES}
+ ${HDF5_LIBRARIES}
+ ${WEBSOCKETPP_LIBRARIES}
+ )
+endif()
+
+if(USE_POSTGRESQL_ETP)
+ install(
+ TARGETS openETPServer oes_utils oes_http oes_eml oes_postgresql
+ )
+else()
+ install(
+ TARGETS openETPServer oes_utils oes_http oes_eml
+ )
+endif()
+
+configure_file( "${HELPER_LIBRARY_DIR}/extract_dependencies.cmake.in" "extract_dependencies.cmake" @ONLY)
+install(
+ SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/extract_dependencies.cmake"
+)
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc92a3467411ba0fb8d4fd6c50cc20d4d5c4ec2a
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.cpp
@@ -0,0 +1,461 @@
+// ============================================================================
+// Copyright 2015-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EmlAppContext.h"
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+# include
+# include
+# define isatty(h) _isatty(h)
+#else
+#include
+#include
+#include
+#endif
+
+namespace po = boost::program_options;
+using oes::apps::eml::EmlAppContext;
+
+using std::endl;
+
+namespace {
+
+// http://stackoverflow.com/questions/6240950
+std::ostream cnull(nullptr);
+
+// See also http://stackoverflow.com/questions/16935908
+// and http://stackoverflow.com/questions/6812224
+std::pair getTerminalLineAndColumnSizes() {
+ std::pair result{ 0, 0 };
+
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ if (0 != GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
+ result.first = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ result.second = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ }
+#else
+ struct winsize w;
+ if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)) {
+ result.first = w.ws_row;
+ result.second = w.ws_col;
+ }
+#endif
+
+ if (result.first == 0) {
+ result.first = 24;
+ } else {
+ result.first = std::min(std::max(result.first, 4u), 256u); // clamp( 4, 256)
+ }
+
+ if (result.second == 0) {
+ result.second = po::options_description::m_default_line_length;
+ } else {
+ // Call succeeded. But values may be out of range.
+ // Redirected output on Linux yields (6, 0) for example.
+ result.second = std::min(std::max(result.second, 64u), 160u); // clamp(64, 160)
+ }
+
+ return result;
+}
+
+} // namespace
+
+// FIXME: lots of copy/paste
+
+namespace oes {
+namespace apps {
+namespace eml {
+
+/****************************************************************************/
+
+#ifdef OES_DEBUG_BUILD
+bool test_parseByteSize() {
+ auto assertOK = [](size_t expected, const char* spec) {
+ assert(expected == parseByteSize(spec));
+ };
+ assertOK(1, "1");
+ assertOK(1, "1B");
+ assertOK(1000, "1KB");
+ assertOK(1024, "1KiB");
+ assertOK(15*1000*1000, "15MB");
+ assertOK(size_t(1.5*1000*1000), "1.5MB");
+ assertOK(size_t(1.5*1024*1024), "1.5MiB");
+
+ auto assertKO = [](const char* expected_err_fragment, const char* spec) {
+ try {
+ parseByteSize(spec);
+ assert(false);
+ } catch (const std::runtime_error& ex) {
+ const std::string err(ex.what());
+ assert(err.find(expected_err_fragment) != std::string::npos);
+ }
+ };
+ assertKO("Expected leading number", "foo");
+ assertKO("Must end with B", "1K");
+ assertKO("Invalid uom", "1FooB");
+ assertKO("Invalid uom", "1FB");
+ assertKO("Size too large", "3GB");
+ assertKO("Size too large", "1TB");
+
+ return true;
+}
+static const bool ok = test_parseByteSize();
+#endif
+
+size_t parseByteSize(const std::string& spec) {
+ std::stringstream ss(spec);
+
+ double val = 0;
+ ss >> val;
+ if (!ss) {
+ throw std::runtime_error("Expected leading number in " + spec);
+ }
+
+ if (val < 0 || val > 2147483647 /*2GiB*/) {
+ throw std::runtime_error(fmt::format("Unexpected size: {}", val));
+ }
+
+ std::string uom;
+ ss >> uom;
+
+ if (uom.empty()) {
+ // No unit, assume bytes directly
+ return static_cast(val);
+ }
+
+ if (uom.back() != 'B') {
+ throw std::runtime_error(fmt::format("Invalid uom: {}: Must end with B, for Byte", uom));
+ }
+
+ const bool power_of_2 = (uom.size() == 3 && uom[1] == 'i');
+
+ if (!(uom.size() == 1 || uom.size() == 2 || power_of_2)) {
+ throw std::runtime_error(fmt::format("Invalid uom: {}. Use KB, MB, GB; KiB, MiB, GiB", uom));
+ }
+
+ const double factor = power_of_2? 1024: 1000;
+
+ switch (uom[0]) {
+ case 'T': val *= factor; BOOST_FALLTHROUGH;
+ case 'G': val *= factor; BOOST_FALLTHROUGH;
+ case 'M': val *= factor; BOOST_FALLTHROUGH;
+ case 'K': val *= factor; BOOST_FALLTHROUGH;
+ case 'B': break;
+ default:
+ throw std::runtime_error(fmt::format("Invalid uom: {}. Use KB, MB, GB; KiB, MiB, GiB", uom));
+ }
+
+ if (val > 2147483647 /*2GiB*/) {
+ throw std::runtime_error(fmt::format("Size too large (>2GiB): {}", val));
+ }
+
+ return static_cast(val);
+}
+
+std::pair parseByteSizes(const std::string& spec) {
+ std::vector sizes;
+ if (spec.empty()) {
+ return { 0, 0 };
+ }
+
+ boost::split(
+ sizes, spec, boost::algorithm::is_any_of("\t ,;|"),
+ boost::algorithm::token_compress_on
+ );
+
+ size_t zsize = 0; // max compressed size
+ size_t usize = 0; // max uncompressed size
+
+ switch (sizes.size()) {
+ case 1:
+ zsize = parseByteSize(sizes[0]);
+ usize = 10 * zsize; // default to 10x the specified max-compressed-size
+ break;
+ case 2:
+ zsize = parseByteSize(sizes[0]);
+ usize = parseByteSize(sizes[1]);
+ break;
+ default:
+ throw std::runtime_error(
+ fmt::format("Expected 1 or 2 sizes, not {}", sizes.size())
+ );
+ }
+
+ return { zsize, usize };
+}
+
+/****************************************************************************/
+
+EmlAppContext::EmlAppContext(
+ variables_map& args
+) : args(args)
+ , help(args.count("help") > 0)
+ , quiet(args.count("quiet") > 0) // quiet "wins" over verbose and debug
+ , debug((!quiet && args.count("debug") > 0)) // debug implies verbose too
+ , verbose((!quiet && (debug || args.count("verbose") > 0)))
+ , timer(args.count("timer") > 0)
+ , command(!args["command"].empty()? args["command"].as(): std::string())
+ , terminalWidth_(terminalWidth())
+{
+}
+
+std::ostream& EmlAppContext::out() const {
+ const std::string& filename = stringArg("output-file");
+ if (!filename.empty()) {
+ if (!ofs_.is_open()) {
+ ofs_.open(filename.c_str());
+ }
+ if (ofs_.good()) {
+ return ofs_;
+ }
+ err() << "openETPServer: Warning: Cannot open file: " << filename << endl;
+ // fall thru
+ }
+ return quiet? cnull: std::cout;
+}
+
+std::ostream& EmlAppContext::err() const {
+ return quiet? cnull: std::cerr;
+}
+
+std::ostream& EmlAppContext::msg() const {
+ return verbose? out() : cnull;
+}
+
+std::ostream& EmlAppContext::dbg() const {
+ return debug ? out() : cnull;
+}
+
+bool EmlAppContext::hasArg(const std::string& name) const {
+ return args.count(name) > 0;
+}
+
+std::string EmlAppContext::stringArg(const std::string& name) const {
+ const po::variable_value& value = args[name];
+ return value.empty()? std::string(): value.as();
+}
+
+/*!
+ * \brief Augments global options with command-specific options.
+ *
+ * \param cmd_desc the command-specific options.
+ * \param unparsed_args the unparsed arguments to parse against \a cmd_desc.
+ * \return 0 on success; -1 on success, but return immediately success (0);
+ * positive error code otherwise.
+ */
+int EmlAppContext::reparse(
+ const options_description& cmd_desc,
+ const std::vector& unparsed_args
+) {
+ try {
+ po::store(
+ po::command_line_parser(unparsed_args)
+ .options(cmd_desc)
+ .run(),
+ args
+ );
+ }
+ catch (const std::exception& ex) {
+ err() << "openETPServer: Error: " << ex.what()
+ << '\n' << '\n' << cmd_desc << endl;
+ return 1; // error
+ }
+
+ if (args.size() == 0) {
+ out() << cmd_desc << endl;
+ return -1; // success, but caller should return 0
+ }
+
+ if (help) {
+ out() << cmd_desc << endl;
+ return -1; // success, but caller should return 0
+ }
+
+ return 0; // success
+}
+
+/*!
+ * \brief Augments global options with command-specific options.
+ *
+ * \param cmd_desc the command-specific options.
+ * \param pos_desc the command-specific positional options (option with no name)
+ * \param unparsed_args the unparsed arguments to parse against \a cmd_desc.
+ * \return 0 on success; -1 on success, but return immediately success (0);
+ * positive error code otherwise.
+ */
+int EmlAppContext::reparse(
+ const options_description& cmd_desc,
+ const positional_options_description& pos_desc,
+ const std::vector& unparsed_args
+) {
+ try {
+ po::store(
+ po::command_line_parser(unparsed_args)
+ .options(cmd_desc)
+ .positional(pos_desc)
+ .run(),
+ args
+ );
+ }
+ catch (const std::exception& ex) {
+ err() << "openETPServer: Error: " << ex.what()
+ << '\n' << '\n' << cmd_desc << endl;
+ return 1; // error
+ }
+
+ if (args.size() == 0) {
+ out() << cmd_desc << endl;
+ return -1; // success, but caller should return 0
+ }
+
+ if (help) {
+ out() << cmd_desc << endl;
+ return -1; // success, but caller should return 0
+ }
+
+ return 0; // success
+}
+
+int EmlAppContext::dispatchCommand(
+ const parsed_options& parsed, const EmlAppNamedCommandVec& commands
+) {
+ OES_ASSERT(!commands.empty(), return 1;);
+
+ for (const EmlAppNamedCommand& entry : commands) {
+ if (command == entry.name) {
+ std::vector command_args = po::collect_unrecognized(
+ parsed.options, po::include_positional
+ );
+
+ // Remove the command itself from command_args.
+ // Note that other unknown options can precede it.
+ const size_t before_size = command_args.size();
+ OES_ASSERT(before_size >= 1, /*do nothing*/;);
+ for (size_t i = 0; i < command_args.size(); ++i) {
+ if (command_args[i] == command) {
+ command_args.erase(command_args.begin() + i);
+ break;
+ }
+ }
+
+ return entry.command(*this, command_args);
+ }
+ }
+
+ return -1; // no match. Unknown command?
+}
+
+int EmlAppContext::dispatchAction(const EmlAppNamedAction actions[], size_t action_count) {
+ OES_ASSERT(actions && action_count, return 1;);
+
+ size_t pop_count = 0;
+ const EmlAppNamedAction* p_action = nullptr;
+ for (size_t i = 0; i < action_count; ++i) {
+ const EmlAppNamedAction& action = actions[i];
+ if (hasArg(action.name)) {
+ ++pop_count;
+ p_action = &action;
+ }
+ }
+ switch (pop_count) {
+ case 0:
+ err() << "Error: No action specified." << std::endl;
+ return 1;
+ case 1:
+ break;
+ default:
+ err() << "Error: Too many actions specified." << std::endl;
+ return 2;
+ }
+
+ assert(p_action);
+ return timeCall(*p_action);
+}
+
+const std::string& EmlAppContext::action() const {
+ OES_ASSERT(!action_.empty(), ;);
+ return action_;
+}
+
+int EmlAppContext::timeCall(const EmlAppNamedAction& entry) {
+ const double user_start = oes::core::System::getUserTime();
+ const double real_start = oes::core::System::getRealTime();
+
+ const int rc = tryCall(entry);
+
+ if (timer && !quiet) {
+ const double user = oes::core::System::getUserTime() - user_start;
+ const double real = oes::core::System::getRealTime() - real_start;
+ oes::core::Format fmt = oes::core::Format::tr("%1|.3f|s (user: %2|.3f|s)");
+ fmt % real % user;
+ std::string timing;
+ fmt >> timing;
+ out() << '\n' << command << (entry.name? " ": "") << (entry.name? entry.name: "")
+ << " in " << timing << ' '
+ << oes::core::System::getMemoryUsage().peakMB() << " peak memory."
+ << endl;
+ }
+
+ return rc;
+}
+
+int EmlAppContext::tryCall(const EmlAppNamedAction& entry) {
+ try {
+ if (entry.name) {
+ action_ = entry.name;
+ } else { // validate and compare commands have no action
+ action_ = command;
+ }
+ return entry.action(*this)? 0: 1;
+ } catch (ActionFailed) {
+ return 1;
+ } catch (const std::exception& ex) {
+ err() << "openETPServer: Error: " << ex.what();
+ return 3; // keep in sync with main()
+ } catch (...) {
+ err() << "openETPServer: Unknown Error" << endl;
+ return 4; // keep in sync with main()
+ }
+}
+
+unsigned EmlAppContext::terminalWidth() {
+ return getTerminalLineAndColumnSizes().second;
+}
+
+/****************************************************************************/
+
+} // namespace eml
+} // namespace apps
+} // namespace oes
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.h b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d23248d22eeeac29c9d99f6d6ade0fdf96207e1
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EmlAppContext.h
@@ -0,0 +1,167 @@
+// ============================================================================
+// Copyright 2015-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+#pragma once
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace oes::apps::eml {
+
+using boost::program_options::options_description;
+using boost::program_options::positional_options_description;
+using boost::program_options::parsed_options;
+using boost::program_options::variables_map;
+
+/****************************************************************************/
+
+struct EmlAppContext;
+using UnparsedArgs = std::vector;
+
+using EmlAppCommand = int (*)(EmlAppContext&, const UnparsedArgs&);
+struct EmlAppNamedCommand { const char* name; EmlAppCommand command; const char* desc; };
+using EmlAppNamedCommandVec = std::vector;
+
+using EmlAppAction = bool (*)(const EmlAppContext&);
+struct EmlAppNamedAction { const char* name; EmlAppAction action; };
+
+struct ActionFailed {}; // Error message already printed, just "abort" current command
+
+/****************************************************************************/
+
+struct EmlAppContext {
+ variables_map& args;
+
+ const bool help;
+ const bool quiet; // warning: order matters
+ const bool debug;
+ const bool verbose;
+ const bool timer;
+
+ const std::string command;
+
+ const unsigned terminalWidth_;
+
+public:
+ EmlAppContext(variables_map& args);
+
+ std::ostream& out() const;
+ std::ostream& err() const;
+ std::ostream& msg() const;
+ std::ostream& dbg() const;
+
+ bool hasArg(const std::string& name) const;
+ std::string stringArg(const std::string& name) const;
+
+ const std::string& action() const;
+
+ int reparse(
+ const options_description& cmd_desc,
+ const std::vector& unparsed_args
+ );
+ int reparse(
+ const options_description& cmd_desc,
+ const positional_options_description& pos_desc,
+ const std::vector& unparsed_args
+ );
+
+ int dispatchCommand(
+ const parsed_options& parsed, const EmlAppNamedCommandVec& commands
+ );
+ int dispatchAction(const EmlAppNamedAction actions[], size_t action_count);
+ int timeCall(const EmlAppNamedAction& action);
+ int tryCall(const EmlAppNamedAction& action);
+
+ template
+ int dispatchActions(const EmlAppNamedAction (&actions)[N]) {
+ return dispatchAction(actions, N);
+ }
+
+ static unsigned terminalWidth();
+
+private: // no copy/assignment
+ EmlAppContext(const EmlAppContext&) = delete;
+ EmlAppContext& operator=(const EmlAppContext&) = delete;
+
+private:
+ std::string action_;
+ mutable std::ofstream ofs_;
+};
+
+/****************************************************************************/
+
+int hdf5_main (EmlAppContext& ctx, const UnparsedArgs& unparsed_args);
+int server_main (EmlAppContext& ctx, const UnparsedArgs& unparsed_args);
+int space_main (EmlAppContext& ctx, const UnparsedArgs& unparsed_args);
+int probe_main (EmlAppContext& ctx, const UnparsedArgs& unparsed_args);
+
+/****************************************************************************/
+
+#if (BOOST_VERSION >= 105900) && (BOOST_VERSION <= 106400)
+template
+struct greedy_typed_value : public boost::program_options::typed_value {
+ explicit greedy_typed_value() : boost::program_options::typed_value(nullptr) {}
+ bool adjacent_tokens_only() const override { return false; }
+ unsigned max_tokens() const override { return 1; }
+};
+template boost::program_options::typed_value* greedy_value() {
+ return new greedy_typed_value();
+}
+#else
+template boost::program_options::typed_value* greedy_value() {
+ return boost::program_options::value();
+}
+#endif
+
+/****************************************************************************/
+
+inline boost::program_options::typed_value* txtArg() {
+ return boost::program_options::value();
+}
+
+inline boost::program_options::typed_value* txtArg(const std::string& default_value) {
+ return txtArg()->default_value(default_value);
+}
+
+inline boost::program_options::typed_value* txtArgOpt(const std::string& implicit_value = "") {
+ return greedy_value()->implicit_value(implicit_value);
+}
+
+inline boost::program_options::typed_value* intArg() {
+ return boost::program_options::value();
+}
+
+inline boost::program_options::typed_value* intArg(int default_value) {
+ return boost::program_options::value()->default_value(default_value);
+}
+
+/****************************************************************************/
+
+std::string outputFilename(
+ const EmlAppContext& ctx, const std::string& input,
+ const char* dash_suffix = nullptr, const char* extension = nullptr
+);
+
+size_t parseByteSize(const std::string& spec);
+std::pair parseByteSizes(const std::string& spec);
+
+/****************************************************************************/
+
+} // oes::apps::eml
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d3c963fc903c165aa06e285b1a17558a596928ff
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.cpp
@@ -0,0 +1,371 @@
+// ============================================================================
+// Copyright 2016-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EtpClient12.h"
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace oes::apps::eml {
+
+using oes::eml::etp12::rpcFor;
+using oes::eml::etp12::Datatypes::ErrorInfo;
+
+//============================================================================
+
+EtpClient12::EtpClient12(
+ const EmlAppContext& ctx
+) : ctx_(ctx)
+{
+}
+
+bool EtpClient12::config() {
+ const std::string& timeouts_spec = ctx_.stringArg("timeout");
+ auto timeouts = oes::core::parseCsvSimpleDurationPair(timeouts_spec);
+ const std::chrono::milliseconds zero_ms(0);
+ if (timeouts.first < zero_ms || timeouts.second < zero_ms) {
+ ctx_.err() << "Error: Invalid --timeout. Unknown time unit (accepts s, ms, and min only)." << std::endl;
+ return false;
+ }
+ if (timeouts.first == zero_ms || timeouts.second == zero_ms) {
+ ctx_.err() << "Error: Invalid --timeout. Cannot be empty or zero." << std::endl;
+ return false;
+ }
+ connect_timeout_ = timeouts.first;
+ put_get_timeout_ = timeouts.second;
+
+ cfg_.verbose_ = ctx_.verbose;
+ cfg_.debug_ = ctx_.debug;
+ cfg_.quiet_ = ctx_.quiet;
+
+ if (!ctx_.hasArg("server-url")) {
+ ctx_.err() << "Error: Missing --server-url option" << std::endl;
+ return false;
+ }
+
+ if (ctx_.hasArg("etp-trace")) {
+ cfg_.enable_tracing_ = true;
+ cfg_.trace_file_ = ctx_.stringArg("etp-trace");
+ }
+
+ std::string space_path = ctx_.stringArg("space-path");
+
+ const std::string& mms_spec = ctx_.stringArg("max-msg-size");
+ if (!mms_spec.empty()) try {
+ cfg_.max_mgs_size_ = parseByteSize(mms_spec);
+ } catch (const std::runtime_error& ex) {
+ ctx_.err() << "Error: Invalid --max-msg-size option: " << ex.what() << std::endl;
+ return false;
+ }
+
+ const int mqd = ctx_.args["msg-queue-depth"].as();
+ cfg_.msg_queue_depth_ = (mqd >= 0)? mqd: -mqd;
+ cfg_.advertise_client_queue_depth_ = (mqd >= 0);
+
+ if (!(0 < cfg_.msg_queue_depth_ && cfg_.msg_queue_depth_ <= 100)) {
+ ctx_.err() << "Error: Invalid --msg-queue-depth option: Not in range [1, 100]" << std::endl;
+ return false;
+ }
+
+ auto warnWhenSet = [this](std::vector args) {
+ for (const auto& arg : args) {
+ if (ctx_.hasArg(arg)) {
+ ctx_.out() << "Warning: Option --" << arg << " ignored" << std::endl;
+ }
+ }
+ };
+ auto warnWhenEmpty = [this](std::vector args) {
+ for (const auto& arg : args) {
+ if (ctx_.stringArg(arg).empty()) {
+ ctx_.out() << "Warning: No --" << arg << " option" << std::endl;
+ }
+ }
+ };
+
+ std::string auth = ctx_.stringArg("auth");
+
+ if (auth == "auto") {
+ if (ctx_.hasArg("jwt-token") || ctx_.hasArg("jwt-secret")) {
+ auth = "bearer";
+ } else if (ctx_.hasArg("username") || ctx_.hasArg("password")) {
+ auth = "basic";
+ } else {
+ auth = "none";
+ }
+ }
+
+ if (auth == "none") {
+ warnWhenSet({ "username", "password", "jwt-secret", "jwt-token" });
+ if (ctx_.stringArg("auth") != "none") { // i.e. not --auth=auto
+ ctx_.msg() << "Warning: No authentication, since no --username or --password" << std::endl;
+ }
+ cfg_.auth_hdr_.clear();
+ } else if (auth == "basic") {
+ warnWhenSet({ "jwt-secret", "jwt-token" });
+ warnWhenEmpty({ "username", "password" });
+
+ std::string username = ctx_.stringArg("username");
+ std::string password = ctx_.stringArg("password");
+ cfg_.withBasicAuth(username, password);
+ } else if (auth == "bearer") {
+ if (ctx_.hasArg("jwt-token")) {
+ warnWhenSet({ "username", "password", "jwt-secret" });
+
+ cfg_.withJwtAuth(ctx_.stringArg("jwt-token"));
+ } else if (ctx_.hasArg("jwt-secret")) {
+ warnWhenSet({ "jwt-token" });
+ warnWhenEmpty({ "username", "password" });
+
+ std::string username = ctx_.stringArg("username");
+ std::string password = ctx_.stringArg("password");
+ cfg_.withJwtAuth(username, password, ctx_.stringArg("jwt-secret"));
+ } else {
+ ctx_.err() << "Error: Bearer authentication requires --jwt-secret or --jwt-token" << std::endl;
+ return false;
+ }
+ } else {
+ assert(auth != "auto");
+ ctx_.err() << "Error: Invalid --auth option: "
+ "Use 'auto', 'none', 'basic, or 'bearer'" << std::endl;
+ return false;
+ }
+
+ if (ctx_.hasArg("no-http-headers")) {
+ cfg_.emulate_browser_client_ = true;
+ }
+
+ if (ctx_.hasArg("acknowledge")) {
+ cfg_.acknowledge_ = true;
+ }
+
+ cfg_.compress_ = ctx_.hasArg("compress");
+ if (cfg_.compress_) {
+ std::string zcomp_csv = ctx_.stringArg("compress");
+ std::vector zcomp_args;
+ boost::split(zcomp_args, zcomp_csv, boost::algorithm::is_any_of(",;|"));
+ // Check compressor names perhaps
+ cfg_.zcompressors_ = zcomp_args;
+ }
+
+ if (ctx_.hasArg("shun-protocols")) {
+ // Split the arg's value
+ std::string shun_csv = ctx_.stringArg("shun-protocols");
+ std::vector shun_args;
+ boost::split(shun_args, shun_csv, boost::algorithm::is_any_of(",;|"));
+
+ // Build the set of valid values to be shunned.
+ // Accept Store (the name), Store(4) (the label), 4 (just the ID).
+ const auto& proto_map = oes::eml::etp12::protocols();
+ using ProtoEnum = oes::eml::etp12::Datatypes::Protocol;
+ std::map accepted;
+ for (const auto& entry : proto_map) {
+ const oes::eml::etp12::ProtocolInfo& pinfo = entry.second;
+ const auto proto_id = ProtoEnum(entry.first);
+ if (proto_id != ProtoEnum::eCore) {
+ accepted.emplace(boost::to_lower_copy(pinfo.name_), proto_id);
+ accepted.emplace(boost::to_lower_copy(pinfo.label()), proto_id);
+ accepted.emplace(std::to_string(int(proto_id)), proto_id);
+ }
+ }
+ // Add my own "shorter" aliases too
+ accepted.emplace("types", ProtoEnum::eSupportedTypes);
+ accepted.emplace("disco", ProtoEnum::eDiscovery);
+ accepted.emplace("trans", ProtoEnum::eTransaction);
+ accepted.emplace("space", ProtoEnum::eDataspace);
+ accepted.emplace("array", ProtoEnum::eDataArray);
+
+ std::set shunned;
+ for (const std::string& arg : shun_args) {
+ auto shun_arg = boost::to_lower_copy(arg);
+ boost::trim(shun_arg);
+ auto found = accepted.find(shun_arg);
+ if (found != accepted.end()) {
+ ProtoEnum proto_id = found->second;
+ if (proto_id == ProtoEnum::eCore) {
+ ctx_.err() << "Error: Cannot shun Core(0) protocol" << std::endl;
+ return false;
+ }
+ if (shunned.emplace(proto_id).second) {
+ auto proto_found = proto_map.find(int(proto_id));
+ assert(proto_found != proto_map.end());
+ ctx_.dbg() << "Shun-Protocol " << proto_found->second.label() << std::endl;
+ }
+ } else {
+ ctx_.err() << "Error: Cannot shun unknown protocol: " << arg << std::endl;
+ return false;
+ }
+ }
+
+ if (shunned.empty()) {
+ ctx_.err() << "Error: Invalid --shun-protocols option: empty" << std::endl;
+ return false;
+ }
+
+ std::vector requested;
+ for (auto proto : cfg_.requested_protocols_) {
+ if (shunned.find(proto) == shunned.end()) {
+ requested.emplace_back(proto);
+ } else {
+ auto proto_found = proto_map.find(int(proto));
+ assert(proto_found != proto_map.end());
+ ctx_.out() << "Warning: Shunning " << proto_found->second.label()
+ << " protocol" << std::endl;
+ }
+ }
+ cfg_.requested_protocols_.swap(requested);
+
+ if (cfg_.requested_protocols_.empty()) {
+ ctx_.out() << "Warning: All requested protocols were shunned" << std::endl;
+ return false;
+ }
+ }
+
+ cfg_.server_url_ = ctx_.stringArg("server-url");
+
+ return true;
+}
+
+bool EtpClient12::connect() {
+ if (!config()) {
+ return false;
+ }
+
+ clt_.reset(new etp::Client(cfg_));
+ ErrorInfo err;
+ if (!clt_->connect(connect_timeout_, &err)) {
+ ctx_.err() << "Error: Connect: " << cfg_.server_url_
+ << " #" << err.m_code << ": " << err.m_message << std::endl;
+ return false;
+ }
+ return true;
+}
+
+std::vector EtpClient12::globSpaces(
+ const std::string& pattern, size_t* total
+) {
+ return globSpacesSince(pattern, 0, total);
+}
+
+std::vector EtpClient12::globSpacesSince(
+ const std::string& pattern, int64_t lastChangedFilter, size_t* total
+) {
+ auto* proto = space_proto();
+ if (!proto) {
+ throw ActionFailed();
+ }
+
+ auto rpc = rpcFor(proto, &etp::proto12::IDataspace::GetDataspaces);
+
+ if (lastChangedFilter) {
+ rpc.req_.m_storeLastWriteFilter = lastChangedFilter;
+ }
+
+ auto ok = rpc.call(ctx_.err(), put_get_timeout_);
+ if (!ok.first) {
+ throw ActionFailed();
+ }
+
+ auto& spaces = rpc.res_.m_dataspaces;
+ if (total) {
+ *total = spaces.size();
+ }
+
+ if (!pattern.empty()) {
+ // TODO No patern matching for now
+ // Exact match
+ spaces.erase(
+ std::remove_if(spaces.begin(), spaces.end(), [pattern](const Dataspace& ds) {
+ return pattern != ds.m_path;
+ }),
+ spaces.end()
+ );
+ }
+
+ return spaces;
+}
+
+//============================================================================
+
+options_description connect_options(unsigned terminalWidth, EtpDefaults defaults) {
+ const int queue_sign = defaults.advertise_client_queue_depth_? +1: -1;
+
+ const std::string& dT = defaults.timeout_;
+ const std::string& dZ = defaults.zcompressors_;
+ const std::string& dM = defaults.max_msg_size_;
+ const int dB = defaults.max_batch_size_;
+ const int dQ = defaults.msg_queue_depth_ * queue_sign;
+
+ auto zarg = defaults.compress_? txtArg(dZ): txtArgOpt(dZ);
+ auto zcmt = "Set Compress-Body flag on all ETP requests\n"
+ "Specify compressor(s) (Default: " + dZ + ")";
+
+ options_description opts("Connect Options", terminalWidth);
+ opts.add_options()
+ ("server-url,S", txtArg(), "ETP 1.2 server URL")
+ ("etp-trace", txtArgOpt(), "Trace ETP messages to file\n"
+ "Filename inferred if empty or directory")
+
+ ("timeout", txtArg(dT), "ETP timeouts for CONNECT and GET/PUT requests")
+ ("max-msg-size,M", txtArg(dM), "Maximum WebSocket message size(s) in bytes")
+ ("max-batch-size,B", intArg(dB), "Maximum parts per multi-part message")
+ ("msg-queue-depth,Q", intArg(dQ), "Maximum concurrent (in-flight) messages")
+ ("acknowledge", "Set Acknowledge flag on all ETP requests")
+
+ // On client-side, compression is opt-in, while
+ // on server-side, compression is always active, BUT depends on client
+ ("compress,Z", zarg, zcmt.c_str())
+
+ // Allows client-side to simulate a server not implementing a protocol
+ // the server does in fact support, by not requesting it. For testing only.
+ // E.g. `space --stats` normally uses SupportedTypes. Shunning SupportedTypes
+ // allows testing its (slower) fallback using Discovery protocol instead.
+ ("shun-protocols", txtArg(), "Force client to ignore those protocols")
+ // ("shun-extensions", txtArg(), "Force client to ignore those extensions") // TODO
+ // ("hide-capabilities", txtArg(), "Force endpoint to hide its capabilities") // TODO
+ ;
+ return opts;
+}
+
+//============================================================================
+
+options_description authenticate_options(unsigned terminalWidth) {
+ options_description opts("Authentication Options", terminalWidth);
+ opts.add_options()
+ // Auth-related options
+ ("auth,A", txtArg("auto"), "Authentication method: 'auto, 'none', 'basic', 'bearer'.")
+ ("username,u", txtArg(), "User's name to authenticate with.")
+ ("password,p", txtArg(), "User's password to authenticate with.")
+ ("jwt-token,T", txtArg(), "Explicit JWT token. Used as-is.")
+ ("jwt-secret,k", txtArg(), "JWT Signing Key. Uses Basic Authentication if omitted.")
+ ("no-http-headers", "Use URL for AuthN. Emulates Browser-based clients.")
+ ;
+ return opts;
+}
+
+//============================================================================
+
+} // namespace oes::apps::eml
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.h b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.h
new file mode 100644
index 0000000000000000000000000000000000000000..44b059ea7841b67c2732c839a189f29de14bcac0
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/EtpClient12.h
@@ -0,0 +1,96 @@
+// ============================================================================
+// Copyright 2016-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+#pragma once
+
+#include "EmlAppContext.h"
+
+#include
+#include
+
+#include
+#include
+
+namespace oes::apps::eml {
+
+namespace etp = oes::eml::etp12;
+using etp::Datatypes::Object::Dataspace;
+
+//============================================================================
+
+struct EtpDefaults {
+ std::string timeout_ = "10s,10s";
+ std::string max_msg_size_ = "1MB";
+
+ int max_batch_size_ = 100;
+ int msg_queue_depth_ = 5;
+
+ bool compress_ = false;
+ std::string zcompressors_ = "gzip"; // TODO: lz4, zstd
+
+ // i.e. whether to enabling blocking server-side on too many concurrent messages
+ // (when false), or instead error-out (when true), the latter implying the client
+ // paces itself to not go over the server-advertised max_queue_size.
+ bool advertise_client_queue_depth_ = true;
+};
+
+options_description connect_options(unsigned terminalWidth, EtpDefaults defaults = {});
+options_description authenticate_options(unsigned terminalWidth);
+
+//============================================================================
+
+struct EtpClient12 {
+ const EmlAppContext& ctx_;
+ etp::ClientConfig cfg_;
+ std::unique_ptr clt_;
+
+ std::chrono::milliseconds connect_timeout_;
+ std::chrono::milliseconds put_get_timeout_;
+
+ EtpClient12(const EmlAppContext& ctx);
+
+ bool config();
+ bool connect();
+
+ std::vector globSpaces(
+ const std::string& pattern, size_t* total = nullptr
+ );
+ std::vector globSpacesSince(
+ const std::string& pattern, int64_t lastChangedFilter = 0, size_t* total = nullptr
+ );
+
+ template
+ TProto* get_proto() {
+ assert(clt_);
+ if (auto* proto = clt_->get_proto()) {
+ return proto;
+ }
+ ctx_.err() << "Error: " << cfg_.server_url_ << ": No "
+ << TProto::protocolName() << " protocol support" << std::endl;
+ return nullptr;
+ }
+
+ auto core_proto() { return get_proto(); }
+ auto store_proto() { return get_proto(); }
+ auto array_proto() { return get_proto(); }
+ auto disco_proto() { return get_proto(); }
+ auto types_proto() { return get_proto(); }
+ auto space_proto() { return get_proto(); }
+ auto trans_proto() { return get_proto(); }
+};
+
+//============================================================================
+
+} // namespace oes::apps::eml
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/Hdf5Cmds.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/Hdf5Cmds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8c536b07b37135fc02a2bbab2a75745316d6a517
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/Hdf5Cmds.cpp
@@ -0,0 +1,1137 @@
+// ============================================================================
+// Copyright 2020-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EmlAppContext.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using std::endl;
+using namespace oes::eml;
+using namespace oes::apps::eml;
+namespace po = boost::program_options;
+namespace fs = std::filesystem;
+
+namespace {
+
+using SimpleTimer = oes::core::HighResTimer;
+using oes::core::thou;
+
+enum class Hdf5ObjType : unsigned char {
+ eUNKNOWN = 0,
+ eGROUP,
+ eDATASET,
+ eNAMED_DATATYPE,
+};
+std::array hdf5ObTypes() {
+ return {
+ Hdf5ObjType::eUNKNOWN,
+ Hdf5ObjType::eGROUP,
+ Hdf5ObjType::eDATASET,
+ Hdf5ObjType::eNAMED_DATATYPE,
+ };
+}
+const char* hdf5ObTypeToString(Hdf5ObjType type) {
+ switch (type) {
+ case Hdf5ObjType::eUNKNOWN: return "Unknown Object";
+ case Hdf5ObjType::eGROUP: return "Group";
+ case Hdf5ObjType::eDATASET: return "Dataset";
+ case Hdf5ObjType::eNAMED_DATATYPE: return "Named Datatype";
+ }
+ assert(false);
+ return "Unknown Object";
+}
+
+enum class Hdf5DataType : unsigned char {
+ eUNKNOWN = 0,
+ eINTEGER,
+ eFLOAT,
+ eTIME,
+ eSTRING,
+ eBITFIELD,
+ eOPAQUE,
+ eCOMPOUND,
+ eREFERENCE,
+ eENUM,
+ eVLEN,
+ eARRAY,
+};
+std::array hdf5DataTypes() {
+ return {
+ Hdf5DataType::eUNKNOWN,
+ Hdf5DataType::eINTEGER,
+ Hdf5DataType::eFLOAT,
+ Hdf5DataType::eTIME,
+ Hdf5DataType::eSTRING,
+ Hdf5DataType::eBITFIELD,
+ Hdf5DataType::eOPAQUE,
+ Hdf5DataType::eCOMPOUND,
+ Hdf5DataType::eREFERENCE,
+ Hdf5DataType::eENUM,
+ Hdf5DataType::eVLEN,
+ Hdf5DataType::eARRAY,
+ };
+}
+const char* hdf5DataTypeToString(Hdf5DataType type) {
+ switch (type) {
+ case Hdf5DataType::eUNKNOWN: return "Unknown";
+ case Hdf5DataType::eINTEGER: return "Integer";
+ case Hdf5DataType::eFLOAT: return "Floating-Point";
+ case Hdf5DataType::eTIME: return "Time";
+ case Hdf5DataType::eSTRING: return "String";
+ case Hdf5DataType::eBITFIELD: return "Bitfield";
+ case Hdf5DataType::eOPAQUE: return "Opaque";
+ case Hdf5DataType::eCOMPOUND: return "Compound";
+ case Hdf5DataType::eREFERENCE: return "Reference";
+ case Hdf5DataType::eENUM: return "Enum";
+ case Hdf5DataType::eVLEN: return "Variable-Length";
+ case Hdf5DataType::eARRAY: return "Array";
+ }
+ assert(false);
+ return "Unknown";
+}
+
+enum class Hdf5NumericType : unsigned char {
+ eNIL = 0,
+ eI08,
+ eU08,
+ eI16,
+ eU16,
+ eI32,
+ eU32,
+ eI64,
+ eU64,
+ eF32,
+ eF64,
+};
+std::array hdf5NumTypes() {
+ return {
+ Hdf5NumericType::eI08,
+ Hdf5NumericType::eU08,
+ Hdf5NumericType::eI16,
+ Hdf5NumericType::eU16,
+ Hdf5NumericType::eI32,
+ Hdf5NumericType::eU32,
+ Hdf5NumericType::eI64,
+ Hdf5NumericType::eU64,
+ Hdf5NumericType::eF32,
+ Hdf5NumericType::eF64,
+ };
+}
+const char* hdf5NumTypeToString(Hdf5NumericType type) {
+ switch (type) {
+ case Hdf5NumericType::eNIL: break;
+ case Hdf5NumericType::eI08: return "char/bool";
+ case Hdf5NumericType::eU08: return "byte";
+ case Hdf5NumericType::eI16: return "short";
+ case Hdf5NumericType::eU16: return "ushort";
+ case Hdf5NumericType::eI32: return "int";
+ case Hdf5NumericType::eU32: return "uint";
+ case Hdf5NumericType::eI64: return "long";
+ case Hdf5NumericType::eU64: return "ulong";
+ case Hdf5NumericType::eF32: return "float";
+ case Hdf5NumericType::eF64: return "double";
+ }
+ assert(false);
+ return "Unknown";
+}
+
+// TODO
+#if 0
+typedef enum H5D_layout_t {
+ H5D_LAYOUT_ERROR = -1,
+
+ H5D_COMPACT = 0, /*raw data is very small */
+ H5D_CONTIGUOUS = 1, /*the default */
+ H5D_CHUNKED = 2, /*slow and fancy */
+ H5D_VIRTUAL = 3, /*actual data is stored in other datasets */
+ H5D_NLAYOUTS = 4 /*this one must be last! */
+} H5D_layout_t;
+
+/* Types of chunk index data structures */
+typedef enum H5D_chunk_index_t {
+ H5D_CHUNK_IDX_BTREE = 0, /* v1 B-tree index (default) */
+ H5D_CHUNK_IDX_SINGLE = 1, /* Single Chunk index (cur dims[]=max dims[]=chunk dims[]; filtered & non-filtered) */
+ H5D_CHUNK_IDX_NONE = 2, /* Implicit: No Index (H5D_ALLOC_TIME_EARLY, non-filtered, fixed dims) */
+ H5D_CHUNK_IDX_FARRAY = 3, /* Fixed array (for 0 unlimited dims) */
+ H5D_CHUNK_IDX_EARRAY = 4, /* Extensible array (for 1 unlimited dim) */
+ H5D_CHUNK_IDX_BT2 = 5, /* v2 B-tree index (for >1 unlimited dims) */
+ H5D_CHUNK_IDX_NTYPES /* This one must be last! */
+} H5D_chunk_index_t;
+
+/* Values for the space allocation time property */
+typedef enum H5D_alloc_time_t {
+ H5D_ALLOC_TIME_ERROR = -1,
+ H5D_ALLOC_TIME_DEFAULT = 0,
+ H5D_ALLOC_TIME_EARLY = 1,
+ H5D_ALLOC_TIME_LATE = 2,
+ H5D_ALLOC_TIME_INCR = 3
+} H5D_alloc_time_t;
+#endif//0
+
+struct ScopedH5Dataset {
+ hid_t dataset_ = -1;
+ hid_t filespace_ = -1;
+ hid_t data_type_ = -1;
+
+ ~ScopedH5Dataset() {
+ if (data_type_ >= 0) {
+ H5Tclose(data_type_);
+ }
+ if (filespace_ >= 0) {
+ H5Sclose(filespace_);
+ }
+ if (dataset_ >= 0) {
+ H5Dclose(dataset_);
+ }
+ }
+
+ bool read(std::vector& buf) const {
+ // TODO: Try disable chunk caching, H5Pset_chunk_cache
+ // to avoid using 1MB per dataset, even after we close it.
+ herr_t read_rc = H5Dread(
+ dataset_, data_type_, H5S_ALL,
+ filespace_, H5P_DEFAULT, buf.data()
+ );
+ return read_rc >= 0;
+ };
+
+ bool read(
+ std::vector& buf,
+ const std::vector& starts,
+ const std::vector& counts
+ ) const {
+ herr_t rc = H5Sselect_hyperslab(
+ filespace_, H5S_SELECT_SET,
+ starts.data(), nullptr, counts.data(), nullptr
+ );
+ if (rc >= 0) {
+ H5::DataSpace mem_space;
+ hsize_t slab_size = etp12::productOf(counts);
+ mem_space.setExtentSimple(1, &slab_size, &slab_size);
+ assert(slab_size <= buf.capacity());
+ buf.resize(slab_size);
+
+ rc = H5Dread(
+ dataset_, data_type_, mem_space.getId(),
+ filespace_, H5P_DEFAULT, buf.data()
+ );
+ }
+ return rc >= 0;
+ };
+};
+
+constexpr int max_rank = 4;
+
+struct Hdf5ObjInfo {
+ std::string name_;
+
+ Hdf5ObjType type_ = Hdf5ObjType::eUNKNOWN;
+ Hdf5DataType data_type_ = Hdf5DataType::eUNKNOWN;
+ unsigned char type_bytes_ = 0;
+ bool type_sign_ = true;
+
+ unsigned char rank_ = 0;
+ std::array dims_{};
+
+ unsigned header_size_ = 0;
+ unsigned attr_count_ = 0;
+
+ size_t size_ = 0;
+ size_t attr_size_ = 0;
+ size_t storage_size_ = 0;
+
+
+ bool is_numeric() const {
+ switch (data_type_) {
+ case Hdf5DataType::eINTEGER:
+ return 1 <= type_bytes_ && type_bytes_ <= 8;
+ case Hdf5DataType::eFLOAT:
+ return type_bytes_ == 4 || type_bytes_ == 8;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ Hdf5NumericType numeric_type() const {
+ switch (data_type_) {
+ case Hdf5DataType::eINTEGER:
+ if (type_sign_) {
+ switch (type_bytes_) {
+ case 1: return Hdf5NumericType::eI08;
+ case 2: return Hdf5NumericType::eI16;
+ case 4: return Hdf5NumericType::eI32;
+ case 8: return Hdf5NumericType::eI64;
+ default: return Hdf5NumericType::eNIL;
+ }
+ } else {
+ switch (type_bytes_) {
+ case 1: return Hdf5NumericType::eU08;
+ case 2: return Hdf5NumericType::eU16;
+ case 4: return Hdf5NumericType::eU32;
+ case 8: return Hdf5NumericType::eU64;
+ default: return Hdf5NumericType::eNIL;
+ }
+ }
+ break; // unreachable
+ case Hdf5DataType::eFLOAT:
+ if (type_bytes_ == 4) {
+ return Hdf5NumericType::eF32;
+ } else if (type_bytes_ == 8) {
+ return Hdf5NumericType::eF64;
+ }
+ break; // unreachable
+ default:
+ break;
+ }
+ return Hdf5NumericType::eNIL;
+ };
+
+ std::string dims() const {
+ std::ostringstream oss;
+ for (unsigned char i = 0; i < rank_; ++i) {
+ oss << thou(dims_[i]) << " x ";
+ }
+ auto s = oss.str();
+ if (!s.empty()) {
+ s.resize(s.size() - 3);
+ }
+ return s;
+ }
+
+ size_t uncompressed_bytes() const {
+ size_t full_size = type_bytes_;
+ for (unsigned char i = 0; i < rank_; ++i) {
+ full_size *= dims_[i];
+ }
+ return full_size;
+ }
+
+ std::string desc(bool ignore_compression_factor = false) const {
+ const auto usize = uncompressed_bytes();
+ return fmt::format(
+ "{}D - {} - {} ({} bytes, {:.1f}x)",
+ rank_, dims(), is_numeric()?
+ hdf5NumTypeToString(numeric_type()):
+ hdf5DataTypeToString(data_type_),
+ thou(usize), ignore_compression_factor?
+ 1.0: (1.0 * usize / storage_size_)
+ );
+ }
+
+ std::unique_ptr open(hid_t file_id) const {
+ assert(!name_.empty());
+
+ auto res = std::make_unique();
+
+ res->dataset_ = H5Dopen2(file_id, name_.c_str(), H5P_DEFAULT);
+ if (res->dataset_ < 0) {
+ return nullptr;
+ }
+
+ res->filespace_ = H5Dget_space(res->dataset_);
+ if (res->filespace_ < 0) {
+ return nullptr;
+ }
+
+ res->data_type_ = H5Dget_type(res->dataset_);
+ if (res->data_type_ < 0) {
+ return nullptr;
+ }
+
+ return res;
+ }
+};
+
+struct Hdf5FileInfo {
+ bool datasets_only_ = false;
+ bool get_dataspace_ = false;
+ std::vector objects_;
+
+ size_t size_per_type_[int(Hdf5ObjType::eNAMED_DATATYPE) + 1] = { 0 };
+ size_t count_per_type_[int(Hdf5ObjType::eNAMED_DATATYPE) + 1] = { 0 };
+ size_t header_size_per_type_[int(Hdf5ObjType::eNAMED_DATATYPE) + 1] = { 0 };
+ size_t attr_count_per_type_[int(Hdf5ObjType::eNAMED_DATATYPE) + 1] = { 0 };
+ size_t count_per_data_type_[int(Hdf5DataType::eARRAY) + 1] = { 0 };
+ size_t count_per_rank_and_num_type_[max_rank + 1][int(Hdf5NumericType::eF64) + 1] = { 0 };
+ size_t count_per_rank_[max_rank + 1] = { 0 };
+ size_t dataset_storage_size_ = 0;
+ size_t dataset_uncompressed_size_ = 0;
+
+ size_t sizeOf(Hdf5ObjType type) const {
+ return size_per_type_[int(type)];
+ }
+ size_t countOf(Hdf5ObjType type) const {
+ return count_per_type_[int(type)];
+ }
+ size_t attrCountOf(Hdf5ObjType type) const {
+ return attr_count_per_type_[int(type)];
+ }
+ size_t headerSizeOf(Hdf5ObjType type) const {
+ return header_size_per_type_[int(type)];
+ }
+};
+
+static herr_t acceptHdf5ObjInfo(
+ hid_t loc_id, const char* name, const H5O_info_t* linfo, void* opdata
+) {
+ const bool is_dataset = (linfo->type == H5O_TYPE_DATASET);
+
+ Hdf5FileInfo* file_info = reinterpret_cast(opdata);
+ if (file_info->datasets_only_ && !is_dataset) {
+ return 0; // i.e. continue iterating / visiting
+ }
+
+ Hdf5ObjInfo& oinfo = file_info->objects_.emplace_back();
+
+ if (name[0] == '.' && name[1] == '\0') {
+ oinfo.name_.clear(); // the root. We'll prepend slash later
+ } else {
+ oinfo.name_.assign(name); // NOT prefixed with slash
+ }
+ oinfo.header_size_ = static_cast(linfo->hdr.space.total);
+ oinfo.attr_count_ = static_cast(linfo->num_attrs);
+
+ oinfo.size_ = linfo->meta_size.obj.heap_size + linfo->meta_size.obj.index_size;
+ oinfo.attr_size_ = linfo->meta_size.attr.heap_size + linfo->meta_size.attr.index_size;
+
+ switch (linfo->type) {
+ case H5O_TYPE_UNKNOWN: oinfo.type_ = Hdf5ObjType::eUNKNOWN; break;
+ case H5O_TYPE_GROUP: oinfo.type_ = Hdf5ObjType::eGROUP; break;
+ case H5O_TYPE_DATASET: oinfo.type_ = Hdf5ObjType::eDATASET; break;
+ case H5O_TYPE_NAMED_DATATYPE: oinfo.type_ = Hdf5ObjType::eNAMED_DATATYPE; break;
+ default: oinfo.type_ = Hdf5ObjType::eUNKNOWN; break;
+ }
+
+ const int idx = int(oinfo.type_);
+ file_info->size_per_type_ [idx] += (oinfo.size_ + oinfo.attr_size_);
+ file_info->count_per_type_ [idx] += 1;
+ file_info->header_size_per_type_ [idx] += oinfo.header_size_;
+ file_info->attr_count_per_type_ [idx] += oinfo.attr_count_;
+
+ if (file_info->get_dataspace_ && is_dataset) {
+ hid_t dataset = H5Dopen1(loc_id, name);
+ hid_t filespace = H5Dget_space(dataset);
+ oinfo.rank_ = static_cast(H5Sget_simple_extent_ndims(filespace));
+ if (oinfo.rank_ <= max_rank) {
+ hsize_t dims[max_rank]{};
+ hsize_t maxdims[max_rank]{};
+ H5Sget_simple_extent_dims(filespace, dims, maxdims);
+ for (auto i = 0; i < oinfo.rank_; ++i) {
+ oinfo.dims_[i] = static_cast(dims[i]);
+ }
+ }
+ oinfo.storage_size_ = H5Dget_storage_size(dataset);
+
+ hid_t data_type = H5Dget_type(dataset);
+ const size_t tsize = H5Tget_size(data_type);
+ oinfo.type_bytes_ = (tsize > 255)? 255: (unsigned char)(tsize);
+ oinfo.type_sign_ = H5Tget_sign(data_type)? true: false;
+
+ switch (H5Tget_class(data_type)) {
+ case H5T_NO_CLASS: oinfo.data_type_ = Hdf5DataType::eUNKNOWN; break;
+ case H5T_INTEGER: oinfo.data_type_ = Hdf5DataType::eINTEGER; break;
+ case H5T_FLOAT: oinfo.data_type_ = Hdf5DataType::eFLOAT; break;
+ case H5T_TIME: oinfo.data_type_ = Hdf5DataType::eTIME; break;
+ case H5T_STRING: oinfo.data_type_ = Hdf5DataType::eSTRING; break;
+ case H5T_BITFIELD: oinfo.data_type_ = Hdf5DataType::eBITFIELD; break;
+ case H5T_OPAQUE: oinfo.data_type_ = Hdf5DataType::eOPAQUE; break;
+ case H5T_COMPOUND: oinfo.data_type_ = Hdf5DataType::eCOMPOUND; break;
+ case H5T_REFERENCE: oinfo.data_type_ = Hdf5DataType::eREFERENCE; break;
+ case H5T_ENUM: oinfo.data_type_ = Hdf5DataType::eENUM; break;
+ case H5T_VLEN: oinfo.data_type_ = Hdf5DataType::eVLEN; break;
+ case H5T_ARRAY: oinfo.data_type_ = Hdf5DataType::eARRAY; break;
+ case H5T_NCLASSES: oinfo.data_type_ = Hdf5DataType::eUNKNOWN; break;
+ }
+
+ H5Dclose(dataset);
+
+ file_info->count_per_rank_[oinfo.rank_] += 1;
+ file_info->count_per_data_type_[int(oinfo.data_type_)] += 1;
+ file_info->dataset_storage_size_ += oinfo.storage_size_;
+ file_info->dataset_uncompressed_size_ += oinfo.uncompressed_bytes();
+ if (oinfo.is_numeric()) {
+ file_info->count_per_rank_and_num_type_[oinfo.rank_][int(oinfo.numeric_type())] += 1;
+ }
+ }
+
+ return 0; // i.e. continue iterating / visiting
+}
+
+bool hdf5_info(const EmlAppContext& ctx) {
+ std::string h5_filename = ctx.stringArg(ctx.action());
+
+ const H5::FileAccPropList& fapl = H5::FileAccPropList();
+ H5::H5File file(h5_filename, H5F_ACC_RDONLY, H5::FileCreatPropList::DEFAULT, fapl);
+
+ Hdf5FileInfo file_info;
+ const herr_t rc = H5Ovisit(
+ file.getId(), H5_INDEX_NAME, H5_ITER_NATIVE, &acceptHdf5ObjInfo, &file_info
+ );
+ if (rc < 0) {
+ ctx.err() << "Error: Failed to scan HDF5 file: " << h5_filename << endl;
+ return false;
+ }
+
+ auto& os = ctx.out();
+ for (auto type : hdf5ObTypes()) {
+ if (size_t n = file_info.countOf(type)) {
+ os << fmt::format(
+ "{:>18} {}{}", thou(n), hdf5ObTypeToString(type), ((n> 1)? "s": "")
+ ) << endl;
+ }
+ }
+ for (auto type : hdf5ObTypes()) {
+ if (size_t n = file_info.attrCountOf(type)) {
+ os << fmt::format(
+ "{:>18} {} attribute{}", thou(n), hdf5ObTypeToString(type), ((n> 1)? "s": "")
+ ) << endl;
+ }
+ }
+ for (auto type : hdf5ObTypes()) {
+ if (size_t n = file_info.headerSizeOf(type)) {
+ os << fmt::format(
+ "{:>18} {} header byte{}", thou(n), hdf5ObTypeToString(type), ((n> 1)? "s": "")
+ ) << endl;
+ }
+ }
+ for (auto type : hdf5ObTypes()) {
+ if (size_t n = file_info.sizeOf(type)) {
+ os << fmt::format(
+ "{:>18} {} (index & heap) byte{}", thou(n), hdf5ObTypeToString(type), ((n> 1)? "s": "")
+ ) << endl;
+ }
+ }
+
+ // counts of values of given byte-size-range, by increasing powers of two.
+ // from 0, 1, 2, 4, 8, 16, 32, 64, ... , 1KB, ... 1MB, ..., 32MB, etc...
+ std::vector bytes_histogram_;
+ std::vector datasets;
+ bytes_histogram_.reserve(32);
+ datasets.reserve(file_info.countOf(Hdf5ObjType::eDATASET));
+
+ std::map group2count;
+ std::map group_name2count;
+ std::map dataset_name2count;
+ for (const Hdf5ObjInfo& oinfo : file_info.objects_) {
+ size_t last_slash = oinfo.name_.rfind('/');
+ if (last_slash != std::string::npos) {
+ ++group2count[oinfo.name_.substr(0, last_slash)];
+
+ if (oinfo.type_ == Hdf5ObjType::eDATASET) {
+ ++dataset_name2count[oinfo.name_.substr(last_slash + 1)];
+ } else {
+ ++group_name2count[oinfo.name_.substr(last_slash + 1)];
+ }
+ } else {
+ ++group2count[std::string()];
+ if (oinfo.type_ == Hdf5ObjType::eDATASET) {
+ ++dataset_name2count[std::string()];
+ } else {
+ ++group_name2count[std::string()];
+ }
+ }
+
+ if (oinfo.type_ == Hdf5ObjType::eDATASET) {
+ datasets.emplace_back(&oinfo);
+
+ // Not sure 0-size is normal...
+ const size_t bucket_idx = (oinfo.size_ == 0)? 0:
+ 1 + static_cast(std::log2(oinfo.size_));
+
+ if (bucket_idx >= bytes_histogram_.size()) {
+ bytes_histogram_.resize(bucket_idx + 1);
+ }
+ ++bytes_histogram_[bucket_idx];
+ }
+ }
+
+ const size_t topN = ctx.args["top"].as(); // 10 by default
+
+ os << fmt::format("\n{:>18} by 'overhead' size-range >>>", "<<< Datasets") << endl;
+ if (!bytes_histogram_.empty() && bytes_histogram_[0]) {
+ os << fmt::format("{:>18} empty", thou(bytes_histogram_[0])) << endl;
+ }
+ for (size_t i = 1; i < bytes_histogram_.size(); ++i) {
+ const size_t width = thou(size_t(pow(2, bytes_histogram_.size() - 1))).size();
+ const size_t count = bytes_histogram_[i];
+ std::ostream& os2 = count? os: ctx.msg();
+ os2 << fmt::format( // show empty range in verbose mode only
+ "{:>18} ({:>{}} - {:>{}}] bytes", thou(count),
+ thou(size_t(pow(2, i - 1))), width,
+ thou(size_t(pow(2, i))), width
+ ) << endl;
+ }
+
+ using map_entry = std::map::value_type;
+ std::vector dataset_names;
+ dataset_names.reserve(dataset_name2count.size());
+ for (const map_entry& entry : dataset_name2count) {
+ dataset_names.emplace_back(&entry);
+ }
+ const size_t topN_byName = std::min<>(topN, dataset_names.size());
+ std::partial_sort(
+ dataset_names.begin(), dataset_names.begin() + topN_byName, dataset_names.end(),
+ [](const map_entry* lhs, const map_entry* rhs) {
+ return lhs->second > rhs->second;
+ }
+ );
+ std::stable_sort( // show them alphabetically though, nicer
+ dataset_names.begin(), dataset_names.begin() + topN_byName,
+ [](const map_entry* lhs, const map_entry* rhs) {
+ return lhs->first < rhs->first;
+ }
+ );
+ os << fmt::format("\n{:>18} by name - Top {} >>>", "<<< Datasets", topN) << endl;
+ for (size_t i = 0; i < topN_byName; ++i) {
+ const map_entry& entry = *dataset_names[i];
+ os << fmt::format("{:>18} {}", thou(entry.second), entry.first) << endl;
+ }
+
+ const size_t topN_bySize = std::min<>(topN, datasets.size());
+ std::partial_sort(
+ datasets.begin(), datasets.begin() + topN_bySize, datasets.end(),
+ [](const Hdf5ObjInfo* lhs, const Hdf5ObjInfo* rhs) {
+ return lhs->size_ > rhs->size_;
+ }
+ );
+ os << fmt::format("\n{:>18} 'overhead' size - Top {} >>>", "<<< Dataset", topN) << endl;
+ for (size_t i = 0; i < topN_bySize; ++i) {
+ const Hdf5ObjInfo& oinfo = *datasets[i];
+ os << fmt::format("{:>18} {}", thou(oinfo.size_), oinfo.name_) << endl;
+ }
+
+ return true;
+}
+
+bool hdf5_stats(const EmlAppContext& ctx) {
+ std::string h5_filename = ctx.stringArg(ctx.action());
+
+ const H5::FileAccPropList& fapl = H5::FileAccPropList();
+ H5::H5File file(h5_filename, H5F_ACC_RDONLY, H5::FileCreatPropList::DEFAULT, fapl);
+
+ Hdf5FileInfo file_info;
+ file_info.get_dataspace_ = true;
+ const herr_t rc = H5Ovisit(
+ file.getId(), H5_INDEX_NAME, H5_ITER_NATIVE, &acceptHdf5ObjInfo, &file_info
+ );
+ if (rc < 0) {
+ ctx.err() << "Error: Failed to scan HDF5 file: " << h5_filename << endl;
+ return false;
+ }
+
+ auto& os = ctx.out();
+ os << fmt::format("{:>18} Dataset stored bytes", thou(file_info.dataset_storage_size_)) << endl;
+ os << fmt::format(
+ "{:>18} Dataset bytes un-chunked & un-compressed",
+ thou(file_info.dataset_uncompressed_size_)
+ ) << endl;
+ os << fmt::format(
+ "{:>17.1f}x Compression ratio (exclude metadata overhead)",
+ 1.0 * file_info.dataset_uncompressed_size_ / file_info.dataset_storage_size_
+ ) << endl;
+
+ os << fmt::format("\n{:>18} by rank >>>", "<<< Datasets") << endl;
+ for (int rank = 0; rank <= max_rank; ++rank) {
+ if (size_t n = file_info.count_per_rank_[rank]) {
+ assert(rank); // i.e. nothing at rank 0
+ os << fmt::format("{:>18} {}D", thou(n), rank) << endl;
+ }
+ }
+
+ os << fmt::format("\n{:>18} by datatype class >>>", "<<< Datasets") << endl;
+ for (Hdf5DataType dtype : hdf5DataTypes()) {
+ if (size_t n = file_info.count_per_data_type_[int(dtype)]) {
+ os << fmt::format("{:>18} {}", thou(n), hdf5DataTypeToString(dtype)) << endl;
+ }
+ }
+
+ os << fmt::format("\n{:>18} by rank & numeric type >>>", "<<< Datasets") << endl;
+ for (int rank = 1; rank <= max_rank; ++rank)
+ for (Hdf5NumericType ntype : hdf5NumTypes()) {
+ if (size_t n = file_info.count_per_rank_and_num_type_[rank][int(ntype)]) {
+ assert(rank); // i.e. nothing at rank 0
+ os << fmt::format("{:>18} {}D - {}", thou(n), rank, hdf5NumTypeToString(ntype)) << endl;
+ }
+ }
+
+ std::vector bytes_histogram_;
+ std::vector datasets;
+ bytes_histogram_.reserve(32);
+ datasets.reserve(file_info.countOf(Hdf5ObjType::eDATASET));
+ for (const Hdf5ObjInfo& oinfo : file_info.objects_) {
+ if (oinfo.type_ == Hdf5ObjType::eDATASET) {
+ datasets.emplace_back(&oinfo);
+
+ // Not sure 0-size is normal...
+ // e.g. 2D point array with 0x3 dimensions.
+ const size_t bucket_idx = (oinfo.storage_size_ == 0)? 0:
+ 1 + static_cast(std::log2(oinfo.storage_size_));
+
+ if (bucket_idx >= bytes_histogram_.size()) {
+ bytes_histogram_.resize(bucket_idx + 1);
+ }
+ ++bytes_histogram_[bucket_idx];
+ }
+ }
+
+ const size_t topN = ctx.args["top"].as(); // 10 by default
+
+ os << fmt::format("\n{:>18} by 'storage' size-range >>>", "<<< Datasets") << endl;
+ if (!bytes_histogram_.empty() && bytes_histogram_[0]) {
+ os << fmt::format("{:>18} empty", thou(bytes_histogram_[0])) << endl;
+ }
+ for (size_t i = 1; i < bytes_histogram_.size(); ++i) {
+ const size_t width = thou(size_t(pow(2, bytes_histogram_.size() - 1))).size();
+ const size_t count = bytes_histogram_[i];
+ std::ostream& os2 = count? os: ctx.msg();
+ os2 << fmt::format( // show empty range in verbose mode only
+ "{:>18} ({:>{}} - {:>{}}] bytes", thou(count),
+ thou(size_t(pow(2, i - 1))), width,
+ thou(size_t(pow(2, i))), width
+ ) << endl;
+ }
+
+ const size_t topN_bySize = std::min<>(topN, datasets.size());
+ std::partial_sort(
+ datasets.begin(), datasets.begin() + topN_bySize, datasets.end(),
+ [](const Hdf5ObjInfo* lhs, const Hdf5ObjInfo* rhs) {
+ return lhs->storage_size_ > rhs->storage_size_;
+ }
+ );
+ os << fmt::format("\n{:>18} 'storage' size - Top {} >>>", "<<< Dataset", topN) << endl;
+ for (size_t i = 0; i < topN_bySize; ++i) {
+ const Hdf5ObjInfo& oinfo = *datasets[i];
+ os << fmt::format(
+ "{:>18} {}\n{:>18} {}",
+ thou(oinfo.storage_size_), oinfo.name_, "", oinfo.desc()
+ ) << endl;
+ }
+
+ return true;
+}
+
+bool hdf5_diff(const EmlAppContext& ctx) {
+ size_t max_buf_size = 1024*1024;
+ const std::string& mbs_spec = ctx.stringArg("max-buf-size");
+ if (!mbs_spec.empty()) try {
+ max_buf_size = parseByteSize(mbs_spec);
+ } catch (const std::runtime_error& ex) {
+ ctx.err() << "Error: Invalid --max-buf-size option: " << ex.what() << std::endl;
+ return false;
+ }
+
+ std::string lhs_filename = ctx.stringArg(ctx.action());
+ std::string rhs_filename = ctx.stringArg("with");
+ if (rhs_filename.empty()) {
+ if (!ctx.hasArg("force")) {
+ ctx.err() << "Error: No --with file to diff against" << std::endl;
+ return false;
+ }
+ rhs_filename = lhs_filename;
+ }
+
+ const fs::path lhs_path(lhs_filename);
+ fs::file_status lhs_stat = fs::status(lhs_path);
+ if (!fs::is_regular_file(lhs_stat)) {
+ fmt::print(ctx.err(), "Error: File not found : {}\n", lhs_filename);
+ return false;
+ }
+
+ if (rhs_filename != lhs_filename) {
+ const fs::path rhs_path(rhs_filename);
+ fs::file_status rhs_stat = fs::status(rhs_path);
+ if (!fs::is_regular_file(rhs_stat)) {
+ fmt::print(ctx.err(), "Error: File not found : {}\n", rhs_filename);
+ return false;
+ }
+ } else {
+ fmt::print(ctx.out(), "Warning: Diff'ing {} with itself\n", lhs_filename);
+ }
+
+ const H5::FileAccPropList fapl;
+ H5::H5File lhs(lhs_filename, H5F_ACC_RDONLY, H5::FileCreatPropList::DEFAULT, fapl);
+ H5::H5File rhs(rhs_filename, H5F_ACC_RDONLY, H5::FileCreatPropList::DEFAULT, fapl);
+ const hid_t lhs_id = lhs.getId();
+ const hid_t rhs_id = rhs.getId();
+
+ Hdf5FileInfo lhs_info;
+ lhs_info.datasets_only_ = true;
+ lhs_info.get_dataspace_ = true;
+ const herr_t lhs_rc = H5Ovisit(
+ lhs_id, H5_INDEX_NAME, H5_ITER_NATIVE, &acceptHdf5ObjInfo, &lhs_info
+ );
+ if (lhs_rc < 0) {
+ fmt::print(ctx.err(), "Error: Failed to scan HDF5 file: {}\n", lhs_filename);
+ return false;
+ }
+ const int lhs_width = 2 + static_cast(std::log10(lhs_info.objects_.size()));
+ fmt::print(
+ ctx.msg(), "Scanned {:>{}} object in Left file\n",
+ thou(lhs_info.objects_.size()), lhs_width
+ );
+
+ Hdf5FileInfo rhs_info;
+ rhs_info.datasets_only_ = true;
+ rhs_info.get_dataspace_ = true;
+ const herr_t rhs_rc = H5Ovisit(
+ rhs_id, H5_INDEX_NAME, H5_ITER_NATIVE, &acceptHdf5ObjInfo, &rhs_info
+ );
+ if (rhs_rc < 0) {
+ fmt::print(ctx.err(), "Error: Failed to scan HDF5 file: {}\n", rhs_filename);
+ return false;
+ }
+ const int rhs_width = 2 + static_cast(std::log10(rhs_info.objects_.size()));
+ const int width = std::max<>(lhs_width, rhs_width);
+ fmt::print(
+ ctx.msg(), "Scanned {:>{}} object in Right file\n",
+ thou(rhs_info.objects_.size()), width
+ );
+
+ auto& lhs_objs = lhs_info.objects_;
+ auto& rhs_objs = rhs_info.objects_;
+
+ auto lt_by_name = [](const auto& lhs, const auto& rhs) {
+ return lhs.name_ < rhs.name_;
+ };
+ auto eq_by_name = [](const auto& lhs, const auto& rhs) {
+ return lhs.name_ == rhs.name_;
+ };
+
+ // Sort them lexicographically.
+ std::sort(lhs_objs.begin(), lhs_objs.end(), lt_by_name);
+ std::sort(rhs_objs.begin(), rhs_objs.end(), lt_by_name);
+
+ // There should not be any duplicate parts
+ assert(std::unique(lhs_objs.begin(), lhs_objs.end(), eq_by_name) == lhs_objs.end());
+ assert(std::unique(rhs_objs.begin(), rhs_objs.end(), eq_by_name) == rhs_objs.end());
+ OES_RELEASE_UNUSED(eq_by_name);
+
+ // Find the in parts not found in out-package.
+ std::vector missing_dsets;
+ std::set_difference(
+ lhs_objs.begin(), lhs_objs.end(),
+ rhs_objs.begin(), rhs_objs.end(),
+ std::inserter(missing_dsets, missing_dsets.begin()),
+ lt_by_name
+ );
+
+ // Find the out parts not found in in-package.
+ std::vector extra_dsets;
+ std::set_difference(
+ rhs_objs.begin(), rhs_objs.end(),
+ lhs_objs.begin(), lhs_objs.end(),
+ std::inserter(extra_dsets, extra_dsets.begin()),
+ lt_by_name
+ );
+
+ // Find the parts common to both packages.
+ std::vector common_dsets;
+ std::set_intersection(
+ lhs_objs.begin(), lhs_objs.end(),
+ rhs_objs.begin(), rhs_objs.end(),
+ std::back_inserter(common_dsets),
+ lt_by_name
+ );
+
+ const size_t match_count = common_dsets.size();
+ if (!match_count) {
+ fmt::print(
+ ctx.out(), "Nothing in common! ({} vs {} datasets)\n",
+ thou(lhs_objs.size()), thou(rhs_objs.size())
+ );
+ return true;
+ }
+
+ if (!missing_dsets.empty() || !extra_dsets.empty()) {
+ fmt::print(
+ ctx.out(), "{} vs {} datasets; {} common; {} missing; {} extra.\n",
+ thou(lhs_objs.size()), thou(rhs_objs.size()),
+ thou(match_count), thou(missing_dsets.size()), thou(extra_dsets.size())
+ );
+ }
+
+ const size_t topN = ctx.args["top"].as(); // 10 by default
+
+ if (!missing_dsets.empty() && topN) {
+ fmt::print(
+ ctx.msg(), "\nTop {} (by-name) missing datasets, out of {}:\n",
+ thou(std::min<>(topN, missing_dsets.size())), thou(missing_dsets.size())
+ );
+
+ size_t count = 0;
+ for (const Hdf5ObjInfo& obj : missing_dsets) {
+ fmt::print(ctx.msg(), "- {}: {}\n", obj.name_, obj.desc());
+ if (++count >= topN) {
+ break;
+ }
+ }
+ }
+
+ if (!extra_dsets.empty() && topN) {
+ fmt::print(
+ ctx.msg(), "\nTop {} (by-name) extra datasets, out of {}:\n",
+ thou(std::min<>(topN, extra_dsets.size())), thou(extra_dsets.size())
+ );
+
+ size_t count = 0;
+ for (const Hdf5ObjInfo& obj : extra_dsets) {
+ fmt::print(ctx.msg(), "- {}: {}\n", obj.name_, obj.desc());
+ if (++count >= topN) {
+ break;
+ }
+ }
+ }
+
+ size_t diff_count = 0;
+ auto show_diff_header = [&]() {
+ if (!diff_count) {
+ fmt::print(
+ ctx.msg(), "\nTop {} (by-name) datasets differences:\n",
+ thou(topN)
+ );
+ }
+ };
+
+ size_t checked = 0;
+ size_t bytes_checked = 0;
+ size_t io_error_count = 0;
+ size_t possible_int_conversion_diff = 0;
+ std::map, size_t> int_convs;
+ for (const Hdf5ObjInfo& lhs_obj : common_dsets) {
+ auto rhs_found = std::lower_bound(
+ rhs_objs.begin(), rhs_objs.end(), lhs_obj, lt_by_name
+ );
+ assert(rhs_found != rhs_objs.end());
+ const Hdf5ObjInfo& rhs_obj = *rhs_found;
+ assert(lhs_obj.name_ == rhs_obj.name_);
+ ++checked;
+
+ // Use desc as "proxy" for rank, dimension, type, total bytes.
+ const std::string& lhs_desc = lhs_obj.desc(true);
+ const std::string& rhs_desc = rhs_obj.desc(true);
+ if (lhs_desc != rhs_desc) {
+ show_diff_header();
+ fmt::print(
+ ctx.msg(), "- {}: meta-data diff:\n {}\n {}\n",
+ lhs_obj.name_, lhs_desc, rhs_desc
+ );
+
+ if ((lhs_obj.data_type_ == Hdf5DataType::eINTEGER) &&
+ (rhs_obj.data_type_ == Hdf5DataType::eINTEGER) &&
+ ((lhs_obj.type_bytes_ != rhs_obj.type_bytes_) ||
+ (lhs_obj.type_sign_ != rhs_obj.type_sign_))
+ ) {
+ // Could be an implicit widening of uint to long (lossless),
+ // or even a ulong to long (possibly lossy).
+ // TODO: Do a smarter semantic-value comparison, not a byte-comparison
+ ++possible_int_conversion_diff;
+ ++int_convs[{ lhs_obj.numeric_type(), rhs_obj.numeric_type() }];
+ ++diff_count;
+ continue; // i.e. Don't stop diff'ing for those particular diffs
+ }
+
+ if (++diff_count >= topN) {
+ break;
+ }
+ continue;
+ }
+
+ // Both datasets have the same "shape". Now compare their (uncompressed) bytes
+ const auto& obj = lhs_obj;
+ const size_t bytes = obj.uncompressed_bytes();
+
+ if (!bytes) {
+ continue; // yes, empty datasets do happen...
+ }
+
+ assert(lhs_obj.rank_ == rhs_obj.rank_);
+ assert(lhs_obj.dims_ == rhs_obj.dims_);
+ assert(lhs_obj.type_bytes_ == rhs_obj.type_bytes_);
+ assert(lhs_obj.uncompressed_bytes() == rhs_obj.uncompressed_bytes());
+
+ std::vector lhs_buf(bytes);
+ std::vector rhs_buf(bytes);
+
+ auto lhs_ds = lhs_obj.open(lhs_id);
+ auto rhs_ds = rhs_obj.open(rhs_id);
+ if (!lhs_ds || !rhs_ds) {
+ ++io_error_count;
+ continue;
+ }
+
+ if (bytes <= max_buf_size) {
+ const bool lhs_read_ok = lhs_ds->read(lhs_buf);
+ const bool rhs_read_ok = rhs_ds->read(rhs_buf);
+ if (!lhs_read_ok || !rhs_read_ok) {
+ ++io_error_count;
+ continue;
+ }
+
+ bytes_checked += bytes;
+
+ if (lhs_buf != rhs_buf) {
+ show_diff_header();
+ fmt::print(
+ ctx.msg(), "- {}: data / content diff:\n {}\n",
+ obj.name_, lhs_desc
+ );
+ if (++diff_count >= topN) {
+ break;
+ }
+ }
+ } else {
+ // Warning: This ignores how the datasets are chunked. Could be slow...
+ std::vector dims(obj.rank_, 0);
+ for (size_t i = 0; i < obj.rank_; ++i) {
+ dims[i] = obj.dims_[i];
+ }
+ auto div = etp12::AryDivision::make(dims, obj.type_bytes_, max_buf_size);
+ const size_t div_count = div.size();
+ for (size_t i = 0; i < div_count; ++i) {
+ std::vector starts, counts;
+ const size_t offset = div.initDataSubarraysType(i, starts, counts);
+
+ const bool lhs_read_ok = lhs_ds->read(lhs_buf, starts, counts);
+ const bool rhs_read_ok = rhs_ds->read(rhs_buf, starts, counts);
+ if (!lhs_read_ok || !rhs_read_ok) {
+ ++io_error_count;
+ break;
+ }
+
+ const size_t read_bytes = etp12::productOf(counts) * div.esize_;
+ bytes_checked += read_bytes;
+
+ if (lhs_buf != rhs_buf) {
+ show_diff_header();
+ fmt::print(
+ ctx.msg(), "- {}: data / content diff (after {} bytes):\n {}\n",
+ obj.name_, thou(offset), lhs_desc
+ );
+ if (++diff_count >= topN) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (diff_count) {
+ int diff_width = static_cast(std::log10(diff_count));
+ diff_width += (diff_width / 3); // commas
+ diff_width += 1;
+
+ fmt::print(
+ ctx.out(), "{}{} common datasets differ (out of {}).{}\n",
+ ctx.verbose? "\n": "", thou(diff_count), thou(match_count),
+ ctx.verbose? "": " (Use -v and --top to see which ones)"
+ );
+ if (possible_int_conversion_diff == diff_count) {
+ fmt::print(
+ ctx.out(),
+ "ALL those differences could be harmless integer conversions:\n"
+ );
+ } else if (possible_int_conversion_diff) {
+ fmt::print(
+ ctx.out(),
+ "{} of those differences could be harmless integer conversions:\n",
+ thou(possible_int_conversion_diff)
+ );
+ }
+
+ // Show the actual integer conversions that actually occurred
+ for (const auto& entry : int_convs) {
+ fmt::print(
+ ctx.out(), " {:>{}} {:>8} -> {:<8}\n",
+ thou(entry.second), diff_width,
+ hdf5NumTypeToString(entry.first.first),
+ hdf5NumTypeToString(entry.first.second)
+ );
+ }
+ } else {
+ fmt::print(
+ ctx.out(), "No diffs in {} common datasets ({} uncompressed bytes).\n",
+ thou(match_count), thou(bytes_checked)
+ );
+ }
+
+ if (checked != match_count) {
+ fmt::print(
+ ctx.out(), "Warning: Stopped diff'ing after {} datasets ({:.1f}%)\n",
+ thou(checked), ((1. * checked) / match_count * 100.)
+ );
+ }
+
+ return true;
+}
+
+} // namespace
+
+namespace oes::apps::eml {
+
+/****************************************************************************/
+
+int hdf5_main(EmlAppContext& ctx, const UnparsedArgs& unparsed_args) {
+ po::options_description cmd_desc(R"(
+Usage: openETPServer [global-options] hdf5 --info file.h5
+Usage: openETPServer [global-options] hdf5 --diff file1.h5 --with file2.h5
+
+Actions)", ctx.terminalWidth_
+ );
+
+ cmd_desc.add_options()
+ ("help,h", "Display this information")
+
+ ("info", txtArg(), "Prints HDF5 file statistics")
+ ("stats", txtArg(), "Prints per-Dataset-type statistics")
+ ("diff", txtArg(), "Diffs two HDF5 files' dataset's metadata & bytes\n"
+ "(Ignores chunking, compression, attributes, etc...)")
+ ;
+
+ po::options_description opt_desc("Options", ctx.terminalWidth_);
+ opt_desc.add_options()
+ ("top", intArg(10), "How many entry to list")
+ ("with", txtArg(), "Which HDF5 file to diff against")
+ ("max-buf-size,M", txtArg("1MB"), "Maximum buffer size in bytes")
+ ("force", "Continue instead of exit, on some warnings/errors")
+ ;
+ cmd_desc.add(opt_desc);
+
+ if (int rc = ctx.reparse(cmd_desc, unparsed_args)) {
+ return (rc == -1)? 0: rc;
+ }
+
+ EmlAppNamedAction actions[] {
+ { "info", &hdf5_info },
+ { "stats", &hdf5_stats },
+ { "diff", &hdf5_diff },
+ };
+ return ctx.dispatchActions(actions);
+}
+
+} // namespace oes::apps::eml
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ProbeCmds.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ProbeCmds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cbd66aaa79c98a85f3f9a8f67254cffd9787d46a
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ProbeCmds.cpp
@@ -0,0 +1,339 @@
+// ============================================================================
+// Copyright 2016-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EmlAppContext.h"
+#include "EtpClient12.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+using namespace oes::apps::eml;
+using namespace oes::core;
+
+namespace po = boost::program_options;
+namespace data12 = oes::eml::etp12::Datatypes;
+
+namespace {
+
+//============================================================================
+
+bool alt_etp12_server_ping(const EmlAppContext& ctx) {
+ EtpClient12 clt(ctx);
+ if (!clt.connect()) {
+ return false;
+ }
+
+ auto* proto = clt.core_proto();
+ if (!proto) {
+ return false;
+ }
+
+ auto rpc = etp::rpcFor(proto, &etp::proto12::ICore::Ping);
+
+ using namespace std::chrono;
+ auto sys_now = time_point_cast(system_clock::now());
+ rpc.req_.m_currentDateTime = sys_now.time_since_epoch().count();
+
+ auto before = time_point_cast(high_resolution_clock::now());
+ auto rc = rpc.call(ctx.err(), clt.put_get_timeout_);
+ auto after = time_point_cast(high_resolution_clock::now());
+
+ if (!rc.first || rc.second != std::future_status::ready) {
+ ctx.err() << "Error: Ping failed" << std::endl;
+ return false;
+ }
+
+ int64_t diff = rpc.res_.m_currentDateTime - rpc.req_.m_currentDateTime;
+ int64_t took = after.time_since_epoch().count() - before.time_since_epoch().count();
+ int64_t skew = std::abs(diff - (std::max(2, took)/2));
+
+ microseconds server_us(rpc.res_.m_currentDateTime);
+ time_point server_tp(server_us);
+
+ auto client_ts = formatExtendedIsoDateTime(sys_now, FracSecRes::eMicroSecFloor);
+ auto server_ts = formatExtendedIsoDateTime(server_tp, FracSecRes::eMicroSecFloor);
+
+ ctx.out() << "Client Ping Timestamp: " << client_ts
+ << " UTC (" << thou(rpc.req_.m_currentDateTime) << "us since EPOCH)" << std::endl;
+ ctx.out() << "Server Pong Timestamp: " << server_ts
+ << " UTC (" << thou(rpc.res_.m_currentDateTime) << "us since EPOCH)" << std::endl;
+ if (diff < 0) {
+ ctx.out() << " Client-Server Diff: -" << thou(-diff) << "us" << std::endl;
+ } else {
+ ctx.out() << " Client-Server Diff: " << thou(diff) << "us" << std::endl;
+ }
+ ctx.out() << " Ping/Pong Roundtrip: " << thou(took) << "us" << std::endl;
+ ctx.out() << " Approx. Clock Skew: " << thou(skew) << "us" << std::endl;
+
+ return true;
+}
+
+//============================================================================
+
+bool alt_etp12_server_info(const EmlAppContext& ctx) {
+ EtpClient12 clt(ctx);
+ if (!clt.config()) {
+ return false;
+ }
+
+ std::string url = ctx.stringArg("server-url");
+ if (url.empty()) {
+ ctx.err() << "Warning: No --server-url specified. Using localhost:9002" << std::endl;
+ url = "http://localhost:9002";
+ }
+
+ std::string req_url;
+ oes::core::HTTP req;
+ oes::core::HTTP::Response res;
+ std::vector headers{ "Accept: application/json" };
+
+ if (!clt.cfg_.auth_hdr_.empty()) {
+ headers.emplace_back("Authorization: " + clt.cfg_.auth_hdr_);
+ ctx.dbg() << "Authorization: " << clt.cfg_.auth_hdr_ << std::endl;
+ }
+
+ req_url = url + "/.well-known/etp-server-capabilities?GetVersions=true";
+ ctx.dbg() << "HTTP GET " << req_url << std::endl;
+ res = req.get(req_url, 0, headers);
+ if (res.code != 200) {
+ ctx.err() << "HTTP GET " << req_url << " KO: "
+ << res.code << ' ' << res.status << std::endl;
+ return false;
+ }
+ ctx.dbg() << "BODY (" << res.body.size() << " bytes):" << std::endl;
+ ctx.msg() << res.body << std::endl;
+ if (!json_validate_syntax(res.body.c_str(), res.body.size())) {
+ ctx.err() << "Error: Invalid JSON: GET ...?GetVersions=true" << std::endl;
+ return false;
+ }
+ std::vector versions;
+ if (!json_parse(res.body.c_str(), res.body.size(), versions, JSON_TYPE_STRING)) {
+ ctx.err() << "Error: Unexpected JSON: GET ...?GetVersions=true" << std::endl;
+ return false;
+ }
+ auto& out = ctx.out();
+
+ switch (const size_t N = versions.size()) {
+ case 0: out << "Warning: Server advertises NO versions." << std::endl; break;
+ case 1: out << "Server advertises 1 version:" << std::endl; break;
+ default: out << "Server advertises " << N << " versions:" << std::endl; break;
+ }
+
+ for (const std::string& v : versions) {
+ out << "- " << v << std::endl;
+ }
+
+ for (const std::string& v : versions) {
+ res.body.clear();
+ res.status.clear();
+ out << "\n=== " << v << " ===" << std::endl;
+ req_url = url + "/.well-known/etp-server-capabilities?GetVersion=" + v;
+ ctx.dbg() << "HTTP GET " << req_url << std::endl;
+ res = req.get(req_url, 0, headers);
+ if (res.code != 200) {
+ ctx.err() << "HTTP GET " << req_url << " KO: "
+ << res.code << ' ' << res.status << std::endl;
+ continue;
+ }
+ ctx.dbg() << "BODY (" << res.body.size() << " bytes):" << std::endl;
+ ctx.msg() << res.body << std::endl;
+ if (!json_validate_syntax(res.body.c_str(), res.body.size())) {
+ ctx.err() << "Error: Invalid JSON: GET ...?GetVersion=" << v << std::endl;
+ continue;
+ }
+ data12::ServerCapabilities caps;
+ oes::eml::my_buffer_ptr buf(new std::vector(
+ res.body.begin(), res.body.end()
+ ));
+ try {
+ oes::eml::decode_json(caps, buf);
+ } catch (const std::exception& ex) {
+ ctx.err() << "Error: Avro JSON Parse: " << ex.what() << std::endl;
+ ctx.err() << "\tof GET ...?GetVersion=" << v << std::endl;
+ continue;
+ } catch (...) {
+ ctx.err() << "Error: Avro JSON Parse: GET ...?GetVersion=" << v << std::endl;
+ continue;
+ }
+
+ fmt::print(
+ out, "{} ({})\n{}\n\n",
+ caps.m_applicationName,
+ caps.m_applicationVersion,
+ caps.m_contactInformation.m_organizationName
+ );
+
+ switch (const size_t N = caps.m_supportedProtocols.size()) {
+ case 0: out << "Warning: NO protocols. Ignored!" << std::endl; continue;
+ case 1: out << "Warning: Just 1 protocol:" << std::endl; break;
+ default: out << N << " protocols:" << std::endl; break;
+ }
+ for (const auto& proto : caps.m_supportedProtocols) {
+ using namespace oes::eml::etp12;
+ auto found = protocols().find(proto.m_protocol);
+ if (found == protocols().end()) {
+ out << "Warning: Unknown protocol ID: " << proto.m_protocol << std::endl;
+ continue;
+ }
+ const ProtocolInfo& info = found->second;
+
+ out << "- " << info.label() << std::endl;
+ }
+
+ const size_t nEnc = caps.m_supportedEncodings.size();
+ const size_t nCmp = caps.m_supportedCompression.size();
+ const size_t nFmt = caps.m_supportedFormats.size();
+
+ if (nEnc + nCmp + nFmt) {
+ out << std::endl;
+ }
+
+ if (nEnc) {
+ std::string csv = boost::join(caps.m_supportedEncodings, ", ");
+ out << nEnc << " supported encodings: " << csv << std::endl;
+ }
+ if (nCmp) {
+ std::string csv = boost::join(caps.m_supportedCompression, ", ");
+ out << nCmp << " supported compression: " << csv << std::endl;
+ }
+ if (nFmt) {
+ std::string csv = boost::join(caps.m_supportedFormats, ", ");
+ out << nFmt << " supported formats: " << csv << std::endl;
+ }
+
+ if (const size_t N = caps.m_supportedDataObjects.size()) {
+ out << "\n" << N << " supported objects: " << std::endl;
+ for (const auto& type : caps.m_supportedDataObjects) {
+ auto& obj_caps = type.m_dataObjectCapabilities;
+
+ // keys of obj_caps are supposed to be case-insensitive,
+ // so we must normalize the keys first...
+ std::map imap;
+ for (auto it = obj_caps.begin(); it != obj_caps.end(); ++it) {
+ imap.emplace(boost::to_lower_copy(it->first), &it->second);
+ }
+
+ std::vector verb_caps;
+ auto it = obj_caps.find("supportsget");
+ if (it != obj_caps.end()) {
+ if (const bool* p_get = std::get_if(&it->second.m_item)) {
+ if (*p_get) {
+ verb_caps.emplace_back("get");
+ }
+ }
+ }
+ it = obj_caps.find("supportsput");
+ if (it != obj_caps.end()) {
+ if (const bool* p_put = std::get_if(&it->second.m_item)) {
+ if (*p_put) {
+ verb_caps.emplace_back("put");
+ }
+ }
+ }
+ it = obj_caps.find("supportsdelete");
+ if (it != obj_caps.end()) {
+ if (const bool* p_del = std::get_if(&it->second.m_item)) {
+ if (*p_del) {
+ verb_caps.emplace_back("delete");
+ }
+ }
+ }
+ // TODO: Show other caps, from DataObjectCapabilityKind
+ std::string csv = boost::join(verb_caps, ", ");
+ fmt::print(out, "- {} ({})\n", type.m_qualifiedType, !csv.empty()? csv: "no caps");
+ }
+ }
+
+ auto max_msg_found = caps.m_endpointCapabilities.find("MaxWebSocketMessagePayloadSize");
+ // FIXME: Remove: Temporary work-around for PV's server
+ if (max_msg_found == caps.m_endpointCapabilities.end()) {
+ max_msg_found = caps.m_endpointCapabilities.find("maxWebSocketMessagePayloadSize");
+ }
+ if (max_msg_found != caps.m_endpointCapabilities.end()) {
+ size_t N = 0;
+ const data12::DataValue_item_v& val = max_msg_found->second.m_item;
+ switch (val.index()) {
+ case size_t(data12::DataValue_item_e::eDouble): N = size_t(std::get(val)); break;
+ case size_t(data12::DataValue_item_e::eFloat): N = size_t(std::get(val)); break;
+ case size_t(data12::DataValue_item_e::eLong): N = size_t(std::get(val)); break;
+ case size_t(data12::DataValue_item_e::eInt): N = size_t(std::get(val)); break;
+ default:
+ out << "\nWarning: Unknown type for MaxWebSocketMessagePayloadSize value" << std::endl;
+ }
+ if (N) {
+ out << "\nMaximum WebSocket Message Payload Size: " << thou(N) << " bytes" << std::endl;
+ }
+ } else {
+ out << "\nNO Maximum WebSocket Message Payload Size advertised!" << std::endl;
+ }
+ }
+
+ return true;
+}
+
+//============================================================================
+
+} //namespace
+
+namespace oes {
+namespace apps {
+namespace eml {
+
+/****************************************************************************/
+
+int probe_main(EmlAppContext& ctx, const UnparsedArgs& unparsed_args) {
+ po::options_description cmd_desc(
+ "\nUsage: openETPServer [global-options] " + ctx.command + " --info -S URL\n"
+ "Usage: openETPServer [global-options] " + ctx.command + " --ping -S URL [auth-options]\n"
+ "\nActions", ctx.terminalWidth_
+ );
+ cmd_desc.add_options()
+ ("help,h", "Display this information")
+
+ ("info", "Get ETP 1.2 server capabilities")
+ ("ping", "Connect and ping ETP 1.2 server")
+ ;
+
+ cmd_desc.add(connect_options(ctx.terminalWidth_));
+ cmd_desc.add(authenticate_options(ctx.terminalWidth_));
+
+ if (int rc = ctx.reparse(cmd_desc, po::positional_options_description(), unparsed_args)) {
+ return (rc == -1)? 0: rc;
+ }
+
+ EmlAppNamedAction actions[] {
+ { "info", &alt_etp12_server_info },
+ { "ping", &alt_etp12_server_ping },
+ };
+ return ctx.dispatchActions(actions);
+}
+
+} // namespace eml
+} // namespace apps
+} // namespace oes
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ServerCmds.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ServerCmds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a33d808b90f72e1a492f306acbfc2ce9facb9de3
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/ServerCmds.cpp
@@ -0,0 +1,440 @@
+// ============================================================================
+// Copyright 2016-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EmlAppContext.h"
+#include "EtpClient12.h"
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace oes::apps::eml;
+using namespace oes::core;
+using namespace oes::eml;
+
+namespace fs = std::filesystem;
+namespace po = boost::program_options;
+
+using oes::eml::etp12::EmlUri;
+
+namespace data12 = oes::eml::etp12::Datatypes;
+using Etp12Resources = std::vector;
+
+namespace {
+
+//============================================================================
+
+struct MainRepo {
+ bool ok_;
+
+ openkv::Connection_ptr conn_;
+ openkv::OpenKVAdminDB_ptr admin_db_;
+
+ fs::path admin_dir_;
+ fs::path admin_dbfile_;
+
+ MainRepo(bool ok = false) : ok_(ok), admin_db_(nullptr) {}
+
+ explicit operator bool() const { return ok_; }
+};
+
+MainRepo getMainRepo(
+ const EmlAppContext& ctx, bool init
+) {
+ std::string dir = ctx.stringArg("db-connection");
+ if (dir.empty()) {
+ const char* connection_string = std::getenv("POSTGRESQL_CONN_STRING");
+ if(connection_string!=nullptr){
+ dir = std::string(connection_string);
+ }
+ if (dir.empty()) {
+ ctx.err() << "Error: Missing required --db-connection or POSTGRESQL_CONN_STRING environment variable" << std::endl;
+ return MainRepo();
+ }
+ }
+
+ const std::string dbfile = dir;
+ OES_UNUSED(init);
+
+ MainRepo main;
+ main.conn_.reset(new oes::emldb::Connection(dbfile.c_str()));
+ main.admin_dir_ = dbfile;
+ main.admin_dbfile_ = dbfile;
+ main.admin_db_ = std::make_shared(main.conn_, main.admin_dbfile_.string());
+ main.ok_ = true;
+
+ return main;
+}
+
+//============================================================================
+
+bool alt_etp12_openkv_start(const EmlAppContext& ctx) {
+ MainRepo au = getMainRepo(ctx, false);
+ if (!au) {
+ return false;
+ }
+
+ std::ostringstream oss;
+ oss << "SELECT set_config('application_name','" << au.admin_db_->app_id() << "', false);";
+ const std::string app_pragmas = oss.str();
+ au.admin_db_->handle()->execute(app_pragmas.c_str());
+ if(!au.admin_db_->isSchemaCreated()){
+ au.admin_db_->createSchema();
+ }
+ if (!au.admin_db_->isSchemaUpToDate(false)) {
+ ctx.err() << "Error: Cannot start server: MainRepo schema out-of-date." << std::endl;
+ return false; // no upgrades for the time being...
+ }
+
+ //if (ctx.dataspaceSpecified()) {
+ // ctx.out() << "Serving Dataspace " << ctx.dataspaceTitle() << std::endl;
+ //} else {
+ // ctx.out() << "Serving All Dataspaces" << std::endl;
+ //}
+
+ std::string jwtSecret = ctx.stringArg("jwt-secret");
+
+ std::string authN = ctx.stringArg("authN");
+ if (ctx.hasArg("auth")) {
+ if (ctx.hasArg("authN") && !ctx.args["authN"].defaulted()) {
+ ctx.err() << "Error: --auth replaced by --authN. Please use the latter." << std::endl;
+ return false;
+ } else {
+ ctx.out() << "Warning: --auth deprecated: Please use --authN instead." << std::endl;
+ authN = ctx.stringArg("auth");
+ }
+ }
+
+ if (authN != "none" && authN != "builtin" && !boost::starts_with(authN, "delegate=")) {
+ ctx.err() << "Error: Invalid --authN: Use 'none', 'builtin', or 'delegate=URL'" << std::endl;
+ return false;
+ }
+
+ const std::string authZ = ctx.stringArg("authZ");
+ if (authZ != "none" && authZ != "builtin" && !boost::starts_with(authZ, "delegate=")) {
+ ctx.err() << "Error: Invalid --authZ: Use 'none', 'builtin', or 'delegate=URL'" << std::endl;
+ return false;
+ }
+
+ if (authN != "builtin" && !jwtSecret.empty()) {
+ ctx.out() << "Warning: Option --jwt-secret ignored; requires --authN=builtin." << std::endl;
+ }
+
+ // AuthN, i.e. Authentication
+ openkv::OpenKVAuthNMgr authN_mgr;
+ if (authN == "none") {
+ ctx.out() << "Warning: Authentication disabled." << std::endl;
+ authN_mgr = [&ctx](
+ const std::string& auth_hdr, auth::AuthDetails* p_details
+ ) {
+ auth::AuthenticatedUser au;
+
+ // try to get the current user, for AuthZ later.
+ if (!auth_hdr.empty()) {
+ ctx.out() << "Warning: Authentication header ignored: --authN none" << std::endl;
+
+ // We try decoding the header with a dummy JWT secret, that will fail
+ // of course, but give us the jwt_payload, to reparse that as JSON, and
+ // we look for the "username" key, i.e. the same "claim" as our JWT processing.
+ auth::AuthDetails local_details;
+ auth::AuthDetails& details = p_details? *p_details: local_details;
+ au = auth::AuthenticatedUser::decodeFromHeader(
+ auth_hdr, "jwt-secret", &details
+ );
+ auth::AuthType prev_type = au.type_;
+
+ if (!au.ok_ && !details.jwt_payload_.empty()) {
+ au = auth::AuthenticatedUser::decodeFromJson(
+ details.jwt_payload_, false, &details
+ );
+ au.type_ = prev_type; // otherwise eUnknown overrides eBearer
+ }
+ }
+ if (au.name_.empty()) {
+ au.name_ = "nobody"; // failed to get username from auth_hdr
+ }
+ au.since_ = std::chrono::system_clock::now();
+ au.read_only_ = false; // No authN, leave it to authZ (if any)
+ au.header_ = auth_hdr;
+ au.ok_ = true;
+ return au;
+ };
+ } else if (authN == "builtin") {
+ if (jwtSecret.empty()) {
+ ctx.out() << "Warning: No --jwt-secret: Bearer authentication disabled (Basic only)." << std::endl;
+ }
+
+ // TODO: Use MainRepo's users table, instead of hard-coded names below
+ std::string username = "foo";
+ std::string password = "bar";
+ openkv::OpenKVUserMgr user_mgr =
+ [usr = username, pwd = password]
+ (const std::string& username, const std::string& password) {
+ return (username == usr) && (password == pwd);
+ }
+ ;
+ authN_mgr = openkv::builtinAuthMgr(user_mgr, jwtSecret);
+ } else {
+ assert(false);
+ return false;
+ }
+
+ // TODO: AuthZ, i.e. Authorization
+ if (authZ == "none") {
+ if (!ctx.args["authZ"].defaulted()) {
+ ctx.out() << "Warning: Authorization disabled." << std::endl;
+ }
+ } else if (authZ == "builtin") {
+ ctx.out() << "Warning: No built-in authorization yet." << std::endl;
+ } else if (boost::starts_with(authZ, "delegate=")) {
+ std::string url = authZ.substr(9);
+ ctx.err() << "Error: Delegate authorization to URL endpoint "
+ << url << ": TODO" << std::endl;
+ return false;
+ } else {
+ assert(false);
+ return false;
+ }
+
+ openkv::OpenKVSpaceResolver space_resolver =
+ [admin_dir = au.admin_dir_]
+ (const std::string& relative_dbfile, bool new_db) -> std::string {
+ // See also how new_space.dbfile_ is assigned in this file,
+ // to be relative to main-folder *and* using "unix path"
+ OES_UNUSED(new_db);
+ return relative_dbfile;
+ }
+ ;
+
+ oes::eml::etp12::ServerConfig config = openkv::makeServerConfig(
+ au.admin_db_, std::move(authN_mgr), nullptr, std::move(space_resolver)
+ );
+
+ config.port_ = ctx.args["port"].as();
+ config.reuseAddressSocket_ = ctx.args["reuseAddressSocket"].as();
+ config.stopOnLastDisconnection_ = ctx.args["stopOnLastDisconnection"].as();
+ config.useIpV4_ = ctx.args["ipv4"].as();
+ config.maxNumberConnection_ = ctx.args["maxConnections"].as();
+
+ config.verbose_ = ctx.verbose;
+ config.debug_ = ctx.debug;
+ config.quiet_ = ctx.quiet;
+
+ const std::string& mms_spec = ctx.stringArg("max-msg-size");
+ if (!mms_spec.empty()) try {
+ auto [zsize, usize] = parseByteSizes(mms_spec);
+ config.max_mgs_size_ = zsize;
+ config.max_mgs_usize_ = usize; // Warning: Not ETP standard
+ } catch (const std::runtime_error& ex) {
+ ctx.err() << "Error: Invalid --max-msg-size option: " << ex.what() << std::endl;
+ return false;
+ }
+
+ // Warning: NOT ETP standard. Used to exist, but was eventually removed, in favor
+ // of purely max_mgs_size-driven splitting of messages into multi-part responses.
+ const int max_batch_size = ctx.args["max-batch-size"].as();
+ if (!(0 <= max_batch_size && max_batch_size <= 10'000) && (max_batch_size != 1'000'000)) {
+ ctx.err() << "Error: Invalid --max-batch-size: Not in [0 - 10,000] range" << std::endl;
+ return false;
+ }
+ config.max_batch_size_ = max_batch_size;
+
+ const int mqd = ctx.args["msg-queue-depth"].as();
+ if (!(0 < mqd && mqd <= 100)) {
+ ctx.err() << "Error: Invalid --msg-queue-depth option: Not in range [1, 100]" << std::endl;
+ return false;
+ }
+ config.msg_queue_depth_ = mqd;
+
+ if (ctx.hasArg("jobs")) {
+ // single-threaded when -j is not explicitly specified
+ int64_t jobs = ctx.args["jobs"].as();
+ OES_ASSERT(jobs >= 0, jobs = 1;);
+ config.totalThreadCount_ = static_cast(jobs);
+ }
+
+ if (ctx.hasArg("etp-trace")) {
+ config.enable_tracing_ = true;
+ config.trace_file_ = ctx.stringArg("etp-trace");
+ }
+
+ oes::eml::etp12::Server server(config);
+ data12::ServerCapabilities svr_info = server.getServerInfo();
+ ctx.out() << "" << svr_info.m_applicationName << ' ' << svr_info.m_applicationVersion << std::endl;
+ return server.start();
+}
+
+//============================================================================
+
+bool alt_etp12_openkv_init_admin_db(const EmlAppContext& ctx) {
+ MainRepo au = getMainRepo(ctx, false);
+ if (!au) {
+ return false;
+ }
+
+ std::ostringstream oss;
+ oss << "SELECT set_config('application_name','" << au.admin_db_->app_id() << "', false);";
+ const std::string app_pragmas = oss.str();
+ au.admin_db_->handle()->execute(app_pragmas.c_str());
+
+ const bool ok = au.admin_db_->createSchema();
+ if (ok) {
+ ctx.out() << "Created main DB" << std::endl;
+ } else {
+ ctx.err() << "Error: Cannot create main DB" << std::endl;
+ }
+
+ // Create the ETP 1.2 mandatory "default" (no name) Dataspace
+ if (ok && !ctx.hasArg("no-default-space")) {
+ // TODO: Create default space
+ }
+
+ return ok;
+}
+
+bool alt_etp12_openkv_check_admin_db(const EmlAppContext& ctx) {
+ MainRepo au = getMainRepo(ctx, false);
+ if (!au) {
+ return false;
+ }
+
+ const bool created = au.admin_db_->isSchemaCreated();
+ if (!created) {
+ ctx.err() << "Error: Empty main DB: " << au.admin_dbfile_ << std::endl;
+ return false;
+ }
+ const bool uptodate = au.admin_db_->isSchemaUpToDate(false);
+ if (!uptodate) {
+ ctx.out() << "Warning: Out-of-date main DB: " << au.admin_dbfile_ << std::endl;
+ return true;
+ }
+ ctx.out() << "OK. Up-to-date main DB: " << au.admin_dbfile_ << std::endl;
+ return true;
+}
+
+//============================================================================
+
+} //namespace
+
+namespace oes::apps::eml {
+
+/****************************************************************************/
+
+int server_main(EmlAppContext& ctx, const UnparsedArgs& unparsed_args) {
+ po::options_description cmd_desc(
+ "\nUsage: openETPServer [global-options] " + ctx.command + " --start -d conn_string [options]\n"
+ "\nActions", ctx.terminalWidth_
+ );
+ cmd_desc.add_options()
+ ("help,h", "Display this information")
+ ("init", "Initialize main DB")
+ ("check", "Check main DB")
+ ("start", "Start ETP 1.2 server")
+ ;
+
+ po::options_description opt_desc("Options", ctx.terminalWidth_);
+ opt_desc.add_options()
+ ("port,P",
+ po::value()->default_value(9002),
+ "Specify port to listen to")
+ ("reuseAddressSocket",
+ po::value()->default_value(true),
+ "Reuse socket address. Allows reusing socket port\n"
+ "shortly after previous server instance stopped.")
+ ("stopOnLastDisconnection",
+ po::value()->default_value(true),
+ "Stop when last client disconnect")
+ ("ipv4",
+ po::value()->default_value(false),
+ "Use ipv4 protocol instead of ipv6")
+ ("maxConnections",
+ po::value()->default_value(-1),
+ "Specify maximum number of connections")
+
+ ("db-connection,d", txtArg(),
+ "PostgreSQL connection string (If not defined, will use POSTGRESQL_CONN_STRING environment variable")
+ ("space-path,s", txtArg(), "ETP dataspace name (folder or folder/project)")
+ ("space-guid", txtArgOpt(), "ETP dataspace guid (uses path if omitted)")
+
+ ("xdata-file", txtArg(), "New Space meta-data as JSON file (on EPC Direct-import)")
+ ("vacuum", "Vacuum (i.e. optimize) DB, post EPC Direct-import")
+
+ ("no-default-space", "Omit ETP 1.2 mandatory 'default' dataspace on --init")
+
+ ("overwrite", "Overwrite existing EPC on Direct-import")
+
+ // Auth-related options
+ ("auth", txtArg(), "Deprecated: Legacy name of authN")
+ ("authN,N", txtArg("builtin"), "Authentication: 'none', 'builtin', or 'delegate=URL'")
+ ("authZ", txtArg("none"), "Authorization: 'none', 'builtin', or 'delegate=URL'")
+ ("jwt-secret,k", txtArg(), "JWT Signing Key (--authN=builtin only).")
+
+ ("jobs,j",
+ greedy_value()->implicit_value(0),
+ "How many worker-threads:\n"
+ "* not specified: Defaults to single-threaded;\n"
+ "* -j: Use as many threads as hardware cores;\n"
+ "* -jN: Use exactly N threads")
+
+ ("force", "Continue instead of exit, on some warnings/errors")
+ ;
+
+ EtpDefaults defaults;
+ defaults.max_msg_size_ = "16MB, 128MB";
+ defaults.max_batch_size_ = 1'000'000; // I.e. disable it for now, by default
+ defaults.compress_ = true;
+
+ cmd_desc.add(opt_desc);
+ cmd_desc.add(connect_options(ctx.terminalWidth_, defaults));
+
+ if (int rc = ctx.reparse(cmd_desc, po::positional_options_description(), unparsed_args)) {
+ return (rc == -1)? 0: rc;
+ }
+
+ EmlAppNamedAction actions[] {
+ { "init", &alt_etp12_openkv_init_admin_db },
+ { "check", &alt_etp12_openkv_check_admin_db },
+ { "start", &alt_etp12_openkv_start },
+ };
+ return ctx.dispatchActions(actions);
+}
+
+} // namespace oes::apps::eml
diff --git a/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/SpaceCmds.cpp b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/SpaceCmds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..439e7ac56e1ae51448bf44eb3f1781462502377b
--- /dev/null
+++ b/Reservoir DMS Suite/open-etp-server/src/bin/openETPServer/SpaceCmds.cpp
@@ -0,0 +1,1591 @@
+// ============================================================================
+// Copyright 2016-2021 Emerson Paradigm Holding LLC. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ============================================================================
+
+#include "EmlAppContext.h"
+#include "EtpClient12.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace oes::apps::eml;
+
+namespace fs = std::filesystem;
+namespace po = boost::program_options;
+namespace etp = oes::eml::etp12;
+
+namespace {
+
+using oes::core::thou;
+using oes::eml::etp12::rpcFor;
+const std::string dflt = "";
+
+int64_t parseSince(const std::string& since, bool utc) {
+ if (since.empty()) {
+ return 0;
+ }
+
+ // Not a valid ISO date-time
+ using namespace std::chrono;
+ microseconds since_epoch(0); // UTC or Local, it depends
+ if (oes::core::usecSinceEpochFromExtendedIsoDateTime(since, since_epoch)) {
+ if (!utc) {
+ // We assume the date we parsed is in local time, not UTC
+ /*
+ ** This requires date/tz.h or C++20
+ time_point tp_localtime(since_epoch);
+ auto local_time = date::make_zoned(date::current_zone(), tp_localtime);
+ return local_time.get_sys_time().time_since_epoch().count();
+ */
+ system_clock::time_point tp(since_epoch);
+ tp = oes::core::localToUtc(tp);
+ system_clock::duration ts = tp.time_since_epoch();
+ since_epoch = duration_cast(ts);
+ }
+ return since_epoch.count();
+ }
+
+ std::istringstream iss(since);
+ int64_t num;
+ std::string uom;
+ iss >> num >> uom;
+ if (iss.fail() || !iss.eof()) {
+ return -1;
+ }
+
+ microseconds offset(0);
+ switch (uom.size()) {
+ case 0: return -1;
+ case 1:
+ switch (uom[0]) {
+ case 's': offset = seconds(num); break;
+ case 'h': offset = hours(num); break;
+ // days, weeks, months, years are C++20 only
+ case 'd': offset = duration>(num); break;
+ case 'w': offset = duration>(num); break;
+ case 'm': offset = duration>(num); break;
+ case 'y': offset = duration>(num); break;
+ }
+ break;
+ case 2: return -1;
+ case 3:
+ if (uom == "min") {
+ offset = minutes(num);
+ }
+ break;
+ default: return -1;
+ }
+
+ if (offset.count() == 0) {
+ return -1;
+ }
+
+ const auto now = system_clock::now(); // UTC
+ const auto since_usec = time_point_cast(now - offset);
+ return since_usec.time_since_epoch().count();
+}
+
+std::string printMicroSecDateTime(int64_t etp_usec, bool utc, bool iso, bool debug) {
+ using namespace oes::core;
+ using namespace std::chrono;
+ microseconds usec(etp_usec); // UTC
+ time_point tp(usec);
+ if (!utc) {
+ tp = time_point_cast(utcToLocal(tp));
+ }
+ std::string dt_fmt = iso? isoDatetimeFormat(): ascDatetimeFormat();
+ FracSecRes dt_res = debug? FracSecRes::eMicroSecFloor: FracSecRes::eWholeSecFloor;
+ std::string ts = formatDateTime(dt_fmt, tp, dt_res);
+ if (utc) {
+ ts += " UTC";
+ }
+ return ts;
+}
+
+std::pair getSpacePath(
+ const EmlAppContext& ctx,
+ bool required = false,
+ bool default_ok = true
+) {
+ if (!ctx.hasArg("space-path")) {
+ if (required) {
+ ctx.err() << "Error: Missing --space-path option" << std::endl;
+ return { {}, false };
+ }
+ }
+
+ std::string space_path = ctx.stringArg("space-path");
+ if (space_path.empty()) {
+ if (!default_ok && !ctx.hasArg("force")) {
+ ctx.err() << "Error: Cannot use " << dflt << " unnamed dataspace" << std::endl;
+ return { {}, false };
+ }
+ ctx.out() << "Warning: Using " << dflt << " unnamed dataspace" << std::endl;
+ }
+ return { std::move(space_path), true };
+}
+
+//============================================================================
+
+// Convert special (Windows) chars that cannot appear in filenames
+// Linux accepts arbitrary strings for filenames, but lets be consistent.
+constexpr char win_special_char[] = "\\/:*?\"<>|";
+std::string scrubFilename(const std::string& s) {
+ if (s.find_first_of(win_special_char) == std::string::npos) {
+ return s;
+ }
+ std::string copy(s);
+ for (char& c : copy) {
+ if (std::strchr(win_special_char, c)) {
+ c = '#';
+ }
+ }
+ return copy;
+};
+
+bool tryRemovefile(std::ostream& err, const std::string& filename, bool overwrite) {
+ if (fs::exists(filename)) {
+ if (!overwrite) {
+ err << "Error: Output file exists (and --overwrite not specified)" << std::endl;
+ return false;
+ }
+ if (!fs::remove(filename)) {
+ err << "Error: Output file exists and cannot be removed" << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+// Unfortunately, fs::status(path).permissions() is not reliable on Windows.
+// Especially for folder in C:\Program Files, with special UAC protections.
+bool canWriteFileInFolder(const std::string& filename) {
+ namespace fs = fs;
+ fs::path path(filename);
+ const size_t len = path.filename().string().size();
+
+ // Warning: .parent_path() of relation path may be empty,
+ // must convert to absolute path before getting its parent.
+ fs::path folder = absolute(path).parent_path();
+ if (!is_directory(folder)) {
+ return false;
+ }
+
+ bool can_write = false;
+ for (int i = 0; i < 7; ++i) {
+ // to avoid running in path length limitations, especially
+ // on Windows, don't use the full 32 chars of the Guid, but
+ // as many of the input file name. But Use a minimum of 7 chars,
+ // just in case, and account for those pre-existing (100 tries).
+ std::string uid = oes::core::Guid::make().toStringNoDash();
+ std::string stem = 'x' + uid.substr(0, std::max(7, len));
+ fs::path tmp_file = folder / stem;
+ if (exists(tmp_file)) {
+ continue;
+ }
+ try {
+ std::ofstream ofs(tmp_file.string());
+ if (ofs.is_open()) {
+ ofs << '\n';
+ ofs.close();
+ can_write = true;
+ try {
+ fs::remove(tmp_file);
+ } catch (const std::exception&) {
+ // ignore, i.e. leave temp file behind...
+ // If we do not catch here, we don't break below
+ }
+ break;
+ }
+ } catch (const std::exception&) {
+ // ignore? or break? We continue spinning if ignored,
+ // but only 7 times, so lets give that a try.
+ }
+ }
+ return can_write;
+}
+
+//============================================================================
+
+int hdf5Level(const EmlAppContext& ctx, const std::string& opt_name) {
+ // we provide a default
+ OES_DEBUG_ASSERT(ctx.hasArg(opt_name));
+
+ const int zlevel = ctx.args[opt_name].as();
+ if (0 <= zlevel && zlevel <= 9) {
+ return zlevel;
+ }
+ fmt::print(
+ ctx.err(),
+ "Warning: Invalid --{} level {}. Not in [0-9] range. Assuming 0.\n",
+ opt_name , zlevel
+ );
+ return 0; // not compressed
+}
+
+int hdf5CompressionLevel(const EmlAppContext& ctx) {
+ return hdf5Level(ctx, "hdf5-compression");
+}
+
+int hdf5LargeDataCompressionLevel(const EmlAppContext& ctx) {
+ return hdf5Level(ctx, "hdf5-large-data-compression");
+}
+
+//============================================================================
+
+bool space_new(const EmlAppContext& ctx) {
+ auto [space_path, space_ok] = getSpacePath(ctx, true, false);
+ if (!space_ok) {
+ return false;
+ }
+
+ oes::eml::etp12::XDataMap xdata;
+ std::string xdata_file = ctx.stringArg("xdata-file");
+ if (!xdata_file.empty()) {
+ if (!oes::eml::etp12::xdataFromJsonFile(xdata, xdata_file)) {
+ ctx.err() << "Error: Cannot read or parse JSON file: " << xdata_file << std::endl;
+ return false;
+ }
+ }
+
+ EtpClient12 clt(ctx);
+ clt.cfg_.requested_protocols_.assign({
+ etp::data12::Protocol::eDataspace
+ });
+ if (!clt.connect()) {
+ return false;
+ }
+
+ auto* proto = clt.space_proto();
+ if (!proto) {
+ return false;
+ }
+
+ auto rpc = rpcFor(proto, &etp::proto12::IDataspace::PutDataspaces);
+
+ etp::data12::Object::Dataspace space;
+ space.m_path = space_path;
+ if (!xdata.empty()) {
+ space.m_customData.swap(xdata);
+ ctx.dbg() << "Added " << space.m_customData.size()
+ << " custom-data from " << xdata_file << std::endl;
+ }
+
+ // by default, the URI is derived from the path,
+ // as per the discussion on the ETP forum.
+ std::string uid = space_path;
+
+ // But as an extension, we allow using a Guid-based URI instead.
+ // And the client can now control the Guid that will be used.
+ // Unless one is not explicitly provided, in which case we make it up.
+ const bool use_guid = ctx.hasArg("space-guid");
+ if (use_guid) {
+ using oes::core::Guid;
+ std::string guid_txt = ctx.stringArg("space-guid");
+ Guid guid = guid_txt.empty()? Guid::make(): Guid::parse(guid_txt);
+ if (!guid.isValid()) {
+ ctx.err() << "Error: Invalid UUID for --space-guid: " << guid_txt << std::endl;
+ return false;
+ }
+ uid = guid.toXmlString();
+ }
+ space.m_uri = etp::EmlUri::make_space(uid).to_string();
+
+ rpc.req_.m_dataspaces.emplace("1", space);
+
+ auto ok = rpc.call(ctx.err(), clt.put_get_timeout_);
+ if (!ok.first) {
+ return false;
+ }
+
+ return true;
+}
+
+bool space_info(const EmlAppContext& ctx) {
+ auto [space_path, space_ok] = getSpacePath(ctx);
+ if (!space_ok) {
+ return false;
+ }
+
+ EtpClient12 clt(ctx);
+ clt.cfg_.requested_protocols_.assign({
+ etp::data12::Protocol::eDataspace
+ });
+ if (!clt.connect()) {
+ return false;
+ }
+
+ auto* proto = clt.space_proto();
+ if (!proto) {
+ return false;
+ }
+
+ auto get_rpc = rpcFor(proto, &etp::proto12::IDataspace::GetDataspaces);
+ auto get_ok = get_rpc.call(ctx.err(), clt.put_get_timeout_);
+ if (!get_ok.first) {
+ return false;
+ }
+
+ const auto& spaces = get_rpc.res_.m_dataspaces;
+ auto found = std::find_if(
+ spaces.begin(), spaces.end(), [space_path](const auto& space) {
+ return space.m_path == space_path;
+ }
+ );
+ if (found == spaces.end()) {
+ ctx.err() << "Error: ETP Dataspace not found: " << space_path << std::endl;
+ return false;
+ }
+ const auto& space = *found;
+
+ auto uri = etp::EmlUri::parse(space.m_uri);
+ if (uri.type_ != etp::EmlUri::eDATA_SPACE) {
+ ctx.err() << "Warning: Invalid URI: Not of dataspace type: " << space.m_uri << std::endl;
+ }
+
+ int64_t cetp_usec = space.m_storeCreated;
+ std::string cts = printMicroSecDateTime(
+ cetp_usec, ctx.hasArg("utc"), ctx.hasArg("iso"), ctx.debug
+ );
+
+ int64_t metp_usec = space.m_storeLastWrite;
+ std::string mts = printMicroSecDateTime(
+ metp_usec, ctx.hasArg("utc"), ctx.hasArg("iso"), ctx.debug
+ );
+
+ std::ostream& out = ctx.out();
+ fmt::print(out, R"(
+Dataspace {}:
+ URI: {}
+ Uid: {}
+ Path: {}
+ Created: {}
+ Last-Modified: {}
+)",
+ space_path.empty()? dflt: space_path,
+ space.m_uri, uri.dataspace_, space.m_path, cts, mts
+ );
+
+ if (const size_t N = space.m_customData.size()) {
+ fmt::print(out, "\n{} custom data:\n", N);
+ size_t max_key = 0;
+ for (const auto& entry : space.m_customData) {
+ const size_t ks = entry.first.size();
+ if (ks <= 32u) {
+ max_key = std::max<>(max_key, ks);
+ }
+ }
+ const size_t tw = ctx.terminalWidth_;
+ const size_t kw = std::max(14, std::min