diff --git a/.vscode/launch.json b/.vscode/launch.json index fcb0ccac1..dabd46511 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,15 +6,31 @@ "configurations": [ { "command": "npm start", - "name": "Launch web app", + "name": "Launch web app with Intersection Data", + "request": "launch", + "cwd": "${workspaceFolder}/webapp", + "type": "node-terminal", + "preLaunchTask": "run-intersection" + }, + { + "command": "npm start", + "name": "Launch web app without Intersection Data", "request": "launch", "cwd": "${workspaceFolder}/webapp", "type": "node-terminal", "preLaunchTask": "run-keycloak-and-postgres" }, + { + "command": "npm start", + "name": "Launch web app with Full ConflictMonitor", + "request": "launch", + "cwd": "${workspaceFolder}/webapp", + "type": "node-terminal", + "preLaunchTask": "run-full-conflictmonitor" + }, { "name": "Python: Flask", - "type": "python", + "type": "debugpy", "request": "launch", "stopOnEntry": false, "program": "${workspaceRoot}/services/api/src/main.py", @@ -29,7 +45,7 @@ "compounds": [ { "name": "Debug Solution", - "configurations": ["Python: Flask", "Launch web app"] + "configurations": ["Python: Flask", "Launch web app with Intersection Data"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index ac1ec05f1..5920a681b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,5 +22,40 @@ "[python]": { "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true - } + }, + "cSpell.words": [ + "BOOTSTRAPSERVERS", + "cdot", + "cimms", + "collstats", + "conflictmonitor", + "conflictvisualizer", + "cviz", + "cvmanager", + "CVPEP", + "formik", + "geojsonconverter", + "healthcheck", + "inet", + "INITDB", + "inprog", + "keyfile", + "LOGLEVEL", + "MESSAGETYPE", + "millis", + "mongosh", + "pgdb", + "postgis", + "pytz", + "reduxjs", + "rsus", + "SASL", + "SNMP", + "svcs", + "TABLENAME", + "usdotjpoode", + "utctimestamp", + "wydot", + "Zabbix" + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e34b83b7f..7c4624ecf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,6 +14,27 @@ }, "command": "${command:python.interpreterPath} -m pytest -v --cov-report xml:cov.xml --cov ." }, + { + "label": "run-intersection", + "type": "docker-compose", + "dockerCompose": { + "up": { + "detached": true, + "build": true, + "services": [ + "cvmanager_postgres", + "cvmanager_keycloak", + "kafka", + "kafka_init", + "conflictvisualizer_api", + "mongodb_container" + ] + }, + "files": ["${workspaceFolder}/docker-compose.yml"], + "envFile": "${workspaceFolder}/.env" + }, + "dependsOn": ["run-keycloak-and-postgres"] + }, { "label": "run-keycloak-and-postgres", "type": "docker-compose", @@ -26,6 +47,32 @@ "files": ["${workspaceFolder}/docker-compose.yml"], "envFile": "${workspaceFolder}/.env" } + }, + { + "label": "run-full-conflictmonitor", + "type": "docker-compose", + "dockerCompose": { + "up": { + "detached": true, + "build": true, + "services": [ + "cvmanager_postgres", + "cvmanager_keycloak", + "kafka", + "kafka_init", + "ode", + "geojsonconverter", + "conflictmonitor", + "deduplicator", + "conflictvisualizer_api", + "mongodb_container", + "connect" + ] + }, + "files": ["${workspaceFolder}/docker-compose-full-cm.yml"], + "envFile": "${workspaceFolder}/.env" + }, + "dependsOn": ["run-keycloak-and-postgres"] } ] } diff --git a/README.md b/README.md index 56425ad5f..6056d21a2 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,102 @@ The JPO CV Manager was originally developed for the Google Cloud Platform and a - Keycloak is used for the CV Manager webapp's authentication. - The Keycloak pod requires a `realm.json` file in the folder: `./resources/keycloak/` to startup with the proper configurations. It also requires a login theme that can be modified and generated using the [keycloakify](https://github.com/keycloakify/keycloakify) forked repository in resources/keycloak/keycloakify. The theme will be automatically generated when using the docker image provided but can also be built using instructions found in the keycloakify folder. +### Intersection Data + ConflictMonitor Integration + +The CVManager now has the ability to manage, configure, and display data from connected intersections. Using the JPO-ODE ConflictMonitor and other JPO-ODE resources, intersection-specific data can be collected, processed, and analyzed. The CVManager has the ability to display the results of this analysis, show live message data, and configure intersection monitoring. This includes the following: + +- Displaying live MAPs, SPATs, and BSMs on a Mapbox map +- Displaying archived MAPs, SPATs, and BSMs on a Mapbox map +- Querying, downloading, and displaying events created by the ConflictMonitor +- Querying, downloading, and displaying assessments of events created by the ConflictMonitor +- Querying, managing, and displaying notifications created by the ConflictMonitor +- Updating and managing configuration parameters controlling message analysis, assessments, and notifications + +More information on the ConflictMonitor and other services described above can be found here: + +- [jpo-conflictmonitor](https://github.com/usdot-jpo-ode/jpo-conflictmonitor) +- [jpo-geojsonconverter](https://github.com/usdot-jpo-ode/jpo-geojsonconverter) +- [jpo-ode](https://github.com/usdot-jpo-ode/jpo-ode) +- [jpo-conflictvisualizer](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer) + +**Ongoing Efforts** +This feature is under active development. This is a joint effort involving combining the features of the existing CIMMS conflictvisualizer tools with the CVManager components, to enable connected vehicle and intersection analysis in one application. + +#### Local Development + +Ease of local development has been a major consideration in the integration of intersection data into the CVManager application. Through the use of public docker images and sample datasets, this process is relatively simple. The services required to show intersection data on the CVManager webapp are: + +- [jpo-conflictvisualizer (api)](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer/tree/cvmgr-cimms-integration/api) + - Modified jpo-conflictvisualizer api which is able to utilize the cvmanager keycloak realm +- kafka + - Base kafka image used to supply required topics to the conflictvisualizer api +- kafka_init + - Kafka topic creation image, to create required topics for the conflictvisualizer api +- MongoDB + - Base MongoDB image, with sample data, used to supply data to the conflictvisualizer api + +**Running a Simple Local Environment** + +1. Update your .env from the sample.env, all intersection-specific service variables are at the bottom. +2. Build the docker-compose: + +```sh +docker compose up -d +``` + +If any issues occur, try: + +```sh +docker compose up --build -d +``` + +This command will create all of the CVManager containers as well a the intersection-specific containers. Now, intersection-specific data will be available through the CVManager webapp. + +**Running the CVManager without Intersection Services** + +1. Update your .env from the sample_no_cm.env (It is not necessary to clear out the intersection-specific variables) +2. Build the docker-compose-no-cm: + If you would like to run all of the ConflictMonitor services including the JPO-ODE and GeoJSONConverter, use the docker-compose-full-cm.yml: + +```sh +docker compose -f docker-compose-no-cm.yml up --build -d +``` + +**Running all ConflictMonitor Services** + +1. Update your .env from the sample.env, all intersection-specific service variables are at the bottom. No additional variables are currently required on top of the simple intersection configuration. +2. Build the combined docker-compose: + +```sh +docker compose -f docker-compose-full-cm.yml up --build -d +``` + +**ConflictMonitor Configuration Scripts** + +A set of scripts and data dumps exists in the [conflictmonitor folder](./conflictmonitor), see the readme in that location for more information. + +#### ConflictVisualizer API + +- The CV Manager webapp has been integrated with the ConflictVisualizer tool to allow users to view data directly from a jpo-conflictmonitor instance. This integration currently requires an additional jpo-conflictvisualizer api to be deployed alongside the jpo-cvmanager api. This allows the webapp to make authenticated requests to the jpo-conflictvisualizer api to retrieve the conflict monitor data. +- [jpo-conflictvisualizer (api)](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer/tree/cvmgr-cimms-integration/api) +- kafka +- kafka_init (to create required kafka topics) +- MongoDB (to hold message and configuration data) + +The ConflictVisualizer api pulls archived message and configuration data from MongoDB, and is able to live-stream SPATs, MAPs, and BSMs from specific kafka topics + +#### MongoDB + +MongoDB is the backing database of the ConflictVisualizer api. This database holds configuration parameters, archived data (SPATs, MAPs, BSMs, ...), and processed data (notifications, assessments, events). For local development, a mongodump has been created in the conflictmonitor/mongo/dump_2024_08_20 directory. This includes notifications, assessments, events, as well as SPATs, MAPs, and BSMs. All of this data is available through the conflictvisualizer api. + +#### Kafka + +Kafka is used by the ConflictVisualizer api to receive data from the ODE, GeoJSONConverter, and ConflictMonitor. These connections enable live data to + +#### Generating Sample Data + +Some simple sample data is injected into the MongoDB instance when created. If more data is useful, the test-message-sender from the jpo-conflictmonitor can also be used to generate live sample data. This component should be cloned/installed separately, and is described here: [jpo-conflictmonitor/test-message-sender](https://github.com/usdot-jpo-ode/jpo-conflictmonitor/tree/develop/test-message-sender) + ## Getting Started The following steps are intended to help get a new user up and running the JPO CV Manager in their own environment. @@ -55,6 +151,7 @@ The following steps are intended to help get a new user up and running the JPO C 1. Follow the Requirements and Limitations section and make sure all requirements are met. 2. Create a copy of the sample.env named ".env" and refer to the Environmental variables section below for more information on each variable. 1. Make sure at least the DOCKER_HOST_IP, KEYCLOAK_ADMIN_PASSWORD, KEYCLOAK_API_CLIENT_SECRET_KEY, and MAPBOX_TOKEN are set for this. + 2. Some of these variables, delineated by sections, pertain to the [jpo-conflictvisualizer (api)](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer/tree/cvmgr-cimms-integration/api), [jpo-conflictmonitor](https://github.com/usdot-jpo-ode/jpo-conflictmonitor), [jpo-geojsonconverter](https://github.com/usdot-jpo-ode/jpo-geojsonconverter), [jpo-ode](https://github.com/usdot-jpo-ode/jpo-ode). Please see the documentation provided for these projects when setting these variables. 3. The CV Manager has four components that need to be containerized and deployed: the API, the PostgreSQL database, Keycloak, and the webapp. - If you are looking to deploy the CV Manager locally, you can simply run the docker-compose, make sure to fill out the .env file to ensure it launches properly. Also, edit your host file ([How to edit the host file](<[resources/kubernetes](https://docs.rackspace.com/support/how-to/modify-your-hosts-file/)>)) and add IP address of your docker host to these custom domains (remove the carrot brackets and just put the IP address): @@ -66,7 +163,15 @@ The following steps are intended to help get a new user up and running the JPO C 4. Apply the docker compose to start the required components: - docker compose up -d + ```sh + docker compose up -d + ``` + + If any issues occur, try: + + ```sh + docker compose up --build -d + ``` 5. Access the website by going to: @@ -144,7 +249,15 @@ For the "Debug Solution" to run properly on Windows 10/11 using WSL, the followi 3. Apply the docker compose to start the required components: - docker compose up -d +```sh +docker compose up -d +``` + +To run only the critical cvmanager components (no conflictmonitor/conflictvisualizer), use this command: + +```sh +docker compose up -d cvmanager_api cvmanager_webapp cvmanager_postgres cvmanager_keycloak +``` 4. Access the website by going to: @@ -189,13 +302,15 @@ For the "Debug Solution" to run properly on Windows 10/11 using WSL, the followi - SSM_DB_NAME: The database name for SSM visualization data. - SRM_DB_NAME: The database name for SRM visualization data. - FIRMWARE_MANAGER_ENDPOINT: Endpoint for the firmware manager deployment's API. -- CSM_EMAIL_TO_SEND_FROM: Origin email address for the API. +- CSM_EMAIL_TO_SEND_FROM: Origin email address for the API error developer emails. +- CSM_EMAILS_TO_SEND_TO: Destination email addresses for the API error developer emails. - CSM_EMAIL_APP_USERNAME: Username for the SMTP server. - CSM_EMAIL_APP_PASSWORD: Password for the SMTP server. -- CSM_EMAILS_TO_SEND_TO: Destination email list. - CSM_TARGET_SMTP_SERVER_ADDRESS: Destination SMTP server address. - CSM_TARGET_SMTP_SERVER_PORT: Destination SMTP server port. - API_LOGGING_LEVEL: The level of which the CV Manager API will log. (DEBUG, INFO, WARNING, ERROR) +- CSM_TLS_ENABLED: Set to "true" if the SMTP server requires TLS. +- CSM_AUTH_ENABLED: Set to "true" if the SMTP server requires authentication. - WZDX_ENDPOINT: WZDX datafeed endpoint. - WZDX_API_KEY: API key for the WZDX datafeed. - TIMEZONE: Timezone to be used for the API. @@ -210,8 +325,30 @@ For the "Debug Solution" to run properly on Windows 10/11 using WSL, the followi MongoDB Variables +#### For Windows Users Only + +If running on Windows, please make sure that your global git config is set up to not convert end-of-line characters during checkout. + +Disable `git core.autocrlf` (One Time Only) + +```bash +git config --global core.autocrlf false +``` + - MONGO_DB_URI: URI for the MongoDB connections. - MONGO_DB_NAME: Database name for RSU counts. +- MONGO_ADMIN_DB_USER: Admin Username for MongoDB +- MONGO_ADMIN_DB_PASS: Admin Password for MongoDB +- MONGO_CV_MANAGER_DB_USER: CV Manager Username for MongoDB +- MONGO_CV_MANAGER_DB_PASS: CV Manager Password for MongoDB + +- MONGO_IP: IP Address of the MongoDB (Defaults to $DOCKER_HOST_IP) +- MONGO_DB_USER: Username of the account used to connect to MongoDB +- MONGO_DB_PASS: Password of the account used to connect to MongoDB +- MONGO_PORT: Port number of MongoDB (default is 27017) +- MONGO_COLLECTION_TTL: Number of days documents will be kept in a MongoDB collection + +- INSERT_SAMPLE_DATA: If true, sample data will be inserted in the CVCounts, V2XGeoJson, and OdeSsmJson collections Keycloak Variables diff --git a/conflictmonitor/README.md b/conflictmonitor/README.md new file mode 100644 index 000000000..8773e1329 --- /dev/null +++ b/conflictmonitor/README.md @@ -0,0 +1,11 @@ +# JPO-Conflictmonitor Components + +This directory contains configuration files for the jpo-conflictmonitor, referenced in the docker-compose.yml. These files configure the kafka init container as well as the mongodb container. + +## kafka + +This directory contains kafka scripts, used for initializing the relevant kafka topics required for ConflictMonitor components. This script came from the JPO-ConflictMonitor repository, and has been modified to create additional topics required by the conflictvisualizer api. + +## mongo + +This directory contains scripts from the ConflictMonitor for initializing collections and indexes, as well as a mongodump dataset used for the visualizer. These files are referenced in the docker-compose.yml, and should not need to be modified for local development. diff --git a/conflictmonitor/kafka/kafka_init.sh b/conflictmonitor/kafka/kafka_init.sh new file mode 100644 index 000000000..7109bbbb9 --- /dev/null +++ b/conflictmonitor/kafka/kafka_init.sh @@ -0,0 +1,93 @@ +sleep 2s +/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --list +echo 'Creating kafka topics' + +# Create topics +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSpatTxPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSpatPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.FilteredOdeSpatJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSpatRxPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeBsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.FilteredOdeBsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.J2735TimBroadcastJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.Asn1DecoderInput" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.Asn1EncoderInput" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.SDWDepositorInput" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeMapTxPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSsmPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSrmTxPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdePsmTxPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdePsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.Asn1DecoderOutput" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.Asn1EncoderOutput" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmAppHealthNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmAppHealthNotifications" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmAssessment" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmBsmEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmBsmIntersection" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmBsmJsonRepartition" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmConnectionOfTravelAssessment" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmConnectionOfTravelEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmConnectionOfTravelNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmCustomConfigTable" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmDefaultConfigTable" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmIntersectionConfigTable" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmIntersectionReferenceAlignmentEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmIntersectionReferenceAlignmentNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmKafkaStateChangeEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmLaneDirectionOfTravelAssessment" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmLaneDirectionOfTravelEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmLaneDirectionOfTravelNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmMapBoundingBox" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmMapBroadcastRateEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmMapMinimumDataEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmMergedConfigTable" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSignalGroupAlignmentEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSignalGroupAlignmentNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSignalStateConflictEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSignalStateConflictNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSignalStateEventAssessment" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSpatBroadcastRateEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSpatMinimumDataEvents" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSpatTimeChangeDetailsEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmSpatTimeChangeDetailsNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmStopLinePassageEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmStopLinePassageNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmStopLineStopAssessment" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmStopLineStopEvent" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.CmStopLineStopNotification" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.DeduplicatedOdeMapJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.DeduplicatedOdeTimJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.DeduplicatedProcessedMap" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.DeduplicatedProcessedMapWKT" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.FilteredOdeBsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.FilteredOdeTimJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeBsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeBsmPojo" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeDriverAlertJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeMapJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedBSMJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedMAPJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedPSMJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedSPATJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedSRMJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedSSMJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeRawEncodedTIMJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSpatJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSpatRxJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSrmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeSsmJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeTIMCertExpirationTimeJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeTimBroadcastJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.OdeTimJson" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.ProcessedMap" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.ProcessedMapWKT" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 +/opt/bitnami/kafka/bin/kafka-topics.sh --create --if-not-exists --topic "topic.ProcessedSpat" --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 + + + +echo 'Kafka created with the following topics:' +/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --list +exit \ No newline at end of file diff --git a/conflictmonitor/mongo/Dockerfile b/conflictmonitor/mongo/Dockerfile new file mode 100644 index 000000000..edf4f7172 --- /dev/null +++ b/conflictmonitor/mongo/Dockerfile @@ -0,0 +1,5 @@ +FROM mongo:6 + +COPY a_init_replicas.js /docker-entrypoint-initdb.d/a_init_replicas.js +COPY b_create_indexes.js /docker-entrypoint-initdb.d/b_create_indexes.js + diff --git a/conflictmonitor/mongo/a_init_replicas.js b/conflictmonitor/mongo/a_init_replicas.js new file mode 100644 index 000000000..303c3c521 --- /dev/null +++ b/conflictmonitor/mongo/a_init_replicas.js @@ -0,0 +1,38 @@ +/* + +This script is the first of two scripts responsible for setting up mongoDB on initial boot. +These scripts should be copied to the /docker-entrypoint-initdb.d/ directory within the docker image; +the docker image will then execute these scripts automatically when the database is first created. +This script and its partner are prefixed with the letters a and b respectively to ensure they are run +in the proper order when copied into the mongoDB docker image. + +Since the conflict monitor uses a replica set in its mongoDB configuration. Initializing the replica set +and configuring the collections are separated from one another. The purpose of this to force a reconnect +to the database. This in turn allows the new connection to be connected to the primary replica which is +required for creating indexes on all the collections. This script is only responsible for creating the +replica set. Almost all other configuration should go in b_create_indexes.js + +Documentation on how the mongoDB docker image runs startup scripts can be found here +https://hub.docker.com/_/mongo/ + +*/ + +const CM_MONGO_ROOT_USERNAME = process.env.MONGO_INITDB_ROOT_USERNAME +const CM_MONGO_ROOT_PASSWORD = process.env.MONGO_INITDB_ROOT_PASSWORD + +db = db.getSiblingDB('admin') +db.auth(CM_MONGO_ROOT_USERNAME, CM_MONGO_ROOT_PASSWORD) +db = db.getSiblingDB('ConflictMonitor') + +console.log('Initializing Replicas') +try { + db_status = rs.status() +} catch (err) { + console.log('Initializing New DB') + try { + rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] }).ok + } catch (err) { + console.log('Unable to Initialize DB') + console.log(err) + } +} diff --git a/conflictmonitor/mongo/b_create_indexes.js b/conflictmonitor/mongo/b_create_indexes.js new file mode 100644 index 000000000..be021552f --- /dev/null +++ b/conflictmonitor/mongo/b_create_indexes.js @@ -0,0 +1,622 @@ +// Create indexes on all collections + +/* +This is the second script responsible for configuring mongoDB automatically on startup. +This script is responsible for creating users, creating collections, adding indexes, and configuring TTLs +For more information see the header in a_init_replicas.js +*/ + +console.log('') +console.log('Running create_indexes.js') + +// Setup Username and Password Definitions +const CM_MONGO_ROOT_USERNAME = process.env.CM_MONGO_ROOT_USERNAME +const CM_MONGO_ROOT_PASSWORD = process.env.CM_MONGO_ROOT_PASSWORD + +const CM_MONGO_CONNECTOR_USERNAME = process.env.CM_MONGO_CONNECTOR_USERNAME +const CM_MONGO_CONNECTOR_PASSWORD = process.env.CM_MONGO_CONNECTOR_PASSWORD + +const CM_MONGO_API_USERNAME = process.env.CM_MONGO_API_USERNAME +const CM_MONGO_API_PASSWORD = process.env.CM_MONGO_API_PASSWORD + +const CM_MONGO_USER_USERNAME = process.env.CM_MONGO_USER_USERNAME +const CM_MONGO_USER_PASSWORD = process.env.CM_MONGO_USER_PASSWORD + +const CM_DATABASE_NAME = process.env.CM_DATABASE_NAME || 'ConflictMonitor' + +const expireSeconds = 5184000 // 2 months +const retryMilliseconds = 10000 + +const users = [ + // {username: CM_MONGO_ROOT_USERNAME, password: CM_MONGO_ROOT_PASSWORD, roles: "root", database: "admin" }, + { + username: CM_MONGO_CONNECTOR_USERNAME, + password: CM_MONGO_CONNECTOR_PASSWORD, + roles: 'readWrite', + database: CM_DATABASE_NAME, + }, + { username: CM_MONGO_API_USERNAME, password: CM_MONGO_API_PASSWORD, roles: 'readWrite', database: CM_DATABASE_NAME }, + { username: CM_MONGO_USER_USERNAME, password: CM_MONGO_USER_PASSWORD, roles: 'read', database: CM_DATABASE_NAME }, +] + +// name -> collection name +// ttlField -> field to perform ttl on +// timeField -> field to index for time queries +// intersectionField -> field containing intersection id for id queries +const collections = [ + // ODE Json data + { + name: 'OdeDriverAlertJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeBsmJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeMapJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeSpatJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeSpatRxJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeSrmJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeSsmJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeTimJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeTimBroadcastJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + { + name: 'OdeTIMCertExpirationTimeJson', + ttlField: 'recordGeneratedAt', + timeField: 'metadata.odeReceivedAt', + intersectionField: null, + rsuIP: 'metadata.originIp', + }, + + // Ode Raw ASN + { + name: 'OdeRawEncodedBSMJson', + ttlField: 'recordGeneratedAt', + timeField: 'BsmMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'BsmMessageContent.metadata.originRsu', + }, + { + name: 'OdeRawEncodedMAPJson', + ttlField: 'recordGeneratedAt', + timeField: 'MapMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'MapMessageContent.metadata.originRsu', + }, + { + name: 'OdeRawEncodedSPATJson', + ttlField: 'recordGeneratedAt', + timeField: 'SpatMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'SpatMessageContent.metadata.originRsu', + }, + { + name: 'OdeRawEncodedSRMJson', + ttlField: 'recordGeneratedAt', + timeField: 'SrmMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'SpatMessageContent.metadata.originRsu', + }, + { + name: 'OdeRawEncodedSSMJson', + ttlField: 'recordGeneratedAt', + timeField: 'SsmMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'SpatMessageContent.metadata.originRsu', + }, + { + name: 'OdeRawEncodedTIMJson', + ttlField: 'recordGeneratedAt', + timeField: 'TimMessageContent.metadata.utctimestamp', + intersectionField: null, + rsuIP: 'SpatMessageContent.metadata.originRsu', + }, + + // GeoJson Converter Data + { + name: 'ProcessedMap', + ttlField: 'recordGeneratedAt', + timeField: 'properties.timeStamp', + intersectionField: 'properties.intersectionId', + }, + { + name: 'ProcessedSpat', + ttlField: 'recordGeneratedAt', + timeField: 'utcTimeStamp', + intersectionField: 'intersectionId', + }, + + // Conflict Monitor Events + { + name: 'CmStopLineStopEvent', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmStopLinePassageEvent', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmIntersectionReferenceAlignmentEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSignalGroupAlignmentEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmConnectionOfTravelEvent', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSignalStateConflictEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmLaneDirectionOfTravelEvent', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSpatTimeChangeDetailsEvent', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSpatMinimumDataEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmMapBroadcastRateEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmMapMinimumDataEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSpatBroadcastRateEvents', + ttlField: 'eventGeneratedAt', + timeField: 'eventGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmBsmEvents', + ttlField: 'recordGeneratedAt', + timeField: 'recordGeneratedAt', + intersectionField: 'intersectionID', + }, + + // Conflict Monitor Assessments + { + name: 'CmLaneDirectionOfTravelAssessment', + ttlField: 'assessmentGeneratedAt', + timeField: 'assessmentGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmConnectionOfTravelAssessment', + ttlField: 'assessmentGeneratedAt', + timeField: 'assessmentGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSignalStateEventAssessment', + ttlField: 'assessmentGeneratedAt', + timeField: 'assessmentGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmStopLineStopAssessment', + ttlField: 'assessmentGeneratedAt', + timeField: 'assessmentGeneratedAt', + intersectionField: 'intersectionID', + }, + + // Conflict Monitor Notifications + { + name: 'CmSpatTimeChangeDetailsNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmLaneDirectionOfTravelNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmConnectionOfTravelNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmAppHealthNotifications', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSignalStateConflictNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmSignalGroupAlignmentNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmStopLinePassageNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmStopLineStopNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + { + name: 'CmNotification', + ttlField: 'notificationGeneratedAt', + timeField: 'notificationGeneratedAt', + intersectionField: 'intersectionID', + }, + + // Mongo Management Collection + { name: 'MongoStorage', ttlField: null, timeField: 'recordGeneratedAt', intersectionField: null }, +] + +try { + db.getMongo().setReadPref('primaryPreferred') + db = db.getSiblingDB('ConflictMonitor') + db.getMongo().setReadPref('primaryPreferred') + var isMaster = db.isMaster() + if (isMaster.primary) { + console.log('Connected to the primary replica set member.') + } else { + console.log('Not connected to the primary replica set member. Current node: ' + isMaster.host) + } +} catch (err) { + console.log('Could not switch DB to Sibling DB') + console.log(err) +} + +// Create Users in Database +for (user of users) { + createUser(user) +} + +// Wait for the collections to exist in mongo before trying to create indexes on them +let missing_collection_count +do { + try { + missing_collection_count = 0 + const collectionNames = db.getCollectionNames() + for (collection of collections) { + // Create Collection if it doesn't exist + let created = false + if (!collectionNames.includes(collection.name)) { + created = createCollection(collection) + // created = true; + } else { + created = true + } + + if (created) { + createTTLIndex(collection) + createTimeIntersectionIndex(collection) + createTimeRsuIpIndex(collection) + createTimeIndex(collection) + } else { + missing_collection_count++ + console.log('Collection ' + collection.name + ' does not exist yet') + } + } + if (missing_collection_count > 0) { + console.log( + 'Waiting on ' + + missing_collection_count + + ' collections to be created...will try again in ' + + retryMilliseconds + + ' ms' + ) + sleep(retryMilliseconds) + } + } catch (err) { + console.log('Error while setting up TTL indexes in collections') + console.log(rs.status()) + console.error(err) + sleep(retryMilliseconds) + } +} while (missing_collection_count > 0) + +console.log('Finished Creating All TTL indexes') + +function createUser(user) { + try { + console.log('Creating User: ' + user.username + ' with Permissions: ' + user.roles) + db.createUser({ + user: user.username, + pwd: user.password, + roles: [{ role: user.roles, db: user.database }], + }) + } catch (err) { + console.log(err) + console.log('Unable to Create User. Perhaps the User already exists.') + } +} + +function createCollection(collection) { + try { + db.createCollection(collection.name) + return true + } catch (err) { + console.log('Unable to Create Collection: ' + collection.name) + console.log(err) + return false + } +} + +// Create TTL Indexes +function createTTLIndex(collection) { + try { + if (collection.hasOwnProperty('ttlField') && collection.ttlField != null) { + const ttlField = collection.ttlField + const collectionName = collection.name + + let indexJson = {} + indexJson[ttlField] = 1 + + if (ttlIndexExists(collection)) { + db.runCommand({ + collMod: collectionName, + index: { + keyPattern: indexJson, + expireAfterSeconds: expireSeconds, + }, + }) + console.log('Updated TTL index for ' + collectionName + ' using the field: ' + ttlField + ' as the timestamp') + } else { + db[collectionName].createIndex(indexJson, { expireAfterSeconds: expireSeconds }) + console.log('Created TTL index for ' + collectionName + ' using the field: ' + ttlField + ' as the timestamp') + } + } + } catch (err) { + console.log( + 'Failed to Create or Update index for ' + collectionName + 'using the field: ' + ttlField + ' as the timestamp' + ) + } +} + +function createTimeIndex(collection) { + if (timeIndexExists(collection)) { + // Skip if Index already Exists + return + } + + if (collection.hasOwnProperty('timeField') && collection.timeField != null) { + const collectionName = collection.name + const timeField = collection.timeField + console.log('Creating Time Index for ' + collectionName) + + var indexJson = {} + indexJson[timeField] = -1 + + try { + db[collectionName].createIndex(indexJson) + console.log( + 'Created Time Intersection index for ' + collectionName + ' using the field: ' + timeField + ' as the timestamp' + ) + } catch (err) { + db.runCommand({ + collMod: collectionName, + index: { + keyPattern: indexJson, + }, + }) + console.log('Updated Time index for ' + collectionName + ' using the field: ' + timeField + ' as the timestamp') + } + } +} + +function createTimeRsuIpIndex() { + if (timeRsuIpIndexExists(collection)) { + // Skip if Index already Exists + return + } + + if ( + collection.hasOwnProperty('timeField') && + collection.timeField != null && + collection.hasOwnProperty('rsuIP') && + collection.rsuIP != null + ) { + const collectionName = collection.name + const timeField = collection.timeField + const rsuIP = collection.rsuIP + console.log('Creating Time rsuIP Index for ' + collectionName) + + var indexJson = {} + indexJson[rsuIP] = -1 + indexJson[timeField] = -1 + + try { + db[collectionName].createIndex(indexJson) + console.log( + 'Created Time rsuIP Intersection index for ' + + collectionName + + ' using the field: ' + + timeField + + ' as the timestamp and : ' + + rsuIP + + ' as the rsuIP' + ) + } catch (err) { + db.runCommand({ + collMod: collectionName, + index: { + keyPattern: indexJson, + }, + }) + console.log( + 'Updated Time rsuIP index for ' + + collectionName + + ' using the field: ' + + timeField + + ' as the timestamp and : ' + + rsuIP + + ' as the rsuIP' + ) + } + } +} + +function createTimeIntersectionIndex(collection) { + if (timeIntersectionIndexExists(collection)) { + // Skip if Index already Exists + return + } + + if ( + collection.hasOwnProperty('timeField') && + collection.timeField != null && + collection.hasOwnProperty('intersectionField') && + collection.intersectionField != null + ) { + const collectionName = collection.name + const timeField = collection.timeField + const intersectionField = collection.intersectionField + console.log('Creating time intersection index for ' + collectionName) + + var indexJson = {} + indexJson[intersectionField] = -1 + indexJson[timeField] = -1 + + try { + db[collectionName].createIndex(indexJson) + console.log( + 'Created time intersection index for ' + + collectionName + + ' using the field: ' + + timeField + + ' as the timestamp and : ' + + intersectionField + + ' as the rsuIP' + ) + } catch (err) { + db.runCommand({ + collMod: collectionName, + index: { + keyPattern: indexJson, + }, + }) + console.log( + 'Updated time intersection index for ' + + collectionName + + ' using the field: ' + + timeField + + ' as the timestamp and : ' + + intersectionField + + ' as the rsuIP' + ) + } + } +} + +function ttlIndexExists(collection) { + return db[collection.name].getIndexes().find((idx) => idx.hasOwnProperty('expireAfterSeconds')) !== undefined +} + +function timeIntersectionIndexExists(collection) { + return ( + db[collection.name] + .getIndexes() + .find((idx) => idx.name == collection.intersectionField + '_-1_' + collection.timeField + '_-1') !== undefined + ) +} + +function timeRsuIpIndexExists(collection) { + return ( + db[collection.name] + .getIndexes() + .find((idx) => idx.name == collection.rsuIP + '_-1_' + collection.timeField + '_-1') !== undefined + ) +} + +function timeIndexExists(collection) { + return db[collection.name].getIndexes().find((idx) => idx.name == collection.timeField + '_-1') !== undefined +} diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmAppHealthNotifications.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmAppHealthNotifications.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmAppHealthNotifications.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmAppHealthNotifications.metadata.json new file mode 100644 index 000000000..c13fe3578 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmAppHealthNotifications.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"92411e5291cf418c9f41ff9d566f993e","collectionName":"CmAppHealthNotifications","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.bson new file mode 100644 index 000000000..1c3e7499d Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.metadata.json new file mode 100644 index 000000000..33dc04498 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmBsmEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"recordGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_recordGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"-1"}},"name":"recordGeneratedAt_-1"}],"uuid":"59245e7327b740db904193929b3feb3d","collectionName":"CmBsmEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.bson new file mode 100644 index 000000000..676d808fa Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.metadata.json new file mode 100644 index 000000000..734fe8da2 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelAssessment.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"1"}},"name":"assessmentGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_assessmentGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"assessmentGeneratedAt_-1"}],"uuid":"898eb35088194246ae8736ee9ba7a79a","collectionName":"CmConnectionOfTravelAssessment","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.bson new file mode 100644 index 000000000..b333b4450 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.metadata.json new file mode 100644 index 000000000..3d5de4c42 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelEvent.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"aee5c8e72cbd4de4867e5bdf60f7aa94","collectionName":"CmConnectionOfTravelEvent","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.bson new file mode 100644 index 000000000..e8524b13a Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.metadata.json new file mode 100644 index 000000000..37e1ffb8c --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmConnectionOfTravelNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"7d3debaa2a434f2fac503ef5f212bfd6","collectionName":"CmConnectionOfTravelNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmIntersectionReferenceAlignmentEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmIntersectionReferenceAlignmentEvents.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmIntersectionReferenceAlignmentEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmIntersectionReferenceAlignmentEvents.metadata.json new file mode 100644 index 000000000..93edf73e7 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmIntersectionReferenceAlignmentEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"3d817377165c4c12a521259972fe83e5","collectionName":"CmIntersectionReferenceAlignmentEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.bson new file mode 100644 index 000000000..053e764ef Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.metadata.json new file mode 100644 index 000000000..4168a2ef7 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelAssessment.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"1"}},"name":"assessmentGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_assessmentGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"assessmentGeneratedAt_-1"}],"uuid":"e75ddc4389574f579313a1c59c909aef","collectionName":"CmLaneDirectionOfTravelAssessment","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.bson new file mode 100644 index 000000000..26ed57664 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.metadata.json new file mode 100644 index 000000000..5626b99b5 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelEvent.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"6cc659d287e345429a0af3c22881a7a7","collectionName":"CmLaneDirectionOfTravelEvent","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelNotification.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelNotification.metadata.json new file mode 100644 index 000000000..e986d5445 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmLaneDirectionOfTravelNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"a330e14c06cd4671ad4d6c158f2e6f4b","collectionName":"CmLaneDirectionOfTravelNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.bson new file mode 100644 index 000000000..e516d8270 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.metadata.json new file mode 100644 index 000000000..edb1dc366 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapBroadcastRateEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"e46adb65bf9640ccbe771860cecdc509","collectionName":"CmMapBroadcastRateEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.bson new file mode 100644 index 000000000..b68582e53 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.metadata.json new file mode 100644 index 000000000..d69bcfe29 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmMapMinimumDataEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"e99d527c2e95468ab71462119a643e97","collectionName":"CmMapMinimumDataEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.bson new file mode 100644 index 000000000..b05a6eef7 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.metadata.json new file mode 100644 index 000000000..8ebbc7121 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"eabb40aea6474584ad95b68b21389093","collectionName":"CmNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentEvents.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentEvents.metadata.json new file mode 100644 index 000000000..6a9391a43 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"8328a9d2938f498186bb7fddfe592d0d","collectionName":"CmSignalGroupAlignmentEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentNotification.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentNotification.metadata.json new file mode 100644 index 000000000..fcd65388f --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalGroupAlignmentNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"37752835aec84553a6da5b157e349916","collectionName":"CmSignalGroupAlignmentNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.bson new file mode 100644 index 000000000..c1376e234 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.metadata.json new file mode 100644 index 000000000..aa0e61554 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"fc7592e5e22a4d4d839bdef3d67f8df0","collectionName":"CmSignalStateConflictEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.bson new file mode 100644 index 000000000..6fc89a86e Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.metadata.json new file mode 100644 index 000000000..f987bf11b --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateConflictNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"8c12d548508c469fb0608f3da3541972","collectionName":"CmSignalStateConflictNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.bson new file mode 100644 index 000000000..e45d1457b Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.metadata.json new file mode 100644 index 000000000..4737a7668 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSignalStateEventAssessment.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"1"}},"name":"assessmentGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_assessmentGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"assessmentGeneratedAt_-1"}],"uuid":"adf2ebacec734a23882284e57cf4ff46","collectionName":"CmSignalStateEventAssessment","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.bson new file mode 100644 index 000000000..78a5c7b2c Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.metadata.json new file mode 100644 index 000000000..dedb512f5 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatBroadcastRateEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"2a1850ad34e24bbb8e7ece763613af6f","collectionName":"CmSpatBroadcastRateEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.bson new file mode 100644 index 000000000..d5ed25d64 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.metadata.json new file mode 100644 index 000000000..6869b1ee8 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatMinimumDataEvents.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"0fbe65c42dd34577a43c0749e84a1c07","collectionName":"CmSpatMinimumDataEvents","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.bson new file mode 100644 index 000000000..47b6e31f1 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.metadata.json new file mode 100644 index 000000000..9bcfd7205 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsEvent.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"dce52437dc98477489db1bb95a3e737c","collectionName":"CmSpatTimeChangeDetailsEvent","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsNotification.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsNotification.metadata.json new file mode 100644 index 000000000..b82385a14 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmSpatTimeChangeDetailsNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"9d620a6e7aff43b7ab056964ec394fc1","collectionName":"CmSpatTimeChangeDetailsNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.bson new file mode 100644 index 000000000..b153a67d8 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.metadata.json new file mode 100644 index 000000000..c7fdedf7e --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageEvent.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"cf6964a7ec4345b9bb9cf3520733d5f4","collectionName":"CmStopLinePassageEvent","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.bson new file mode 100644 index 000000000..ee38186f4 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.metadata.json new file mode 100644 index 000000000..2a641613d --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLinePassageNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"f014869780154679af98c08cb5133aca","collectionName":"CmStopLinePassageNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopAssessment.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopAssessment.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopAssessment.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopAssessment.metadata.json new file mode 100644 index 000000000..1786236d0 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopAssessment.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"1"}},"name":"assessmentGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_assessmentGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"assessmentGeneratedAt":{"$numberInt":"-1"}},"name":"assessmentGeneratedAt_-1"}],"uuid":"00722b1d3d224d7abda9074cf74f6148","collectionName":"CmStopLineStopAssessment","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopEvent.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopEvent.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopEvent.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopEvent.metadata.json new file mode 100644 index 000000000..76ffcddd9 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopEvent.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"1"}},"name":"eventGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"eventGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_eventGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"eventGeneratedAt":{"$numberInt":"-1"}},"name":"eventGeneratedAt_-1"}],"uuid":"4c36b1e21b63454ea440c591517a5dea","collectionName":"CmStopLineStopEvent","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopNotification.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopNotification.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopNotification.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopNotification.metadata.json new file mode 100644 index 000000000..dd428aba4 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/CmStopLineStopNotification.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"1"}},"name":"notificationGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionID":{"$numberInt":"-1"},"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"intersectionID_-1_notificationGeneratedAt_-1"},{"v":{"$numberInt":"2"},"key":{"notificationGeneratedAt":{"$numberInt":"-1"}},"name":"notificationGeneratedAt_-1"}],"uuid":"d3c4aaf656be47379413cc615c769d22","collectionName":"CmStopLineStopNotification","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.bson new file mode 100644 index 000000000..8350326ae Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.metadata.json new file mode 100644 index 000000000..744ec1766 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/MongoStorage.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"-1"}},"name":"recordGeneratedAt_-1"}],"uuid":"f08f273cb7a744a4810f0c223d281103","collectionName":"MongoStorage","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.bson new file mode 100644 index 000000000..104e5e059 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.metadata.json new file mode 100644 index 000000000..656cb8e9e --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeBsmJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"9796744b53a64b01ac4cb63bf1ff73b5","collectionName":"OdeBsmJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeDriverAlertJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeDriverAlertJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeDriverAlertJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeDriverAlertJson.metadata.json new file mode 100644 index 000000000..70a96d5de --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeDriverAlertJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"8d31621ad4864a6489740ebb0d8f898b","collectionName":"OdeDriverAlertJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.bson new file mode 100644 index 000000000..b3150180f Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.metadata.json new file mode 100644 index 000000000..6887901fb --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeMapJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"b78e3ec27ba34f9e804c93c3a6608999","collectionName":"OdeMapJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedBSMJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedBSMJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedBSMJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedBSMJson.metadata.json new file mode 100644 index 000000000..e0b5ebe3c --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedBSMJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"BsmMessageContent.metadata.originRsu":{"$numberInt":"-1"},"BsmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"BsmMessageContent.metadata.originRsu_-1_BsmMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"BsmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"BsmMessageContent.metadata.utctimestamp_-1"}],"uuid":"e914264f17244a46b92aed6fadbcdade","collectionName":"OdeRawEncodedBSMJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedMAPJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedMAPJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedMAPJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedMAPJson.metadata.json new file mode 100644 index 000000000..55d585086 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedMAPJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"MapMessageContent.metadata.originRsu":{"$numberInt":"-1"},"MapMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"MapMessageContent.metadata.originRsu_-1_MapMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"MapMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"MapMessageContent.metadata.utctimestamp_-1"}],"uuid":"42381a3198124e7e8bee08430afc1a68","collectionName":"OdeRawEncodedMAPJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSPATJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSPATJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSPATJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSPATJson.metadata.json new file mode 100644 index 000000000..1cb503cdc --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSPATJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"SpatMessageContent.metadata.originRsu":{"$numberInt":"-1"},"SpatMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SpatMessageContent.metadata.originRsu_-1_SpatMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"SpatMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SpatMessageContent.metadata.utctimestamp_-1"}],"uuid":"87ff1ce53101416fbcc9cbbb075c39a4","collectionName":"OdeRawEncodedSPATJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSRMJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSRMJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSRMJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSRMJson.metadata.json new file mode 100644 index 000000000..913cab262 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSRMJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"SpatMessageContent.metadata.originRsu":{"$numberInt":"-1"},"SrmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SpatMessageContent.metadata.originRsu_-1_SrmMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"SrmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SrmMessageContent.metadata.utctimestamp_-1"}],"uuid":"ea932c2c9a0f4842b5c25df4d3cb7946","collectionName":"OdeRawEncodedSRMJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSSMJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSSMJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSSMJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSSMJson.metadata.json new file mode 100644 index 000000000..fddfb07a5 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedSSMJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"SpatMessageContent.metadata.originRsu":{"$numberInt":"-1"},"SsmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SpatMessageContent.metadata.originRsu_-1_SsmMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"SsmMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SsmMessageContent.metadata.utctimestamp_-1"}],"uuid":"e6b5362f810a49a5808855cc6d3d3074","collectionName":"OdeRawEncodedSSMJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedTIMJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedTIMJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedTIMJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedTIMJson.metadata.json new file mode 100644 index 000000000..28393cb47 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeRawEncodedTIMJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"SpatMessageContent.metadata.originRsu":{"$numberInt":"-1"},"TimMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"SpatMessageContent.metadata.originRsu_-1_TimMessageContent.metadata.utctimestamp_-1"},{"v":{"$numberInt":"2"},"key":{"TimMessageContent.metadata.utctimestamp":{"$numberInt":"-1"}},"name":"TimMessageContent.metadata.utctimestamp_-1"}],"uuid":"869e674cbc80493db9a4166761883718","collectionName":"OdeRawEncodedTIMJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.bson new file mode 100644 index 000000000..1c6a6c331 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.metadata.json new file mode 100644 index 000000000..469f125a4 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"ae2efabd0e8140c1903f94dea1668fdf","collectionName":"OdeSpatJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatRxJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatRxJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatRxJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatRxJson.metadata.json new file mode 100644 index 000000000..c6785351c --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSpatRxJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"57b7a96118b74039934e03d9d5712564","collectionName":"OdeSpatRxJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSrmJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSrmJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSrmJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSrmJson.metadata.json new file mode 100644 index 000000000..8ef07a0ba --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSrmJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"518b754346e447799fd080c6bd88b266","collectionName":"OdeSrmJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSsmJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSsmJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSsmJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSsmJson.metadata.json new file mode 100644 index 000000000..c6133a995 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeSsmJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"4471fb38e08148f392a0fe5693037071","collectionName":"OdeSsmJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTIMCertExpirationTimeJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTIMCertExpirationTimeJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTIMCertExpirationTimeJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTIMCertExpirationTimeJson.metadata.json new file mode 100644 index 000000000..dba33ee1e --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTIMCertExpirationTimeJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"4661a0d2a96549518df7bb43ef919e46","collectionName":"OdeTIMCertExpirationTimeJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimBroadcastJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimBroadcastJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimBroadcastJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimBroadcastJson.metadata.json new file mode 100644 index 000000000..92fa1fe02 --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimBroadcastJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"3d04dc77685245fc8f6d10734391a4f7","collectionName":"OdeTimBroadcastJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimJson.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimJson.bson new file mode 100644 index 000000000..e69de29bb diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimJson.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimJson.metadata.json new file mode 100644 index 000000000..5299ee36b --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/OdeTimJson.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"metadata.originIp":{"$numberInt":"-1"},"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.originIp_-1_metadata.odeReceivedAt_-1"},{"v":{"$numberInt":"2"},"key":{"metadata.odeReceivedAt":{"$numberInt":"-1"}},"name":"metadata.odeReceivedAt_-1"}],"uuid":"d098528dd8c54b81b752f7d483437743","collectionName":"OdeTimJson","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.bson new file mode 100644 index 000000000..4f55a4bb7 Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.metadata.json new file mode 100644 index 000000000..03db7a1ab --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedMap.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"properties.intersectionId":{"$numberInt":"-1"},"properties.timeStamp":{"$numberInt":"-1"}},"name":"properties.intersectionId_-1_properties.timeStamp_-1"},{"v":{"$numberInt":"2"},"key":{"properties.timeStamp":{"$numberInt":"-1"}},"name":"properties.timeStamp_-1"}],"uuid":"7a2c6de872414bea9d98b007ee88872f","collectionName":"ProcessedMap","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.bson b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.bson new file mode 100644 index 000000000..608ef66bd Binary files /dev/null and b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.bson differ diff --git a/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.metadata.json b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.metadata.json new file mode 100644 index 000000000..34bd63a8e --- /dev/null +++ b/conflictmonitor/mongo/dump_2024_08_20/ConflictMonitor/ProcessedSpat.metadata.json @@ -0,0 +1 @@ +{"indexes":[{"v":{"$numberInt":"2"},"key":{"_id":{"$numberInt":"1"}},"name":"_id_"},{"v":{"$numberInt":"2"},"key":{"recordGeneratedAt":{"$numberInt":"1"}},"name":"recordGeneratedAt_1","expireAfterSeconds":{"$numberInt":"5184000"}},{"v":{"$numberInt":"2"},"key":{"intersectionId":{"$numberInt":"-1"},"utcTimeStamp":{"$numberInt":"-1"}},"name":"intersectionId_-1_utcTimeStamp_-1"},{"v":{"$numberInt":"2"},"key":{"utcTimeStamp":{"$numberInt":"-1"}},"name":"utcTimeStamp_-1"}],"uuid":"7bfae48f39204c518542a13d3a59acd8","collectionName":"ProcessedSpat","type":"collection"} \ No newline at end of file diff --git a/conflictmonitor/mongo/keyfile.txt b/conflictmonitor/mongo/keyfile.txt new file mode 100644 index 000000000..a32a4347a --- /dev/null +++ b/conflictmonitor/mongo/keyfile.txt @@ -0,0 +1 @@ +1234567890 diff --git a/conflictmonitor/mongo/manage-volume-cron b/conflictmonitor/mongo/manage-volume-cron new file mode 100644 index 000000000..0833b0652 --- /dev/null +++ b/conflictmonitor/mongo/manage-volume-cron @@ -0,0 +1,13 @@ +CM_DATABASE_NAME=${CM_DATABASE_NAME} +CM_DATABASE_STORAGE_COLLECTION_NAME=${CM_DATABASE_STORAGE_COLLECTION_NAME} +CM_DATABASE_SIZE_GB=${CM_DATABASE_SIZE_GB} +CM_DATABASE_SIZE_TARGET_PERCENT=${CM_DATABASE_SIZE_TARGET_PERCENT} +CM_DATABASE_DELETE_THRESHOLD_PERCENT=${CM_DATABASE_DELETE_THRESHOLD_PERCENT} +CM_DATABASE_MAX_TTL_RETENTION_SECONDS=${CM_DATABASE_MAX_TTL_RETENTION_SECONDS} +CM_DATABASE_MIN_TTL_RETENTION_SECONDS=${CM_DATABASE_MIN_TTL_RETENTION_SECONDS} +CM_DATABASE_COMPACTION_TRIGGER_PERCENT=${CM_DATABASE_COMPACTION_TRIGGER_PERCENT} +MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} +MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + +0 * * * * root mongosh /docker-entrypoint-initdb.d/manage_volume.js > /var/log/cron.log 2>&1 +# An empty line is required at the end of this file for a valid cron file. \ No newline at end of file diff --git a/conflictmonitor/mongo/manage_volume.js b/conflictmonitor/mongo/manage_volume.js new file mode 100644 index 000000000..11026acfc --- /dev/null +++ b/conflictmonitor/mongo/manage_volume.js @@ -0,0 +1,238 @@ +// Mongo Data Management Script + +// Features +// Automatically Logs Collection Sizes +// Automatically Updates Data Retention Periods to prevent overflow +// Performs Emergency Record Deletion when Collections get to large + +// Database to Perform operation on. +const CM_DATABASE_NAME = process.env.CM_DATABASE_NAME || 'ConflictMonitor' + +// The name of the collection to store the data as. +const CM_DATABASE_STORAGE_COLLECTION_NAME = process.env.CM_DATABASE_STORAGE_COLLECTION_NAME || 'MongoStorage' + +// Total Size of the database disk in GB. This script will work to ensure all data fits within this store. +const CM_DATABASE_SIZE_GB = process.env.CM_DATABASE_SIZE_GB || 1000 + +// Specified as a percent of total database size. This is the storage target the database will try to hit. +const CM_DATABASE_SIZE_TARGET_PERCENT = process.env.CM_DATABASE_SIZE_TARGET_PERCENT || 0.8 + +// Specified as a percent of total database size. +const CM_DATABASE_DELETE_THRESHOLD_PERCENT = process.env.CM_DATABASE_DELETE_THRESHOLD_PERCENT || 0.9 + +// The maximum amount of time data should be retained. Measured in Seconds. +const CM_DATABASE_MAX_TTL_RETENTION_SECONDS = process.env.CM_DATABASE_MAX_TTL_RETENTION_SECONDS || 5184000 // 60 Days + +// The minimum amount of time data should be retained. Measured in Seconds. This only effects TTL's set on the data. It will not prevent the database from manual data deletion. +const CM_DATABASE_MIN_TTL_RETENTION_SECONDS = process.env.CM_DATABASE_MIN_TTL_RETENTION_SECONDS || 604800 // 7 Days + +// When the free space of a collection exceeds this percent of the collections total volume, automatic compaction should occur +const CM_DATABASE_COMPACTION_TRIGGER_PERCENT = process.env.CM_DATABASE_COMPACTION_TRIGGER_PERCENT || 0.5 + +const CM_MONGO_ROOT_USERNAME = process.env.MONGO_INITDB_ROOT_USERNAME || 'root' +const CM_MONGO_ROOT_PASSWORD = process.env.MONGO_INITDB_ROOT_PASSWORD || 'root' + +const MS_PER_HOUR = 60 * 60 * 1000 +const BYTE_TO_GB = 1024 * 1024 * 1024 +const DB_TARGET_SIZE_BYTES = CM_DATABASE_SIZE_GB * CM_DATABASE_SIZE_TARGET_PERCENT * BYTE_TO_GB +const DB_DELETE_SIZE_BYTES = CM_DATABASE_SIZE_GB * CM_DATABASE_DELETE_THRESHOLD_PERCENT * BYTE_TO_GB + +print('Managing Mongo Data Volumes') + +db = db.getSiblingDB('admin') +db.auth(CM_MONGO_ROOT_USERNAME, CM_MONGO_ROOT_PASSWORD) +db = db.getSiblingDB('ConflictMonitor') + +class CollectionStats { + constructor(name, allocatedSpace, freeSpace, indexSpace) { + this.name = name + this.allocatedSpace = allocatedSpace + this.freeSpace = freeSpace + this.indexSize = indexSpace + } +} + +class StorageRecord { + constructor(collectionStats, totalAllocatedStorage, totalFreeSpace, totalIndexSize) { + this.collectionStats = collectionStats + this.recordGeneratedAt = ISODate() + this.totalAllocatedStorage = totalAllocatedStorage + this.totalFreeSpace = totalFreeSpace + this.totalIndexSize = totalIndexSize + this.totalSize = totalAllocatedStorage + totalFreeSpace + totalIndexSize + } +} + +function ema_deltas(records) { + const a = 0.5 + let average_delta = 0 + + for (let i = 0; i < records.length - 1; i++) { + const delta = records[i + 1] - records[i] + average_delta += Math.pow(a, records.length - i - 1) * delta + } + + return average_delta +} + +function updateTTL() { + print('Updating TTL') + const ttl = getLatestTTL() + if (ttl == 0) { + print('Skipping TTL Update') + // Do not update TTL's + return + } + + const newestRecords = db + .getCollection(CM_DATABASE_STORAGE_COLLECTION_NAME) + .find() + .sort({ recordGeneratedAt: -1 }) + .limit(10) + + let sizes = [] + newestRecords.forEach((doc) => { + let total = 0 + for (let i = 0; i < doc.collectionStats.length; i++) { + total += + doc.collectionStats[i].allocatedSpace + doc.collectionStats[i].freeSpace + doc.collectionStats[i].indexSize + } + + sizes.push(total) + }) + + // Overshoot Prevention + const growth = ema_deltas(sizes) + const oldestSpat = db.getCollection('ProcessedSpat').find().sort({ recordGeneratedAt: 1 }).limit(1) + + let new_ttl = ttl + let possible_ttl = ttl + + // Check if collection is still growing to capacity, or if it in steady state + if (oldestSpat.recordGeneratedAt > ISODate() - ttl + MS_PER_HOUR && growth > 0) { + possible_ttl = DB_TARGET_SIZE_BYTES / growth + } else { + possible_ttl = 3600 * ((DB_TARGET_SIZE_BYTES - sizes[0]) / BYTE_TO_GB) + ttl // Shift the TTL by roughly 1 hour for every GB of data over or under + } + + // Clamp TTL and assign to new TTL; + + if (!isNaN(possible_ttl) && possible_ttl != 0) { + if (possible_ttl > CM_DATABASE_MAX_TTL_RETENTION_SECONDS) { + new_ttl = CM_DATABASE_MAX_TTL_RETENTION_SECONDS + } else if (possible_ttl < CM_DATABASE_MIN_TTL_RETENTION_SECONDS) { + new_ttl = CM_DATABASE_MIN_TTL_RETENTION_SECONDS + } else { + new_ttl = Math.round(possible_ttl) + } + new_ttl = Number(new_ttl) + print('Calculated New TTL for MongoDB: ' + new_ttl) + applyNewTTL(new_ttl) + } else { + print('Not Updating TTL New TTL is NaN') + } +} + +function getLatestTTL() { + const indexes = db.getCollection('ProcessedSpat').getIndexes() + for (let i = 0; i < indexes.length; i++) { + if (indexes[i].hasOwnProperty('expireAfterSeconds')) { + return indexes[i]['expireAfterSeconds'] + } + } + return 0 +} + +function getTTLKey(collection) { + const indexes = db.getCollection(collection).getIndexes() + for (let i = 0; i < indexes.length; i++) { + if (indexes[i].hasOwnProperty('expireAfterSeconds')) { + return [indexes[i]['key'], indexes[i]['expireAfterSeconds']] + } + } + return [null, null] +} + +function applyNewTTL(ttl) { + var collections = db.getCollectionNames() + for (let i = 0; i < collections.length; i++) { + const collection = collections[i] + let [key, oldTTL] = getTTLKey(collection) + if (oldTTL != ttl && key != null) { + print('Updating TTL For Collection: ' + collection, ttl) + db.runCommand({ + collMod: collection, + index: { + keyPattern: key, + expireAfterSeconds: ttl, + }, + }) + } + } +} + +function addNewStorageRecord() { + var collections = db.getCollectionNames() + let totalAllocatedStorage = 0 + let totalFreeSpace = 0 + let totalIndexSize = 0 + + let records = [] + + for (var i = 0; i < collections.length; i++) { + let stats = db.getCollection(collections[i]).stats() + let colStats = db.runCommand({ collstats: collections[i] }) + let blockManager = colStats['wiredTiger']['block-manager'] + + let freeSpace = Number(blockManager['file bytes available for reuse']) + let allocatedStorage = Number(blockManager['file size in bytes']) + let indexSize = Number(stats.totalIndexSize) + + records.push(new CollectionStats(collections[i], allocatedStorage, freeSpace, indexSize)) + + totalAllocatedStorage += allocatedStorage + totalFreeSpace += freeSpace + totalIndexSize += indexSize + + print(collections[i], allocatedStorage / BYTE_TO_GB, freeSpace / BYTE_TO_GB, indexSize / BYTE_TO_GB) + } + + const storageRecord = new StorageRecord(records, totalAllocatedStorage, totalFreeSpace, totalIndexSize) + db.getCollection(CM_DATABASE_STORAGE_COLLECTION_NAME).insertOne(storageRecord) +} + +function compactCollections() { + print('Checking Collection Compaction') + + var collections = db.getCollectionNames() + + let activeCompactions = [] + db.currentOp({ active: true, secs_running: { $gt: 0 } }).inprog.forEach((op) => { + if (op.msg && op.msg.includes('compact')) { + print('Found Active Compactions') + activeCompactions.push(op.command.compact) + } + }) + + for (var i = 0; i < collections.length; i++) { + let colStats = db.runCommand({ collstats: collections[i] }) + let blockManager = colStats['wiredTiger']['block-manager'] + + let freeSpace = Number(blockManager['file bytes available for reuse']) + let allocatedStorage = Number(blockManager['file size in bytes']) + + // If free space makes up a significant proportion of allocated storage + if (freeSpace > allocatedStorage * CM_DATABASE_COMPACTION_TRIGGER_PERCENT && allocatedStorage > 1 * BYTE_TO_GB) { + if (!activeCompactions.includes(collections[i])) { + print('Compacting Collection', collections[i]) + db.runCommand({ compact: collections[i], force: true }) + } else { + print('Skipping Compaction, Collection Compaction is already scheduled') + } + } + } +} + +addNewStorageRecord() +updateTTL() +compactCollections() diff --git a/conflictmonitor/mongo/mongorestore.sh b/conflictmonitor/mongo/mongorestore.sh new file mode 100644 index 000000000..7ee6dc230 --- /dev/null +++ b/conflictmonitor/mongo/mongorestore.sh @@ -0,0 +1 @@ +mongorestore /dump --username ${MONGO_INITDB_ROOT_USERNAME} --password ${MONGO_INITDB_ROOT_PASSWORD} \ No newline at end of file diff --git a/docker-compose-addons.yml b/docker-compose-addons.yml index 0cd834c1f..b0092d77a 100644 --- a/docker-compose-addons.yml +++ b/docker-compose-addons.yml @@ -37,11 +37,9 @@ services: SMTP_USERNAME: ${SMTP_USERNAME} SMTP_PASSWORD: ${SMTP_PASSWORD} SMTP_EMAIL: ${SMTP_EMAIL} - SMTP_EMAIL_RECIPIENTS: ${SMTP_EMAIL_RECIPIENTS} MESSAGE_TYPES: ${COUNT_MESSAGE_TYPES} PROJECT_ID: ${GCP_PROJECT_ID} - ODE_KAFKA_BROKERS: ${ODE_KAFKA_BROKERS} PG_DB_HOST: ${PG_DB_HOST} PG_DB_NAME: ${PG_DB_NAME} @@ -100,11 +98,14 @@ services: depends_on: - cvmanager_postgres environment: + STORAGE_TYPE: ${STORAGE_TYPE} + ISS_API_KEY: ${ISS_API_KEY} ISS_API_KEY_NAME: ${ISS_API_KEY_NAME} ISS_PROJECT_ID: ${ISS_PROJECT_ID} ISS_SCMS_TOKEN_REST_ENDPOINT: ${ISS_SCMS_TOKEN_REST_ENDPOINT} ISS_SCMS_VEHICLE_REST_ENDPOINT: ${ISS_SCMS_VEHICLE_REST_ENDPOINT} + ISS_KEY_TABLE_NAME: ${ISS_KEY_TABLE_NAME} PG_DB_HOST: ${PG_DB_HOST} PG_DB_NAME: ${PG_DB_NAME} @@ -140,10 +141,9 @@ services: BLOB_STORAGE_PROVIDER: ${BLOB_STORAGE_PROVIDER} BLOB_STORAGE_BUCKET: ${BLOB_STORAGE_BUCKET} - GCP_PROJECT: ${GCP_PROJECT} + GCP_PROJECT: ${GCP_PROJECT_ID} GOOGLE_APPLICATION_CREDENTIALS: '/google/gcp_credentials.json' - FW_EMAIL_RECIPIENTS: ${FW_EMAIL_RECIPIENTS} SMTP_SERVER_IP: ${SMTP_SERVER_IP} SMTP_EMAIL: ${SMTP_EMAIL} SMTP_USERNAME: ${SMTP_USERNAME} @@ -155,4 +155,4 @@ services: logging: options: max-size: '10m' - max-file: '5' \ No newline at end of file + max-file: '5' diff --git a/docker-compose-full-cm.yml b/docker-compose-full-cm.yml new file mode 100644 index 000000000..7a0a68bd3 --- /dev/null +++ b/docker-compose-full-cm.yml @@ -0,0 +1,449 @@ +version: '3' +services: + cvmanager_api: + build: + context: services + dockerfile: Dockerfile.api + image: jpo_cvmanager_api:latest + restart: always + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '8081:5000' + environment: + PG_DB_HOST: ${PG_DB_HOST} + PG_DB_USER: ${PG_DB_USER} + PG_DB_PASS: ${PG_DB_PASS} + PG_DB_NAME: postgres + INSTANCE_CONNECTION_NAME: ${INSTANCE_CONNECTION_NAME} + + MONGO_DB_URI: ${MONGO_DB_URI} + MONGO_DB_NAME: ${MONGO_DB_NAME} + + COUNTS_MSG_TYPES: ${COUNTS_MSG_TYPES} + + GEO_DB_NAME: ${GEO_DB_NAME} + SSM_DB_NAME: ${SSM_DB_NAME} + SRM_DB_NAME: ${SRM_DB_NAME} + + MAX_GEO_QUERY_RECORDS: ${MAX_GEO_QUERY_RECORDS} + + FIRMWARE_MANAGER_ENDPOINT: ${FIRMWARE_MANAGER_ENDPOINT} + + WZDX_API_KEY: ${WZDX_API_KEY} + WZDX_ENDPOINT: ${WZDX_ENDPOINT} + + CORS_DOMAIN: ${CORS_DOMAIN} + KEYCLOAK_ENDPOINT: http://${KEYCLOAK_DOMAIN}:8084/ + KEYCLOAK_REALM: ${KEYCLOAK_REALM} + KEYCLOAK_API_CLIENT_ID: ${KEYCLOAK_API_CLIENT_ID} + KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} + + CSM_EMAIL_TO_SEND_FROM: ${CSM_EMAIL_TO_SEND_FROM} + CSM_EMAIL_APP_USERNAME: ${CSM_EMAIL_APP_USERNAME} + CSM_EMAIL_APP_PASSWORD: ${CSM_EMAIL_APP_PASSWORD} + CSM_EMAILS_TO_SEND_TO: ${CSM_EMAILS_TO_SEND_TO} + CSM_TARGET_SMTP_SERVER_ADDRESS: ${CSM_TARGET_SMTP_SERVER_ADDRESS} + CSM_TARGET_SMTP_SERVER_PORT: ${CSM_TARGET_SMTP_SERVER_PORT} + + TIMEZONE: ${TIMEZONE} + LOGGING_LEVEL: ${API_LOGGING_LEVEL} + logging: + options: + max-size: '10m' + max-file: '5' + + cvmanager_webapp: + build: + context: webapp + dockerfile: Dockerfile + args: + API_URI: http://${WEBAPP_DOMAIN}:8081 + MAPBOX_TOKEN: ${MAPBOX_TOKEN} + KEYCLOAK_HOST_URL: http://${KEYCLOAK_DOMAIN}:8084/ + COUNT_MESSAGE_TYPES: ${COUNTS_MSG_TYPES} + VIEWER_MESSAGE_TYPES: ${VIEWER_MSG_TYPES} + DOT_NAME: ${DOT_NAME} + MAPBOX_INIT_LATITUDE: ${MAPBOX_INIT_LATITUDE} + MAPBOX_INIT_LONGITUDE: ${MAPBOX_INIT_LONGITUDE} + MAPBOX_INIT_ZOOM: ${MAPBOX_INIT_ZOOM} + CVIZ_API_SERVER_URL: ${CVIZ_API_SERVER_URL} + CVIZ_API_WS_URL: ${CVIZ_API_WS_URL} + image: jpo_cvmanager_webapp:latest + restart: always + depends_on: + cvmanager_keycloak: + condition: service_healthy + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '80:80' + logging: + options: + max-size: '10m' + + cvmanager_postgres: + image: postgis/postgis:15-master + restart: always + ports: + - '5432:5432' + environment: + POSTGRES_USER: ${PG_DB_USER} + POSTGRES_PASSWORD: ${PG_DB_PASS} + volumes: + - pgdb:/var/lib/postgresql/data + - ./resources/sql_scripts:/docker-entrypoint-initdb.d + logging: + options: + max-size: '10m' + + cvmanager_keycloak: + build: + context: ./resources/keycloak + dockerfile: Dockerfile + args: + KEYCLOAK_LOGIN_THEME_NAME: ${KEYCLOAK_LOGIN_THEME_NAME}.jar + image: jpo_cvmanager_keycloak:latest + restart: always + depends_on: + - cvmanager_postgres + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '8084:8080' + environment: + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + WEBAPP_ORIGIN: http://${WEBAPP_DOMAIN} + WEBAPP_CM_ORIGIN: http://${WEBAPP_CM_DOMAIN} + KC_HEALTH_ENABLED: true + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://${PG_DB_HOST}/postgres?currentSchema=keycloak + KC_DB_USERNAME: ${PG_DB_USER} + KC_DB_PASSWORD: ${PG_DB_PASS} + KC_HOSTNAME: ${KEYCLOAK_DOMAIN} + KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} + KEYCLOAK_CM_API_CLIENT_SECRET_KEY: ${KEYCLOAK_CM_API_CLIENT_SECRET_KEY} + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} + command: + - start-dev + - --log-level=${KC_LOGGING_LEVEL} + - --import-realm + - --spi-theme-welcome-theme=custom-welcome + logging: + options: + max-size: '10m' + + kafka: + image: bitnami/kafka:latest + hostname: kafka + ports: + - '9092:9092' + volumes: + - kafka:/bitnami + environment: + KAFKA_ENABLE_KRAFT: 'yes' + KAFKA_CFG_PROCESS_ROLES: 'broker,controller' + KAFKA_CFG_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_CFG_LISTENERS: 'PLAINTEXT://:9094,CONTROLLER://:9093,EXTERNAL://:9092' + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT' + KAFKA_CFG_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:9094,EXTERNAL://${DOCKER_HOST_IP}:9092' + KAFKA_BROKER_ID: '1' + KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: '1@kafka:9093' + ALLOW_PLAINTEXT_LISTENER: 'yes' + KAFKA_CFG_NODE_ID: '1' + KAFKA_CFG_DELETE_TOPIC_ENABLE: 'true' + KAFKA_CFG_LOG_RETENTION_HOURS: 2 + logging: + options: + max-size: '10m' + max-file: '5' + + kafka_init: + image: bitnami/kafka:latest + depends_on: + kafka: + condition: service_started + volumes: + - ./conflictmonitor/kafka/kafka_init.sh:/kafka_init.sh + entrypoint: ['/bin/sh', 'kafka_init.sh'] + + ode: + image: usdotjpoode/jpo-ode:develop + ports: + - '8080:8080' + - '9090:9090' + - '46753:46753/udp' + - '46800:46800/udp' + - '47900:47900/udp' + - '44900:44900/udp' + - '44910:44910/udp' + - '44920:44920/udp' + - '44930:44930/udp' + - '44940:44940/udp' + - '5555:5555/udp' + - '6666:6666/udp' + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + ZK: ${DOCKER_HOST_IP}:2181 + depends_on: + - kafka + volumes: + - ode_vol:/jpo-ode + - ode_vol:/home/uploads + logging: + options: + max-size: '10m' + max-file: '5' + + geojsonconverter: + image: usdotjpoode/geojsonconverter:develop + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + geometry.output.mode: ${GEOMETRY_OUTPUT_MODE} + spring.kafka.bootstrap-servers: ${DOCKER_HOST_IP}:9092 + logging: + options: + max-size: '10m' + max-file: '5' + depends_on: + - ode + + conflictmonitor: + image: usdotjpoode/jpo-conflictmonitor:develop + restart: always + ports: + - '8082:8082' + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + KAFKA_BROKER_IP: ${KAFKA_BROKER_IP} + DB_HOST_IP: ${DB_HOST_IP} + spring.kafka.bootstrap-servers: ${KAFKA_BROKER_IP}:9092 + healthcheck: + test: ['CMD', 'java', '-version'] + interval: 10s + timeout: 10s + retries: 20 + logging: + options: + max-size: '10m' + max-file: '5' + deploy: + resources: + limits: + memory: 3G + depends_on: + - ode + - geojsonconverter + + conflictvisualizer_api: + image: us-central1-docker.pkg.dev/cdot-oim-cv-dev/jpo-conflictvisualizer-api-cvmanager/jpo-conflictvisualizer-api-cvmanager:2024-q2 + ports: + - '8089:8081' + restart: always + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + environment: + AUTH_SERVER_URL: http://${KEYCLOAK_DOMAIN}:8084 + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + DB_HOST_IP: ${DB_HOST_IP} + DB_HOST_PORT: ${DB_HOST_PORT} + SPRING_KAFKA_BOOTSTRAPSERVERS: ${KAFKA_BROKER_IP}:${KAFKA_BROKER_PORT} + CM_SERVER_URL: 'NONE' + load: 'false' + KAFKA_TYPE: 'ON-PREM' + ACM_CONFIG_FILE: adm.properties + ACM_LOG_TO_CONSOLE: true + ACM_LOG_TO_FILE: false + ACM_LOG_LEVEL: DEBUG + CM_MONGO_API_USERNAME: ${CM_MONGO_API_USERNAME} + CM_MONGO_API_PASSWORD: ${CM_MONGO_API_PASSWORD} + entrypoint: + - sh + - -c + - | + sleep 60 + java -Djava.rmi.server.hostname=$DOCKER_HOST_IP -Dcom.sun.management.jmxremote.port=9090 -Dcom.sun.management.jmxremote.rmi.port=9090 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=true -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dlogback.configurationFile=/home/logback.xml -jar /home/jpo-conflictvisualizer-api.jar + logging: + options: + max-size: '10m' + max-file: '5' + depends_on: + cvmanager_keycloak: + condition: service_healthy + mongodb_container: + condition: service_healthy + kafka_init: + condition: service_started + + deduplicator: + image: us-central1-docker.pkg.dev/cdot-oim-cv-dev/jpo-conflictvisualizer-api-cvmanager/jpo-deduplicator:v1.0.0 + restart: always + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + KAFKA_BROKER_IP: ${KAFKA_BROKER_IP} + spring.kafka.bootstrap-servers: ${KAFKA_BROKER_IP}:9092 + healthcheck: + test: ['CMD', 'java', '-version'] + interval: 10s + timeout: 10s + retries: 20 + logging: + options: + max-size: '10m' + max-file: '5' + deploy: + resources: + limits: + memory: 3G + depends_on: + - conflictmonitor + + mongodb_container: + image: mongo:6 + container_name: jpo-conflictmonitor-mongodb-container + restart: always + environment: + - MONGO_REPLICA_SET_NAME=rs0 + - DB_HOST_IP=${DB_HOST_IP} + - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + - CM_MONGO_CONNECTOR_USERNAME=${CM_MONGO_CONNECTOR_USERNAME} + - CM_MONGO_CONNECTOR_PASSWORD=${CM_MONGO_CONNECTOR_PASSWORD} + - CM_MONGO_API_USERNAME=${CM_MONGO_API_USERNAME} + - CM_MONGO_API_PASSWORD=${CM_MONGO_API_PASSWORD} + - CM_MONGO_USER_USERNAME=${CM_MONGO_USER_USERNAME} + - CM_MONGO_USER_PASSWORD=${CM_MONGO_USER_PASSWORD} + - CM_DATABASE_NAME=${CM_DATABASE_NAME} + - CM_DATABASE_STORAGE_COLLECTION_NAME=${CM_DATABASE_STORAGE_COLLECTION_NAME} + - CM_DATABASE_SIZE_GB=${CM_DATABASE_SIZE_GB} + - CM_DATABASE_SIZE_TARGET_PERCENT=${CM_DATABASE_SIZE_TARGET_PERCENT} + - CM_DATABASE_DELETE_THRESHOLD_PERCENT=${CM_DATABASE_DELETE_THRESHOLD_PERCENT} + - CM_DATABASE_MAX_TTL_RETENTION_SECONDS=${CM_DATABASE_MAX_TTL_RETENTION_SECONDS} + - CM_DATABASE_MIN_TTL_RETENTION_SECONDS=${CM_DATABASE_MIN_TTL_RETENTION_SECONDS} + - CM_DATABASE_COMPACTION_TRIGGER_PERCENT=${CM_DATABASE_COMPACTION_TRIGGER_PERCENT} + ports: + - '27017:27017' + volumes: + - mongodb_data_container:/data/db + - ./conflictmonitor/mongo/manage-volume-cron:/docker-entrypoint-initdb.d/manage-volume-cron + - ./conflictmonitor/mongo/keyfile.txt:/data/keyfile-import.txt + - ./conflictmonitor/mongo/a_init_replicas.js:/docker-entrypoint-initdb.d/a_init_replicas.js + - ./conflictmonitor/mongo/b_create_indexes.js:/docker-entrypoint-initdb.d/b_create_indexes.js + - ./conflictmonitor/mongo/manage_volume.js:/docker-entrypoint-initdb.d/manage_volume.js + - ./conflictmonitor/mongo/dump_2024_08_20:/dump + healthcheck: + test: | + test $$(mongosh --username ${MONGO_INITDB_ROOT_USERNAME} --password ${MONGO_INITDB_ROOT_PASSWORD} --quiet --eval "try { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: '${DB_HOST_IP}' }] }).ok } catch (_) { rs.status().ok }") -eq 1 + interval: 10s + start_period: 60s + entrypoint: + - bash + - -c + - | + apt update + apt install -y cron gettext systemctl dos2unix + systemctl start cron + systemctl enable cron + envsubst < /docker-entrypoint-initdb.d/manage-volume-cron > /etc/cron.d/manage-volume-cron + dos2unix /etc/cron.d/manage-volume-cron + chmod 644 /etc/cron.d/manage-volume-cron + systemctl restart cron + cp /data/keyfile-import.txt /data/keyfile.txt + chmod 400 /data/keyfile.txt + chown 999:999 /data/keyfile.txt + + exec docker-entrypoint.sh $$@ & + + sleep 30 + mongorestore /dump --username ${MONGO_INITDB_ROOT_USERNAME} --password ${MONGO_INITDB_ROOT_PASSWORD} + wait + + command: ['mongod', '--replSet', 'rs0', '--bind_ip_all', '--keyFile', '/data/keyfile.txt'] + logging: + options: + max-size: '10m' + max-file: '5' + deploy: + resources: + limits: + memory: 3G + + connect: + image: cp-kafka-connect:6.1.9 + build: + context: ./conflictmonitor/docker/connect + dockerfile: Dockerfile + container_name: jpo-conflictmonitor-kafka-connect + restart: always + ports: + - '8083:8083' + depends_on: + mongodb_container: + condition: service_healthy + kafka: + condition: service_started + conflictmonitor: + condition: service_started + environment: + DOCKER_HOST_IP: ${DOCKER_HOST_IP} + DB_HOST_IP: ${DB_HOST_IP} + CONNECT_BOOTSTRAP_SERVERS: ${KAFKA_BROKER_IP}:9092 + CONNECT_REST_ADVERTISED_HOST_NAME: connect + CONNECT_REST_PORT: 8083 + CONNECT_GROUP_ID: compose-connect-group + CONNECT_CONFIG_STORAGE_TOPIC: CmConnectConfigs + CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_CONFIG_STORAGE_CLEANUP_POLICY: compact + CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000 + CONNECT_OFFSET_STORAGE_TOPIC: CmConnectOffsets + CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_OFFSET_STORAGE_CLEANUP_POLICY: compact + CONNECT_STATUS_STORAGE_TOPIC: CmConnectStatus + CONNECT_STATUS_STORAGE_CLEANUP_POLICY: compact + CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_KEY_CONVERTER: 'org.apache.kafka.connect.json.JsonConverter' + CONNECT_VALUE_CONVERTER: 'org.apache.kafka.connect.json.JsonConverter' + CONNECT_INTERNAL_KEY_CONVERTER: 'org.apache.kafka.connect.json.JsonConverter' + CONNECT_INTERNAL_VALUE_CONVERTER: 'org.apache.kafka.connect.json.JsonConverter' + CONNECT_LOG4J_ROOT_LOGLEVEL: 'ERROR' + CONNECT_LOG4J_LOGGERS: 'org.apache.kafka.connect.runtime.rest=ERROR,org.reflections=ERROR,com.mongodb.kafka=ERROR' + CONNECT_PLUGIN_PATH: /usr/share/confluent-hub-components + CONNECT_ZOOKEEPER_CONNECT: 'zookeeper:2181' + CM_MONGO_CONNECTOR_USERNAME: ${CM_MONGO_CONNECTOR_USERNAME} + CM_MONGO_CONNECTOR_PASSWORD: ${CM_MONGO_CONNECTOR_PASSWORD} + logging: + options: + max-size: '10m' + max-file: '5' + command: + - bash + - -c + - | + /etc/confluent/docker/run & + echo "Waiting for Kafka Connect to start listening on kafka-connect ❳" + while [ $$(curl -s -o /dev/null -w %{http_code} http://${KAFKA_CONNECT_IP}:8083/connectors) -eq 000 ] ; do + echo -e $$(date) " Kafka Connect listener HTTP state: " $$(curl -s -o /dev/null -w %{http_code} http://${KAFKA_CONNECT_IP}:8083/connectors) " (waiting for 200)" + sleep 5 + done + sleep 10 + echo -e "\n--\n+> Creating Kafka Connect MongoDB sink" + bash /scripts/connect_start.sh "mongodb://${CM_MONGO_CONNECTOR_USERNAME}:${CM_MONGO_CONNECTOR_USERNAME}@${DOCKER_HOST_IP}:27017/?authMechanism=DEFAULT&authSource=ConflictMonitor&replicaSet=rs0" + sleep infinity + deploy: + resources: + limits: + memory: 3G + +volumes: + mongodb_data_container: + pgdb: + driver: local + ode_vol: + kafka: {} diff --git a/docker-compose-mongo.yml b/docker-compose-mongo.yml new file mode 100644 index 000000000..1292dfa26 --- /dev/null +++ b/docker-compose-mongo.yml @@ -0,0 +1,55 @@ +version: '3' + +include: + - docker-compose.yml + +services: + mongo: + image: mongo:7 + container_name: mongo + restart: always + ports: + - '27017:27017' + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ADMIN_DB_USER} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_DB_PASS} + MONGO_INITDB_DATABASE: admin + entrypoint: + - bash + - -c + - | + openssl rand -base64 741 > /mongo_keyfile + chmod 400 /mongo_keyfile + chown 999:999 /mongo_keyfile + exec docker-entrypoint.sh $$@ + command: 'mongod --bind_ip_all --replSet rs0 --keyFile /mongo_keyfile' + volumes: + - mongo_data:/data/db + healthcheck: + test: | + echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + start_period: 30s + + mongo-setup: + image: mongo:7 + container_name: mongo_setup + depends_on: + - mongo + restart: on-failure + environment: + MONGO_ADMIN_DB_USER: ${MONGO_ADMIN_DB_USER} + MONGO_ADMIN_DB_PASS: ${MONGO_ADMIN_DB_PASS} + MONGO_DB_NAME: ${MONGO_DB_NAME} + MONGO_CV_MANAGER_DB_USER: ${MONGO_CV_MANAGER_DB_USER} + MONGO_CV_MANAGER_DB_PASS: ${MONGO_CV_MANAGER_DB_PASS} + MONGO_COLLECTION_TTL: ${MONGO_COLLECTION_TTL} + INSERT_SAMPLE_DATA: ${INSERT_SAMPLE_DATA} + entrypoint: ['/bin/bash', 'setup_mongo.sh'] + volumes: + - ./resources/mongo_scripts/setup_mongo.sh:/setup_mongo.sh + - ./resources/mongo_scripts/create_indexes.js:/create_indexes.js + - ./resources/mongo_scripts/insert_data.js:/insert_data.js + +volumes: + mongo_data: diff --git a/docker-compose-no-cm.yml b/docker-compose-no-cm.yml new file mode 100644 index 000000000..16d66d896 --- /dev/null +++ b/docker-compose-no-cm.yml @@ -0,0 +1,143 @@ +version: '3.9' +services: + cvmanager_api: + build: + context: services + dockerfile: Dockerfile.api + image: jpo_cvmanager_api:latest + restart: always + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '8081:5000' + environment: + PG_DB_HOST: ${PG_DB_HOST} + PG_DB_USER: ${PG_DB_USER} + PG_DB_PASS: ${PG_DB_PASS} + PG_DB_NAME: postgres + INSTANCE_CONNECTION_NAME: ${INSTANCE_CONNECTION_NAME} + + MONGO_DB_URI: ${MONGO_DB_URI} + MONGO_DB_NAME: ${MONGO_DB_NAME} + + COUNTS_MSG_TYPES: ${COUNTS_MSG_TYPES} + + GEO_DB_NAME: ${GEO_DB_NAME} + SSM_DB_NAME: ${SSM_DB_NAME} + SRM_DB_NAME: ${SRM_DB_NAME} + + MAX_GEO_QUERY_RECORDS: ${MAX_GEO_QUERY_RECORDS} + + FIRMWARE_MANAGER_ENDPOINT: ${FIRMWARE_MANAGER_ENDPOINT} + + WZDX_API_KEY: ${WZDX_API_KEY} + WZDX_ENDPOINT: ${WZDX_ENDPOINT} + + CORS_DOMAIN: ${CORS_DOMAIN} + KEYCLOAK_ENDPOINT: http://${KEYCLOAK_DOMAIN}:8084/ + KEYCLOAK_REALM: ${KEYCLOAK_REALM} + KEYCLOAK_API_CLIENT_ID: ${KEYCLOAK_API_CLIENT_ID} + KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} + + CSM_EMAIL_TO_SEND_FROM: ${CSM_EMAIL_TO_SEND_FROM} + CSM_EMAIL_APP_USERNAME: ${CSM_EMAIL_APP_USERNAME} + CSM_EMAIL_APP_PASSWORD: ${CSM_EMAIL_APP_PASSWORD} + CSM_EMAILS_TO_SEND_TO: ${CSM_EMAILS_TO_SEND_TO} + CSM_TARGET_SMTP_SERVER_ADDRESS: ${CSM_TARGET_SMTP_SERVER_ADDRESS} + CSM_TARGET_SMTP_SERVER_PORT: ${CSM_TARGET_SMTP_SERVER_PORT} + + TIMEZONE: ${TIMEZONE} + LOGGING_LEVEL: ${API_LOGGING_LEVEL} + logging: + options: + max-size: '10m' + max-file: '5' + + cvmanager_webapp: + build: + context: webapp + dockerfile: Dockerfile + args: + API_URI: http://${WEBAPP_DOMAIN}:8081 + MAPBOX_TOKEN: ${MAPBOX_TOKEN} + KEYCLOAK_HOST_URL: http://${KEYCLOAK_DOMAIN}:8084/ + COUNT_MESSAGE_TYPES: ${COUNTS_MSG_TYPES} + VIEWER_MESSAGE_TYPES: ${VIEWER_MSG_TYPES} + DOT_NAME: ${DOT_NAME} + MAPBOX_INIT_LATITUDE: ${MAPBOX_INIT_LATITUDE} + MAPBOX_INIT_LONGITUDE: ${MAPBOX_INIT_LONGITUDE} + MAPBOX_INIT_ZOOM: ${MAPBOX_INIT_ZOOM} + CVIZ_API_SERVER_URL: ${CVIZ_API_SERVER_URL} + CVIZ_API_WS_URL: ${CVIZ_API_WS_URL} + image: jpo_cvmanager_webapp:latest + restart: always + depends_on: + cvmanager_keycloak: + condition: service_healthy + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '80:80' + logging: + options: + max-size: '10m' + + cvmanager_postgres: + image: postgis/postgis:15-master + restart: always + ports: + - '5432:5432' + environment: + POSTGRES_USER: ${PG_DB_USER} + POSTGRES_PASSWORD: ${PG_DB_PASS} + volumes: + - pgdb:/var/lib/postgresql/data + - ./resources/sql_scripts:/docker-entrypoint-initdb.d + logging: + options: + max-size: '10m' + + cvmanager_keycloak: + build: + context: ./resources/keycloak + dockerfile: Dockerfile + args: + KEYCLOAK_LOGIN_THEME_NAME: ${KEYCLOAK_LOGIN_THEME_NAME}.jar + image: jpo_cvmanager_keycloak:latest + restart: always + depends_on: + - cvmanager_postgres + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + ports: + - '8084:8080' + environment: + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + WEBAPP_ORIGIN: http://${WEBAPP_DOMAIN} + WEBAPP_CM_ORIGIN: http://${WEBAPP_CM_DOMAIN} + KC_HEALTH_ENABLED: true + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://${PG_DB_HOST}/postgres?currentSchema=keycloak + KC_DB_USERNAME: ${PG_DB_USER} + KC_DB_PASSWORD: ${PG_DB_PASS} + KC_HOSTNAME: ${KEYCLOAK_DOMAIN} + KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} + KEYCLOAK_CM_API_CLIENT_SECRET_KEY: ${KEYCLOAK_CM_API_CLIENT_SECRET_KEY} + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} + command: + - start-dev + - --log-level=${KC_LOGGING_LEVEL} + - --import-realm + - --spi-theme-welcome-theme=custom-welcome + logging: + options: + max-size: '10m' + +volumes: + pgdb: + driver: local diff --git a/docker-compose-obu-ota-server.yml b/docker-compose-obu-ota-server.yml new file mode 100644 index 000000000..88854d9aa --- /dev/null +++ b/docker-compose-obu-ota-server.yml @@ -0,0 +1,56 @@ +version: '3' +services: + # OBU OTA Server and Nginx proxy services + jpo_ota_backend: + build: + context: ./services + dockerfile: Dockerfile.obu_ota_server + image: obu_ota_server:latest + restart: unless-stopped + ports: + - 8085:8085 + environment: + SERVER_HOST: ${OBU_OTA_SERVER_HOST} + LOGGING_LEVEL: ${OBU_OTA_SERVER_LOGGING_LEVEL} + BLOB_STORAGE_PROVIDER: ${BLOB_STORAGE_PROVIDER} + BLOB_STORAGE_BUCKET: ${OBU_OTA_BLOB_STORAGE_BUCKET} + BLOB_STORAGE_PATH: ${OBU_OTA_BLOB_STORAGE_PATH} + + OTA_USERNAME: ${OTA_USERNAME} + OTA_PASSWORD: ${OTA_PASSWORD} + + PG_DB_HOST: ${PG_DB_HOST} + PG_DB_NAME: ${PG_DB_NAME} + PG_DB_USER: ${PG_DB_USER} + PG_DB_PASS: ${PG_DB_PASS} + + MAX_COUNT: ${MAX_COUNT} + volumes: + - ./resources/ota/firmwares:/firmwares + logging: + options: + max-size: '10m' + max-file: '5' + + jpo_ota_nginx: + build: + context: resources/ota/nginx + image: obu_ota_nginx:latest + restart: unless-stopped + ports: + - 80:80 + - 443:443 + environment: + NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx + SERVER_HOST: ${OBU_OTA_SERVER_HOST} + volumes: + - ./resources/ota/nginx/nginx-${NGINX_ENCRYPTION}.conf:/etc/nginx/templates/nginx.conf.template + - ./resources/ota/nginx/gen_dhparam.sh:/docker-entrypoint.d/gen_dhparam.sh + - ./resources/ota/nginx/ssl/${SERVER_CERT_FILE}:/etc/ssl/certs/ota_server.crt + - ./resources/ota/nginx/ssl/${SERVER_KEY_FILE}:/etc/ssl/private/ota_server.key + depends_on: + - jpo_ota_backend + logging: + options: + max-size: '10m' + max-file: '5' diff --git a/docker-compose-webapp-deployment.yml b/docker-compose-webapp-deployment.yml index 17484d076..955390938 100644 --- a/docker-compose-webapp-deployment.yml +++ b/docker-compose-webapp-deployment.yml @@ -17,6 +17,8 @@ services: MAPBOX_INIT_LATITUDE: ${MAPBOX_INIT_LATITUDE} MAPBOX_INIT_LONGITUDE: ${MAPBOX_INIT_LONGITUDE} MAPBOX_INIT_ZOOM: ${MAPBOX_INIT_ZOOM} + CVIZ_API_SERVER_URL: ${CVIZ_API_SERVER_URL} + CVIZ_API_WS_URL: ${CVIZ_API_WS_URL} image: jpo_cvmanager_webapp:latest restart: always logging: diff --git a/docker-compose.yml b/docker-compose.yml index c2a5117e2..d9362828e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: cvmanager_api: build: @@ -22,7 +21,6 @@ services: MONGO_DB_NAME: ${MONGO_DB_NAME} COUNTS_MSG_TYPES: ${COUNTS_MSG_TYPES} - GOOGLE_APPLICATION_CREDENTIALS: '/google/gcp_credentials.json' GEO_DB_NAME: ${GEO_DB_NAME} SSM_DB_NAME: ${SSM_DB_NAME} @@ -42,16 +40,16 @@ services: KEYCLOAK_API_CLIENT_SECRET_KEY: ${KEYCLOAK_API_CLIENT_SECRET_KEY} CSM_EMAIL_TO_SEND_FROM: ${CSM_EMAIL_TO_SEND_FROM} - CSM_EMAIL_APP_USERNAME: ${CSM_EMAIL_APP_USERNAME} - CSM_EMAIL_APP_PASSWORD: ${CSM_EMAIL_APP_PASSWORD} CSM_EMAILS_TO_SEND_TO: ${CSM_EMAILS_TO_SEND_TO} CSM_TARGET_SMTP_SERVER_ADDRESS: ${CSM_TARGET_SMTP_SERVER_ADDRESS} CSM_TARGET_SMTP_SERVER_PORT: ${CSM_TARGET_SMTP_SERVER_PORT} + CSM_TLS_ENABLED: ${CSM_TLS_ENABLED} + CSM_AUTH_ENABLED: ${CSM_AUTH_ENABLED} + CSM_EMAIL_APP_USERNAME: ${CSM_EMAIL_APP_USERNAME} + CSM_EMAIL_APP_PASSWORD: ${CSM_EMAIL_APP_PASSWORD} TIMEZONE: ${TIMEZONE} LOGGING_LEVEL: ${API_LOGGING_LEVEL} - volumes: - - ${GOOGLE_APPLICATION_CREDENTIALS}:/google/gcp_credentials.json logging: options: max-size: '10m' @@ -71,6 +69,8 @@ services: MAPBOX_INIT_LATITUDE: ${MAPBOX_INIT_LATITUDE} MAPBOX_INIT_LONGITUDE: ${MAPBOX_INIT_LONGITUDE} MAPBOX_INIT_ZOOM: ${MAPBOX_INIT_ZOOM} + CVIZ_API_SERVER_URL: ${CVIZ_API_SERVER_URL} + CVIZ_API_WS_URL: ${CVIZ_API_WS_URL} image: jpo_cvmanager_webapp:latest restart: always depends_on: @@ -139,6 +139,152 @@ services: options: max-size: '10m' + kafka: + image: bitnami/kafka:latest + hostname: kafka + ports: + - '9092:9092' + volumes: + - kafka:/bitnami + environment: + KAFKA_ENABLE_KRAFT: 'yes' + KAFKA_CFG_PROCESS_ROLES: 'broker,controller' + KAFKA_CFG_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_CFG_LISTENERS: 'PLAINTEXT://:9094,CONTROLLER://:9093,EXTERNAL://:9092' + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT' + KAFKA_CFG_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:9094,EXTERNAL://${DOCKER_HOST_IP}:9092' + KAFKA_BROKER_ID: '1' + KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: '1@kafka:9093' + ALLOW_PLAINTEXT_LISTENER: 'yes' + KAFKA_CFG_NODE_ID: '1' + KAFKA_CFG_DELETE_TOPIC_ENABLE: 'true' + KAFKA_CFG_LOG_RETENTION_HOURS: 2 + logging: + options: + max-size: '10m' + max-file: '5' + + kafka_init: + image: bitnami/kafka:latest + depends_on: + kafka: + condition: service_started + volumes: + - ./conflictmonitor/kafka/kafka_init.sh:/kafka_init.sh + entrypoint: ['/bin/sh', 'kafka_init.sh'] + + conflictvisualizer_api: + image: us-central1-docker.pkg.dev/cdot-oim-cv-dev/jpo-conflictvisualizer-api-cvmanager/jpo-conflictvisualizer-api-cvmanager:2024-q2 + ports: + - '8089:8081' + restart: always + extra_hosts: + ${WEBAPP_DOMAIN}: ${WEBAPP_HOST_IP} + ${KEYCLOAK_DOMAIN}: ${KC_HOST_IP} + environment: + AUTH_SERVER_URL: http://${KEYCLOAK_DOMAIN}:8084 + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + DB_HOST_IP: ${DB_HOST_IP} + DB_HOST_PORT: ${DB_HOST_PORT} + SPRING_KAFKA_BOOTSTRAPSERVERS: ${KAFKA_BROKER_IP}:${KAFKA_BROKER_PORT} + CM_SERVER_URL: 'NONE' + load: 'false' + KAFKA_TYPE: 'ON-PREM' + ACM_CONFIG_FILE: adm.properties + ACM_LOG_TO_CONSOLE: true + ACM_LOG_TO_FILE: false + ACM_LOG_LEVEL: DEBUG + CM_MONGO_API_USERNAME: ${CM_MONGO_API_USERNAME} + CM_MONGO_API_PASSWORD: ${CM_MONGO_API_PASSWORD} + entrypoint: + - sh + - -c + - | + sleep ${CM_STARTUP_DELAY_SECONDS:-90} + java -Djava.rmi.server.hostname=$DOCKER_HOST_IP -Dcom.sun.management.jmxremote.port=9090 -Dcom.sun.management.jmxremote.rmi.port=9090 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=true -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dlogback.configurationFile=/home/logback.xml -jar /home/jpo-conflictvisualizer-api.jar + logging: + options: + max-size: '10m' + max-file: '5' + depends_on: + cvmanager_keycloak: + condition: service_healthy + mongodb_container: + condition: service_healthy + kafka_init: + condition: service_started + + mongodb_container: + image: mongo:6 + container_name: jpo-conflictmonitor-mongodb-container + restart: always + environment: + - MONGO_REPLICA_SET_NAME=rs0 + - DB_HOST_IP=${DB_HOST_IP} + - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + - CM_MONGO_CONNECTOR_USERNAME=${CM_MONGO_CONNECTOR_USERNAME} + - CM_MONGO_CONNECTOR_PASSWORD=${CM_MONGO_CONNECTOR_PASSWORD} + - CM_MONGO_API_USERNAME=${CM_MONGO_API_USERNAME} + - CM_MONGO_API_PASSWORD=${CM_MONGO_API_PASSWORD} + - CM_MONGO_USER_USERNAME=${CM_MONGO_USER_USERNAME} + - CM_MONGO_USER_PASSWORD=${CM_MONGO_USER_PASSWORD} + - CM_DATABASE_NAME=${CM_DATABASE_NAME} + - CM_DATABASE_STORAGE_COLLECTION_NAME=${CM_DATABASE_STORAGE_COLLECTION_NAME} + - CM_DATABASE_SIZE_GB=${CM_DATABASE_SIZE_GB} + - CM_DATABASE_SIZE_TARGET_PERCENT=${CM_DATABASE_SIZE_TARGET_PERCENT} + - CM_DATABASE_DELETE_THRESHOLD_PERCENT=${CM_DATABASE_DELETE_THRESHOLD_PERCENT} + - CM_DATABASE_MAX_TTL_RETENTION_SECONDS=${CM_DATABASE_MAX_TTL_RETENTION_SECONDS} + - CM_DATABASE_MIN_TTL_RETENTION_SECONDS=${CM_DATABASE_MIN_TTL_RETENTION_SECONDS} + - CM_DATABASE_COMPACTION_TRIGGER_PERCENT=${CM_DATABASE_COMPACTION_TRIGGER_PERCENT} + ports: + - '27017:27017' + volumes: + - mongodb_data_container:/data/db + - ./conflictmonitor/mongo/manage-volume-cron:/docker-entrypoint-initdb.d/manage-volume-cron + - ./conflictmonitor/mongo/keyfile.txt:/data/keyfile-import.txt + - ./conflictmonitor/mongo/a_init_replicas.js:/docker-entrypoint-initdb.d/a_init_replicas.js + - ./conflictmonitor/mongo/b_create_indexes.js:/docker-entrypoint-initdb.d/b_create_indexes.js + - ./conflictmonitor/mongo/manage_volume.js:/docker-entrypoint-initdb.d/manage_volume.js + - ./conflictmonitor/mongo/mongorestore.sh:/docker-entrypoint-initdb.d/mongorestore.sh + - ./conflictmonitor/mongo/dump_2024_08_20:/dump + healthcheck: + test: | + test $$(mongosh --username ${MONGO_INITDB_ROOT_USERNAME} --password ${MONGO_INITDB_ROOT_PASSWORD} --quiet --eval "try { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: '${DB_HOST_IP}' }] }).ok } catch (_) { rs.status().ok }") -eq 1 + interval: 10s + start_period: 60s + entrypoint: + - bash + - -c + - | + apt update + apt install -y cron gettext systemctl dos2unix + systemctl start cron + systemctl enable cron + envsubst < /docker-entrypoint-initdb.d/manage-volume-cron > /etc/cron.d/manage-volume-cron + dos2unix /etc/cron.d/manage-volume-cron + chmod 644 /etc/cron.d/manage-volume-cron + systemctl restart cron + cp /data/keyfile-import.txt /data/keyfile.txt + chmod 400 /data/keyfile.txt + chown 999:999 /data/keyfile.txt + + exec docker-entrypoint.sh $$@ + + command: ['mongod', '--replSet', 'rs0', '--bind_ip_all', '--keyFile', '/data/keyfile.txt'] + logging: + options: + max-size: '10m' + max-file: '5' + deploy: + resources: + limits: + memory: 3G + volumes: + mongodb_data_container: pgdb: driver: local + ode_vol: + kafka: {} diff --git a/docs/Release_notes.md b/docs/Release_notes.md index c7e198dbd..0d776a96d 100644 --- a/docs/Release_notes.md +++ b/docs/Release_notes.md @@ -1,8 +1,39 @@ ## JPO CV Manager Release Notes +## Version 1.4.0 + +### **Summary** + +This release includes the integration of the jpo-conflictvisualizer's web application directly into the CV Manager web application, the addition of the Commsignia OBU OTA firmware server, customizable user and organization level emails, Material UI theming, and many smaller changes and bug fixes. The integration of the jpo-conflictvisualizer expands the set of required services to run the jpo-cvmanager so the docker-compose has been updated to support this. You can read more about the specifics of the jpo-conflict visualizer at its repository [here](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer). In the future, this documentation will be integrated into the jpo-cvmanager repository. The OBU OTA server is specifically developed for the Commsignia OBU OTA functionality and currently does not support other devices. The email customization menu is for user level emails only and organization level emails will override these. + +Enhancements in this release: + +- USDOT PR 22: Optional TLS/Authentication for emailer support. +- CDOT PR 101: Organization level emails override user email preferences and send emails to a specified group email. (Such as Google Groups) +- CDOT PR 100: Enhanced firmware manager logs for better RSU identification on failures. +- CDOT 99: PostgreSQL based logging for the OBU OTA firmware server for recording OBU requests to the server. +- CDOT PR 95: The CV Manager's web application Admin page's 'Add' and 'Edit' menus for RSUs, Users, and Organizations has been reworked as a dialog box using Material UI. +- CDOT PR 94: Material UI theming throughout the CV Manager web application. +- CDOT PR 93: CIMMS dashboard integration. +- CDOT PR 89: Update TIM message forwarding configurations to use the TX table instead of the RX table. +- CDOT PR 88: Reduces the ping requirement strictness for the ping checker in the rsu_status_check service due to false negatives during testing. +- CDOT PR 86: OBU OTA server bug fixes. +- CDOT PR 84: Adds logic for the iss_health_check to check for missing fields in the ISS response object. +- CDOT PR 83: CV Manager web application bug fix for correctly populating consecutive selected RSU/User/Organization from the Admin pin. +- CDOT PR 82: Adds toast notifications to the CV Manager web application. +- CDOT PR 81: Adds an email customization menu on the CV Manager web application and API to support allowing users to customize which kinds of emails to receive. +- CDOT PR 80: Resolve issue with the organization dropdown not affecting the web application display. +- CDOT PR 78: Upgrade Material UI version from v4 to v5. +- CDOT PR 77: Fixes the orphaned RSU and user bug that was possible to create from the CV Manager web application. +- CDOT PR 76: Add local mongoDB to the jpo-cvmanager repository. +- CDOT PR 75: OBU OTA server added as a service to support Commsignia OBU OTA firmware upgrades. +- CDOT PR 58: Native jpo-conflictvisualizer integration directly into the jpo-cvmanager web application. +- CDOT PR 90, 91, 92, 96, 98: Miscellaneous unit test and bug fixes. + ## Version 1.3.0 ### **Summary** + This release includes enhanced MongoDB support, replacing GCP BigQuery in the CV Manager and integrating with the existing [Conflict Visualizer](https://github.com/usdot-jpo-ode/jpo-conflictvisualizer) MongoDB deployment. The web application now meets WCAG accessibility standards, featuring improved V2X data visualization and CV counts. Key updates include a daily aggregate of CV counts for better MongoDB query performance, Keycloak token refresh optimization, SNMP configurations pulled from PostgreSQL, support for PSM and TIM messages and new services like the RSU Status Checker. Additional enhancements include email alerts for firmware manager failures, a 'Contact Support' button on the 'Help' page and a filter for RSU vendors. The project now fully supports Python 3.12.2, includes various bug fixes and introduces several performance improvements across different modules. Enhancements in this release: diff --git a/resources/keycloak/Dockerfile b/resources/keycloak/Dockerfile index 65b8b3edd..ec806ff85 100644 --- a/resources/keycloak/Dockerfile +++ b/resources/keycloak/Dockerfile @@ -11,5 +11,5 @@ COPY --from=ubi-micro-build /mnt/rootfs / COPY custom-welcome /opt/keycloak/themes/custom-welcome COPY realm.json /opt/keycloak/data/import/realm.json COPY ${KEYCLOAK_LOGIN_THEME_NAME} /opt/keycloak/providers/theme.jar -HEALTHCHECK --interval=5s --timeout=10s --retries=20 \ +HEALTHCHECK --interval=5s --timeout=10s --retries=30 \ CMD curl --fail http://localhost:8080/health || exit 1 \ No newline at end of file diff --git a/resources/keycloak/realm.json b/resources/keycloak/realm.json index a3babe998..eff02e622 100644 --- a/resources/keycloak/realm.json +++ b/resources/keycloak/realm.json @@ -81,9 +81,7 @@ "description": "${role_admin}", "composite": true, "composites": { - "realm": [ - "create-realm" - ], + "realm": ["create-realm"], "client": { "cvmanager-realm": [ "manage-events", @@ -157,15 +155,9 @@ "description": "${role_default-roles}", "composite": true, "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], + "realm": ["offline_access", "uma_authorization"], "client": { - "account": [ - "manage-account", - "view-profile" - ] + "account": ["manage-account", "view-profile"] } }, "clientRole": false, @@ -245,9 +237,7 @@ "composite": true, "composites": { "client": { - "conflictvisualizer-realm": [ - "query-clients" - ] + "conflictvisualizer-realm": ["query-clients"] } }, "clientRole": true, @@ -270,10 +260,7 @@ "composite": true, "composites": { "client": { - "conflictvisualizer-realm": [ - "query-users", - "query-groups" - ] + "conflictvisualizer-realm": ["query-users", "query-groups"] } }, "clientRole": true, @@ -442,10 +429,7 @@ "composite": true, "composites": { "client": { - "cvmanager-realm": [ - "query-groups", - "query-users" - ] + "cvmanager-realm": ["query-groups", "query-users"] } }, "clientRole": true, @@ -522,9 +506,7 @@ "composite": true, "composites": { "client": { - "cvmanager-realm": [ - "query-clients" - ] + "cvmanager-realm": ["query-clients"] } }, "clientRole": true, @@ -617,10 +599,7 @@ "composite": true, "composites": { "client": { - "master-realm": [ - "query-groups", - "query-users" - ] + "master-realm": ["query-groups", "query-users"] } }, "clientRole": true, @@ -670,9 +649,7 @@ "composite": true, "composites": { "client": { - "master-realm": [ - "query-clients" - ] + "master-realm": ["query-clients"] } }, "clientRole": true, @@ -733,9 +710,7 @@ "composite": true, "composites": { "client": { - "account": [ - "manage-account-links" - ] + "account": ["manage-account-links"] } }, "clientRole": true, @@ -749,9 +724,7 @@ "composite": true, "composites": { "client": { - "account": [ - "view-consent" - ] + "account": ["view-consent"] } }, "clientRole": true, @@ -824,9 +797,7 @@ "clientRole": false, "containerId": "02814271-c58d-403f-89a6-7c831ad6a61d" }, - "requiredCredentials": [ - "password" - ], + "requiredCredentials": ["password"], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, @@ -834,15 +805,9 @@ "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, "otpPolicyCodeReusable": false, - "otpSupportedApplications": [ - "totpAppMicrosoftAuthenticatorName", - "totpAppGoogleName", - "totpAppFreeOTPName" - ], + "otpSupportedApplications": ["totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName"], "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicySignatureAlgorithms": ["ES256"], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", @@ -852,9 +817,7 @@ "webAuthnPolicyAvoidSameAuthenticatorRegister": false, "webAuthnPolicyAcceptableAaguids": [], "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], "webAuthnPolicyPasswordlessRpId": "", "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", @@ -882,10 +845,7 @@ ], "disableableCredentialTypes": [], "requiredActions": [], - "realmRoles": [ - "default-roles-master", - "admin" - ], + "realmRoles": ["default-roles-master", "admin"], "clientRoles": { "conflictvisualizer-realm": [ "manage-clients", @@ -914,19 +874,14 @@ "scopeMappings": [ { "clientScope": "offline_access", - "roles": [ - "offline_access" - ] + "roles": ["offline_access"] } ], "clientScopeMappings": { "account": [ { "client": "account-console", - "roles": [ - "manage-account", - "view-groups" - ] + "roles": ["manage-account", "view-groups"] } ] }, @@ -941,9 +896,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/master/account/*" - ], + "redirectUris": ["/realms/master/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -961,19 +914,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "8517338d-71d1-4b50-b04f-24782591477a", @@ -985,9 +927,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/master/account/*" - ], + "redirectUris": ["/realms/master/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -1016,19 +956,8 @@ "config": {} } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "57a06e7a-767a-4756-99d1-803e9d9fc7db", @@ -1054,19 +983,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "52b6ea69-e6e2-4f49-b0a5-794e544dbecb", @@ -1092,19 +1010,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "2624779c-3669-4386-aeb4-9a4623d1098c", @@ -1181,19 +1088,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "4d685882-aae4-4931-8f69-add8a8523629", @@ -1205,12 +1101,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/master/console/*" - ], - "webOrigins": [ - "+" - ], + "redirectUris": ["/admin/master/console/*"], + "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -1245,19 +1137,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] } ], "clientScopes": [ @@ -1768,20 +1649,8 @@ ] } ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins", - "acr" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], + "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins", "acr"], + "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", @@ -1793,9 +1662,7 @@ }, "smtpServer": {}, "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], + "eventsListeners": ["jboss-logging"], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, @@ -1810,12 +1677,8 @@ "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] } }, { @@ -1844,9 +1707,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -1891,9 +1752,7 @@ "subType": "authenticated", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -1903,9 +1762,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "max-clients": [ - "200" - ] + "max-clients": ["200"] } } ], @@ -1916,18 +1773,10 @@ "providerId": "hmac-generated", "subComponents": {}, "config": { - "kid": [ - "dfbf699f-79c0-4f35-aaa7-db56e599f03b" - ], - "secret": [ - "W9kRtZFV_dTxQNGd-nueSDnnNxWBfZ08d76T4W0TTi46fO7Fl5Wh-5QlciXAXsps96kMSN0hfJeYf6d_1fIeBw" - ], - "priority": [ - "100" - ], - "algorithm": [ - "HS256" - ] + "kid": ["dfbf699f-79c0-4f35-aaa7-db56e599f03b"], + "secret": ["W9kRtZFV_dTxQNGd-nueSDnnNxWBfZ08d76T4W0TTi46fO7Fl5Wh-5QlciXAXsps96kMSN0hfJeYf6d_1fIeBw"], + "priority": ["100"], + "algorithm": ["HS256"] } }, { @@ -1936,15 +1785,9 @@ "providerId": "aes-generated", "subComponents": {}, "config": { - "kid": [ - "c9aa2fa7-ccd5-42ed-9a7b-a62772c09191" - ], - "secret": [ - "QoOE0VU8d5-rflhDANBHxQ" - ], - "priority": [ - "100" - ] + "kid": ["c9aa2fa7-ccd5-42ed-9a7b-a62772c09191"], + "secret": ["QoOE0VU8d5-rflhDANBHxQ"], + "priority": ["100"] } }, { @@ -1956,15 +1799,11 @@ "privateKey": [ "MIIEowIBAAKCAQEAwT7c3z44h6uPMd4X7jqoH0DAnjyi+PoPllJXl0M9km0uD/ppE2WG/BLAdWepYYoiSbRv3iXjx7IJ6PPkQ4gJ4Gxj3+6yREruN3FrvAbD7VHLpoKsdGRnzmnHnV+XOE01a4KIMGp9wBnZzQ35PrTtkRqp5qLtkZ0KLtNAEGNKLj4lZtW/aLUg536NlSCn/LmvQhtpF60BtfKfGSPi5tEz3DbfBI9rxlo7K9ZImH6Z/HDEjSKrQhlPdiBlqBH85xRFTHTkP1ESusJ/grKyT3dlIFUjTTmh5A1EMsMKkO0CmczIdzhXbMZ1MpisQXgEU01dbLQpjDslkjqNzKLfiMwWpwIDAQABAoIBAAN4V6HQOSM/LqgrL4/dJRAeILYQTwEzjKgGfzlcakQIgFmwYbxVtgd/ZjmUNY6PhAFjDtj2LH+yv1nt0c7/CQ6wGp/7uCa9EoT6CLPTJBNhusE3x2m4chYx9hPfu5l4TPsSkMmABc2n5A+gipHs71N8uOL1KAcI7Aq6p6zE8MnzMv8TUH2a7DF/EaBDrHOX3YbZJHV88+0g3ILf6cBtRVhvIyB/wWaQb01AV/H3Fg9z/YMd6SgbJItn0wWpkePT7bXKIuK5O1Ba8Rcepw5wpMHOeX/ZtK1rm8PplgcuOZ4v5jwnBlvGTO6PN3LhPhqf7itlHncRD8ujlgvX+HtvjzECgYEA85y71HAsAduWZuOjPDk3BBGpF2QfRhLm5qEvw0vxpOwFkynGlieKiEKI5BAGY0w5iz4/g95yiFHEF/bHz/UUXm9LCe2Gy/cd6v46daqfWkhHMEFlNEfxHCRCj9d/J+Kuxpi9K5E/qSHZHC+nHo5V52gpZ8HlJZLRqrfqYDYKoa8CgYEAyxJ4wGv2uQdZ63GKP10vJ2jyPUFmLrysd8ekqpyyFTX1cKcxtOCnofFJ9xURCYHV7NXkJbBZU1tXibHtOvRwzjjqvllr9sSCdhWehxSdta61N4hlYYFIXujEMC3boT/d72BVpQyE7eZ64/1fz4gesj8rV91AQmjzjtigRZJRcIkCgYBZXze9YQWUDOYpivu4vVjEomIBVdbvU0HofFvUbwkQsxH8gkf7kDgPczFbUdG2HiHCRqzwiOxFvJGPJRb64PN/DZ9e3ggkzdzo+CmkP1tEuN19A5DIVFhNNbRBpxJcJJpv+1rzH89WEjffUlAiMp+rTJhcG1MgrLNEyUIv18OguwKBgQCWFUzRSfnKvjgi3oNCWWhkRBfkVdVjbWY6EH8O6UhkjMCdRbRi7jZ2ZZI43oT89cxZgatgf3lFNhj4V1vxWn+UqlQz4nr8ojeZdlj3lLEKedjM9i2XZqlKG9YDlaDhCAbKx/QES8Bi4xioL7cD9qJZMn6iLY80hcScKlYplP5DoQKBgA3hirTJB+2+D3ou+HQJEMP8X8pMZ9mNQ8YBqOQEm+UaQ4Cg06/HLdKqHmZ1srpGybZ6atZwddW90lEbWDUafz9x6K7AUpEfyx0jw7jfLDy1Sl9SSbU3zpVUZJbJfPVL/xZic9ACJVa315qTI7z45fB+pCau6Xv6pmEeOgSCo1BQ" ], - "keyUse": [ - "SIG" - ], + "keyUse": ["SIG"], "certificate": [ "MIICmzCCAYMCBgGND5WfHjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwMTE2MDAwMzAwWhcNMzQwMTE2MDAwNDQwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBPtzfPjiHq48x3hfuOqgfQMCePKL4+g+WUleXQz2SbS4P+mkTZYb8EsB1Z6lhiiJJtG/eJePHsgno8+RDiAngbGPf7rJESu43cWu8BsPtUcumgqx0ZGfOacedX5c4TTVrgogwan3AGdnNDfk+tO2RGqnmou2RnQou00AQY0ouPiVm1b9otSDnfo2VIKf8ua9CG2kXrQG18p8ZI+Lm0TPcNt8Ej2vGWjsr1kiYfpn8cMSNIqtCGU92IGWoEfznFEVMdOQ/URK6wn+CsrJPd2UgVSNNOaHkDUQywwqQ7QKZzMh3OFdsxnUymKxBeARTTV1stCmMOyWSOo3Mot+IzBanAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAL38ECCrYGlZDfY1urNoQS6xDDgSyyzTN8jhKpffur1K7t2Gsa9LhjKAYYmx0/BzP/3e24QaYIGfM/kMx2FnnQ/eXA+AFy8pGAAHZadag2WKNu7aU2DY4ng9KubfdA4HvxZFbhRJVg3sdF02y0OxY+UWEV7y3/LKbwjeOwDNvth7NTGIvip10HGMGb2PjCdtQXFF8PcFmfx6uJPKDqOyzS3StG3fquLmhfk+YB3MdVmsMEVxs8qP1ReiJ3XJV6lUKd1A3AR1R/+odCXnsYgf/rjmhauwzF7F57mumLBLS1OADhOLLCFs6boMmhJF5t7vs/084kcnGcVCRBNOSppE+rE=" ], - "priority": [ - "100" - ] + "priority": ["100"] } }, { @@ -1976,18 +1815,12 @@ "privateKey": [ "MIIEowIBAAKCAQEA3h062LCEjaFRP5RmGymtdXcdn61ZrINb9j4kMt2dSYDi1euN9G85LBePslVytwv8rk7k8Z4ReWf33GGJjonBFTKYaL0g3ZRCp4cizWtDIJtXBJSodjsigze73rMXs395Y4n/uMcKo1c7va16x3agUrntX9HZRpOGQ0UUpYzzdUiMYGoa9bWKuvxqAQDonNRXx04Mz+J48TtmfB73R9pM07NvclihZikp3kUv43vffKbu7jaEVcmBO7Zj2rlTHB3xRnDNhGKes4bK00ldzdUwpjWyqGw7fxMj0XK19VPbIaqwq7VJIqi4wM6qcZFKo/mFIkvvLX1jZLLSe+AoRCrznwIDAQABAoIBABN5M5TA8SDWYQsL0IyWxwRH87scAIMypx2ROCzqJ3qaHV53qaP3G7zvueOpldunkOPwEHwrNB6qoggFLda8jBxMKcxGXKXLSbFqFH/huhUXRCCnhJeNEAsPbV4VN0GVA7MEQSfVnOtSNW99ULOWhRrg/dygnNs7tDycnLahA3f866O6hHPF6ggT3j9kEV54ya2krUmU0eGZXZAMcIAbuGgJr/MPiE5u9r2VIjwJTujyf1TgBMpfJaWLOIYDUs528VEZUFJbv5CHc3c8cKexiBafqcz8/keL7teninV6ASLRSNmYHjhFtIk68OSKYvZqJAceZR2wYxLh/IwP2+xH3o0CgYEA+k8hm/x5XZ9PuFT5Hl0SluYLumIUeDRvzwK96WdlBRe2tVmHvDg9wQFa2YyDx5nXl+YoglnlgAZ+Nk34L1sOBCpEZhFZ7c0efLZEtjFf1cbO4Ar0FgDfU6KxyZj7HXJn0FvgW1h1KleZV5miaTfEGZ3uE841TlFcKWk76tC1UEsCgYEA4yn/CmZeYU+ZbqTOWStOluXzfJpHmZOFE+cKlICH6RrcX7VCxhjXRVgJ4dpjAMnGUfkL821V3JknEPFyKS1JpfSWvF5751re97MtRysD+2xw6P8rrgjQuuHE+uZAGuCWCUjgFUUElSYgNPpvaU508vyi4CDYrZoFzEplzkC23X0CgYATRMYskNny6BGl+fyXZsjIjvr2JRi4TCkTQX3HGut+4d1xxmuZhKbUVbtdpeB7HA+ppNEXf74YBefvXD8vvg2tKmfLh6hpkvG23f0aHWDoPv6r5ov1qamHca3H/BvQn374Xio+Pef/E3E9ehkzilRxOGQcaDJYThEPKweuwtRCUwKBgDjrG+laLwnI7RPpHX8AN+fdZD3zVj9n1C9hc6gz8Fn7Df65JysFrGLGpWs+0hGvfQ6rDVCIM7xbb4tyQ/2HSG3ZtC8sqXUVsspzzcOIRq4nxL7MuQAZW1uIGFgZezSA03cuGF+b9IL+k5FSsrm7G9iKbrEj6cbN0egXOB0O4ALtAoGBAMHZA9TqnWR+G8rj0eNSR/YXVQDY5uQJbj2sCke2VvwTZ0G5VHK+xuLESjsy5KMKYQtcT/ZRR6EDo0Jr/xbNeLs+D1iGBklRUOwtED7i7mlPDj6P+nfLAWR+k5L9zYg5mddYdq6+J7nf/l1VDnppE3NDwETOTG8nd34ENdOHn9qq" ], - "keyUse": [ - "ENC" - ], + "keyUse": ["ENC"], "certificate": [ "MIICmzCCAYMCBgGND5Wg5jANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwMTE2MDAwMzAwWhcNMzQwMTE2MDAwNDQwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDeHTrYsISNoVE/lGYbKa11dx2frVmsg1v2PiQy3Z1JgOLV6430bzksF4+yVXK3C/yuTuTxnhF5Z/fcYYmOicEVMphovSDdlEKnhyLNa0Mgm1cElKh2OyKDN7vesxezf3ljif+4xwqjVzu9rXrHdqBSue1f0dlGk4ZDRRSljPN1SIxgahr1tYq6/GoBAOic1FfHTgzP4njxO2Z8HvdH2kzTs29yWKFmKSneRS/je998pu7uNoRVyYE7tmPauVMcHfFGcM2EYp6zhsrTSV3N1TCmNbKobDt/EyPRcrX1U9shqrCrtUkiqLjAzqpxkUqj+YUiS+8tfWNkstJ74ChEKvOfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJX4WIFQtIxBmbG/0NlJ+aGTSmayU1Qo+La2Qy1yvclX0OfvyQBkXlvOmnDRsBdl/lKXh804LCHl2qXFbS8r9Qy5d/Vb71hynUc5IAHyRlMY3xaz8mH8xxFZh7iUFkl3hXA4bNqa9f4zDmTyoNjlFn5pgL98PFqU3Lc4nuL73woow5JCMuTALBj9+IKP6UZxh05eR5T9JEV3fwXW+MCvRwxl8b6Eu2g7WOykk3IjMV7u7zBGtTjMsQAREmFTtP4Pk2oeeDr08YpxufSZWAY8nDtVteq54ueP39fnAYwrApRT5zFrW/XbMtYffL+l/VHwCTYRV1BqcyUNrRli4sMYSYw=" ], - "priority": [ - "100" - ], - "algorithm": [ - "RSA-OAEP" - ] + "priority": ["100"], + "algorithm": ["RSA-OAEP"] } } ] @@ -2786,15 +2619,9 @@ "description": "${role_default-roles}", "composite": true, "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], + "realm": ["offline_access", "uma_authorization"], "client": { - "account": [ - "view-profile", - "manage-account" - ] + "account": ["view-profile", "manage-account"] } }, "clientRole": false, @@ -2820,9 +2647,7 @@ "composite": true, "composites": { "client": { - "realm-management": [ - "query-clients" - ] + "realm-management": ["query-clients"] } }, "clientRole": true, @@ -2854,10 +2679,7 @@ "composite": true, "composites": { "client": { - "realm-management": [ - "query-groups", - "query-users" - ] + "realm-management": ["query-groups", "query-users"] } }, "clientRole": true, @@ -3039,9 +2861,7 @@ "composite": true, "composites": { "client": { - "account": [ - "view-consent" - ] + "account": ["view-consent"] } }, "clientRole": true, @@ -3082,9 +2902,7 @@ "composite": true, "composites": { "client": { - "account": [ - "manage-account-links" - ] + "account": ["manage-account-links"] } }, "clientRole": true, @@ -3127,14 +2945,9 @@ "name": "ADMIN", "path": "/ADMIN", "attributes": {}, - "realmRoles": [ - "ADMIN" - ], + "realmRoles": ["ADMIN"], "clientRoles": { - "realm-management": [ - "manage-realm", - "realm-admin" - ] + "realm-management": ["manage-realm", "realm-admin"] }, "subGroups": [] }, @@ -3143,9 +2956,7 @@ "name": "USER", "path": "/USER", "attributes": {}, - "realmRoles": [ - "USER" - ], + "realmRoles": ["USER"], "clientRoles": {}, "subGroups": [] } @@ -3158,9 +2969,7 @@ "clientRole": false, "containerId": "32e7a5de-88df-49ff-be58-a2358342aef0" }, - "requiredCredentials": [ - "password" - ], + "requiredCredentials": ["password"], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, @@ -3168,15 +2977,9 @@ "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, "otpPolicyCodeReusable": false, - "otpSupportedApplications": [ - "totpAppMicrosoftAuthenticatorName", - "totpAppGoogleName", - "totpAppFreeOTPName" - ], + "otpSupportedApplications": ["totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName"], "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicySignatureAlgorithms": ["ES256"], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", @@ -3186,9 +2989,7 @@ "webAuthnPolicyAvoidSameAuthenticatorRegister": false, "webAuthnPolicyAcceptableAaguids": [], "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], "webAuthnPolicyPasswordlessRpId": "", "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", @@ -3224,14 +3025,9 @@ ], "disableableCredentialTypes": [], "requiredActions": [], - "realmRoles": [ - "ADMIN", - "default-roles-conflictvisualizer" - ], + "realmRoles": ["ADMIN", "default-roles-conflictvisualizer"], "notBefore": 0, - "groups": [ - "/ADMIN" - ] + "groups": ["/ADMIN"] }, { "id": "a08a9efc-cd46-4111-836f-b665db815faa", @@ -3254,32 +3050,22 @@ ], "disableableCredentialTypes": [], "requiredActions": [], - "realmRoles": [ - "USER", - "default-roles-conflictvisualizer" - ], + "realmRoles": ["USER", "default-roles-conflictvisualizer"], "notBefore": 0, - "groups": [ - "/USER" - ] + "groups": ["/USER"] } ], "scopeMappings": [ { "clientScope": "offline_access", - "roles": [ - "offline_access" - ] + "roles": ["offline_access"] } ], "clientScopeMappings": { "account": [ { "client": "account-console", - "roles": [ - "manage-account", - "view-groups" - ] + "roles": ["manage-account", "view-groups"] } ] }, @@ -3294,9 +3080,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/conflictvisualizer/account/*" - ], + "redirectUris": ["/realms/conflictvisualizer/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -3314,19 +3098,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "c7c9fb05-a4d2-460a-a0b5-ae22374035f4", @@ -3338,9 +3111,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/conflictvisualizer/account/*" - ], + "redirectUris": ["/realms/conflictvisualizer/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -3369,19 +3140,8 @@ "config": {} } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "a3dea47a-aa8e-4eb9-bd60-cc2ccc6c6b66", @@ -3407,19 +3167,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "0f9298ea-1f51-48e6-ab84-bbbebabdbdb6", @@ -3445,19 +3194,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "bdbb88ca-d453-40c2-b547-5d5887e8c3ba", @@ -3471,12 +3209,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/*" - ], - "webOrigins": [ - "/*" - ], + "redirectUris": ["/*"], + "webOrigins": ["/*"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -3496,19 +3230,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "70254050-2a61-479d-93db-a962a2f558d2", @@ -3523,14 +3246,8 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "${KEYCLOAK_CM_API_CLIENT_SECRET_KEY}", - "redirectUris": [ - "http://localhost:3000/*", - "${WEBAPP_CM_ORIGIN}/*" - ], - "webOrigins": [ - "http://localhost:3000", - "${WEBAPP_CM_ORIGIN}" - ], + "redirectUris": ["http://localhost:3000/*", "${WEBAPP_CM_ORIGIN}/*"], + "webOrigins": ["http://localhost:3000", "${WEBAPP_CM_ORIGIN}"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -3553,19 +3270,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "a2076ecd-5345-490a-bac9-77a7d69c280b", @@ -3591,19 +3297,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "35726e9e-f948-4775-a957-e54bf22f490e", @@ -3615,12 +3310,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/conflictvisualizer/console/*" - ], - "webOrigins": [ - "+" - ], + "redirectUris": ["/admin/conflictvisualizer/console/*"], + "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -3655,19 +3346,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] } ], "clientScopes": [ @@ -4178,20 +3858,8 @@ ] } ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins", - "acr" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], + "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins", "acr"], + "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", @@ -4203,9 +3871,7 @@ }, "smtpServer": {}, "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], + "eventsListeners": ["jboss-logging"], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, @@ -4220,9 +3886,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "max-clients": [ - "200" - ] + "max-clients": ["200"] } }, { @@ -4267,9 +3931,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -4279,12 +3941,8 @@ "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] } }, { @@ -4294,9 +3952,7 @@ "subType": "authenticated", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -4329,15 +3985,11 @@ "privateKey": [ "MIIEogIBAAKCAQEAy4jPC9qu4pLJF5i1GSPYPaStuGi0o9k5CfXvrRynZbaZMZWg3r+t9VWsSuGuRXWvN0Q6ChzClV0SJ2jYvCV7qhHEE+1NE7dgkL1wh8e58dsHeKx3lFOtCVg6iSNsL65MWLerbaW2pnswXy/lkwquX8Zof5DD20fM6WrReVSb6mlO60SZ5c3WoidgAAo4HaeyXS/0ASrCAdKQwSHph6oYLPSHpDshDsitRBuS8Hk5+Ex1ZCw1ym/Hbs8Z5NeqQok1KzTCwwJ/EOPfkXuicQnYDHxvFAtDim0kepH8xw1T5+SV3/X7ICYTTIwQGNn0TB5uoZcO2BRSzsZekKdt/dT6CwIDAQABAoIBACgl7u2AMBIqcTdR3Js+alfShaAEK2nMngc8d5A2wEB8keBpiweVWNu1kBxfQxCZg7wLncVD4hAzgTK76FDItgmYooxpuVQDzq0OaUWnXKL8GQ+xOY9NKCtZN4a4sY0APTgc1th0oUBauXJ1ULw+FaJ30UIkjLXTBnUeqH5d9bl5vQsgcCLxhUIGM8OvOaLFex9W0tg9AlmW2f7ZgybP6Fa4uJXSo02K4zwDAwtOQmnEvCcfI2qMSDGZbdeXUTZUkys66s1q6OrXc1bo77VSzA1ClHZmFM8AO+wHuVgiK9tTtipUJ4MuuOVA2jQJHBs7oSYvTV/azclNCkJci5Fwi2ECgYEA906N0jWEJQJSDqe+YxbKH0gBE4g8TaShuaT+ljNHQCPuT/7NnAIEMci63eIw0oseCM/MfJnjPmP5naFICqlTWLQYjlloxrzVkz2y/wrZM/YJUCFGIP26jMcJLPYHeLK9QD7utp+P6UJ9fMQ2kkD7BjErEFV548fscSptBh+uAiECgYEA0rBb0iCETlUeSQATP9U/V8HdN3Hf6ua8limu02/kFWGwnSoDPuy+IU1fAMuFkgMBiGpJjbBw6mUcuhj7YkvQybnQY6e8vpS+f9jNoFCQt2n+r61XPEqig6fbmYKiJk9Al9GjRtomVikIjmv4NIo3v2qe4ExH4haqzjZChriDzqsCgYA2KlxhmBsTSAjU8OSAK3Olmk2yC3q5vr81O/AO0bhfUf9WQgaijsaAaOiUxH/Q/Wtcnra467Ob7KW1Yqe2vhNlMDzYoLiUHrPghfj4Z1XfTZoIlOEZRLlhMA7QbCqCwxM0SRRbp2MLweZeN1OEgPr6BjbaYv5JZ3Zf6tzqJHImQQKBgCDW2kkDRnwLKmBIgbeWXnwoPHnS5wrvEf/52UUdkJiAlI26qazaK7x3GdK+5j/e9hM0Nei+0qrGPdcH487rcEyxCLkvwOyXtKWqvko5pITiIY9yXkGIhJIuzLy9rtZ3zeKcC24UvJr7ZFkGnTZbQNs2HDNr0Fx+GftwW6gyBGFnAoGAKIKCtasZKWOYeaSHDGEUSw8xLEnC7lXy0dJ/68X++06aMeQsjfa4nyEmSiuZ/IdqK1m8gHyQXXQueJigRyNiSBG7MWv3xzrLJ8Sr25k7AdTyU0vvbW/VXJnK+lX63NiE7V7fPGhWxLF8c01L4XpVcsGU3sAphDG+0+kYz5e8FQw=" ], - "keyUse": [ - "SIG" - ], + "keyUse": ["SIG"], "certificate": [ "MIICszCCAZsCBgGND5sWKTANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJjb25mbGljdHZpc3VhbGl6ZXIwHhcNMjQwMTE2MDAwODU4WhcNMzQwMTE2MDAxMDM4WjAdMRswGQYDVQQDDBJjb25mbGljdHZpc3VhbGl6ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLiM8L2q7ikskXmLUZI9g9pK24aLSj2TkJ9e+tHKdltpkxlaDev631VaxK4a5Fda83RDoKHMKVXRInaNi8JXuqEcQT7U0Tt2CQvXCHx7nx2wd4rHeUU60JWDqJI2wvrkxYt6ttpbamezBfL+WTCq5fxmh/kMPbR8zpatF5VJvqaU7rRJnlzdaiJ2AACjgdp7JdL/QBKsIB0pDBIemHqhgs9IekOyEOyK1EG5LweTn4THVkLDXKb8duzxnk16pCiTUrNMLDAn8Q49+Re6JxCdgMfG8UC0OKbSR6kfzHDVPn5JXf9fsgJhNMjBAY2fRMHm6hlw7YFFLOxl6Qp2391PoLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAILnjnYEP3d+eIIEYfh3T0S3I6Vmf2DezOJV36u1obFvuE80QbdKsUka+0FimcKnXcQroB1ZVO761Zy3W4IefqHZvCKMaVMIHt6yG3qK9kGb28xKPAqN1Zal9BAJk/XdVlmqtshm0UZYQ49cBqLhhP8mqfIR+mv3Ict8PnVFGkBWkihW+Nvx0M5gHIYlncAjgRCoWXBlR75ZgLuVTtMVwE80NlRfp/hkgkaj1WagC3xdZ5f5CLVcwTvvLqpcZ8FbB5iNI3oQLAXgpHgh2EpDYvB4DViGjJ9N/Z1GYCPETOK1A+hf4PGA3wF4fujOJ8VN2HRXUS2aoZ+OOxMfwqgvFYA=" ], - "priority": [ - "100" - ] + "priority": ["100"] } }, { @@ -4349,18 +4001,12 @@ "privateKey": [ "MIIEpQIBAAKCAQEAk45iaNteDDROult+hGyb91tKRFIYkapxqQe0Liv7qZorAe7OQrqU+r9TdFer1sbTLAyPYvUoblanWBgdoyd+atL/MhTdifE6F3G0HGzqHO62lgUaFr9nuMMjJojWOOUzVuOVATD8N6S8knW2y27JUyzS+cnIUB1iXHmZAY3RwU7a82+j2J+xT5DfF0MYIq8R/R9Uztmgz6W3PLQbvQ4C/QRy9kt1yxmQGWZUmAlIWgKTP1Bo7ID2/zdRjMEzr61X1P1wvIKg8+0+MgUUd1KqM1e8AC6UbY5N6ASOtIggnICkrmB9pG4bNsM383el69AxHagIttB79dH0+CeOdlmi0QIDAQABAoIBAAGd17oqXRdpI0urYPF2dyb4mxGwjJBb+67MZM70sGclRz9YLG5SuPseSX3G0B0kRIABCzkcUnsS+/ZdHUYCUsI82Yrk66BtQiOrnTuKfe4fN2ThXW3OXwaJLMNpUF+DM1LKX4GJ1dmONnqsS3pjFlWQ8ibGbSljiQZWVrTLpvalMEKXjxvdf4i3gTB7zBAUr55qv7PSskzftoK2HNooWpR36DlLjMJHLrYToOuaHpREgUprvbLLzC5Y17hMBlLUevczHIg0/av4xYnS/QYEqhEHTHwjY4gtjQ4EdwdE7xvX0rYCgQtYypyTCyySVbu18rAsedOxKu4yycPHw+10OAECgYEAxOGUBd5Omcdi6/ehrYt7LukC9ukPDql1OeUmrVpblQD7t6I2A0CP15JlVfaJoZG3nfy40lJasI9ZWhfv1Uz9D9AFDlYlBF7SILrlI8c9+lJP0QC1KQyTrXL72tXk5Sb3dQEJvQJxMfgWUrPgJvvnIexEAl67+5RUdC7ly2P+vkECgYEAv90nT74zWzLzgnWQG3sgBw2P2Fh09LWEJA3hu02WWyalXXwY/l5Y4LmDIo9M9RWiHNz3GZ6blkBwIGjJTa39Xj1G6454Cg4sZ2oZ7ucZpgFYq7vGjQxysybD33aKI6ZoL7yX+yjSI/HIy7baavRNdoxAXrUj97F09YfR6STX4JECgYEAtLKtXu14ip97ZUO0eknIV3e5JtEk5roVQ3vUEcsavxlV7mbM2BNLcfmFVG6gR1AsjK5FG5RGdEI9KflKQCUXJoov+caK1wYIKc4fPMVDVxpw80yI+RH4AHvGOEWUfdVTzWmUfItfRRODuYgqNN2Cd5oXmW1Fb1PmZ3QbqJ8wUYECgYEAnXWJYrds/Ga3VBTZnMQSh9dIezw1V/N0LAa8f/Rv9gSkaDGFbZTOijeVeJJ0jRsg/WEW5g62D7x4iRCWTMsDCgluH7m/qDjzljeMavV8pjGqrN4hV/akV4Tz8XweaJ2UGcFEVZqtw1QV/6HkZSx0OltmJJOyngAkRbEew6E6DhECgYEAoq52lDO+OBEqqrZ2i/j2BhveN7SPT+8gyf6Zb6EcnV8QuqJmIie3LEwJnBBDkrE8EhjK1DbxCFCd+7ObtPM4YAN5DuKs0CSuvDBoza70/ek112S6+87gZ1boyCKPsx8kdPyge3QICOFUwzxhmFjtKr6uzJMvTn3QzUgf5DtLRmI=" ], - "keyUse": [ - "ENC" - ], + "keyUse": ["ENC"], "certificate": [ "MIICszCCAZsCBgGND5sW7zANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJjb25mbGljdHZpc3VhbGl6ZXIwHhcNMjQwMTE2MDAwODU4WhcNMzQwMTE2MDAxMDM4WjAdMRswGQYDVQQDDBJjb25mbGljdHZpc3VhbGl6ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTjmJo214MNE66W36EbJv3W0pEUhiRqnGpB7QuK/upmisB7s5CupT6v1N0V6vWxtMsDI9i9ShuVqdYGB2jJ35q0v8yFN2J8ToXcbQcbOoc7raWBRoWv2e4wyMmiNY45TNW45UBMPw3pLySdbbLbslTLNL5ychQHWJceZkBjdHBTtrzb6PYn7FPkN8XQxgirxH9H1TO2aDPpbc8tBu9DgL9BHL2S3XLGZAZZlSYCUhaApM/UGjsgPb/N1GMwTOvrVfU/XC8gqDz7T4yBRR3UqozV7wALpRtjk3oBI60iCCcgKSuYH2kbhs2wzfzd6Xr0DEdqAi20Hv10fT4J452WaLRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIQU/kDws30SkbpE4GXTHzPPpChJV7zTCdc05mg/bAQ4HEYPgX0UhfFRJsGiK9XnhmookGyPSbsf8xA9lbiHT3BDF5lW+pZ7trGv3c05/S0hpTBDFme+THJPva8yGgDXWTHyLB7L8GJA4WMeQtZ7+xU6A6o3W7RGnDRv2zfdoegbHXAD36xV387RoMhwiWC8IpGNRI1Vop2oVSskfnjKGSOxB60COCo/pCjbhRAC2pA2pad0a1d6vuKZVes0qPaoIbrbOnZqswzOt+7gbmufZ6pNG7gB4dYIdbrkwu5ykjHKXicAgTGPsom68+ODYJiEA6CzPLKsmD3VplBTPu7Huu8=" ], - "priority": [ - "100" - ], - "algorithm": [ - "RSA-OAEP" - ] + "priority": ["100"], + "algorithm": ["RSA-OAEP"] } }, { @@ -4369,18 +4015,10 @@ "providerId": "hmac-generated", "subComponents": {}, "config": { - "kid": [ - "093421fa-ea07-4a61-bd3e-1e7f49f7bf2c" - ], - "secret": [ - "g5uGwWmIkYk-wYldnQ28zpvqB6v6HPrqwQCfuPPZvsI5chYFIQQdLZaPTUwBBNMiiGbuq3h4LUy3Nw1MY9mrug" - ], - "priority": [ - "100" - ], - "algorithm": [ - "HS256" - ] + "kid": ["093421fa-ea07-4a61-bd3e-1e7f49f7bf2c"], + "secret": ["g5uGwWmIkYk-wYldnQ28zpvqB6v6HPrqwQCfuPPZvsI5chYFIQQdLZaPTUwBBNMiiGbuq3h4LUy3Nw1MY9mrug"], + "priority": ["100"], + "algorithm": ["HS256"] } }, { @@ -4389,15 +4027,9 @@ "providerId": "aes-generated", "subComponents": {}, "config": { - "kid": [ - "b1167c6a-732c-485b-9291-8e74be24ee5f" - ], - "secret": [ - "7O87ebN9g4E0a3Y25qmGZg" - ], - "priority": [ - "100" - ] + "kid": ["b1167c6a-732c-485b-9291-8e74be24ee5f"], + "secret": ["7O87ebN9g4E0a3Y25qmGZg"], + "priority": ["100"] } } ] @@ -5156,21 +4788,33 @@ "failureFactor": 30, "roles": { "realm": [ + { + "id": "cb80b6bc-eafa-4f58-80ca-1e38ac8af5a1", + "name": "USER", + "description": "Conflictvisualizer User", + "composite": false, + "clientRole": false, + "containerId": "d0dc22ea-63fe-42ee-81d0-beed32b15ea7", + "attributes": {} + }, + { + "id": "eeb12ecf-17c1-45f0-9bc4-957a618039f9", + "name": "ADMIN", + "description": "Conflictvisualizer Administrator", + "composite": false, + "clientRole": false, + "containerId": "d0dc22ea-63fe-42ee-81d0-beed32b15ea7", + "attributes": {} + }, { "id": "998ccb10-fd0a-4e8a-9e56-e1c7962bf20c", "name": "default-roles-cvmanager", "description": "${role_default-roles}", "composite": true, "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], + "realm": ["offline_access", "uma_authorization"], "client": { - "account": [ - "manage-account", - "view-profile" - ] + "account": ["manage-account", "view-profile"] } }, "clientRole": false, @@ -5232,10 +4876,7 @@ "composite": true, "composites": { "client": { - "realm-management": [ - "query-groups", - "query-users" - ] + "realm-management": ["query-groups", "query-users"] } }, "clientRole": true, @@ -5285,9 +4926,7 @@ "composite": true, "composites": { "client": { - "realm-management": [ - "query-clients" - ] + "realm-management": ["query-clients"] } }, "clientRole": true, @@ -5433,9 +5072,7 @@ "composite": true, "composites": { "client": { - "account": [ - "view-consent" - ] + "account": ["view-consent"] } }, "clientRole": true, @@ -5467,9 +5104,7 @@ "composite": true, "composites": { "client": { - "account": [ - "manage-account-links" - ] + "account": ["manage-account-links"] } }, "clientRole": true, @@ -5515,7 +5150,28 @@ ] } }, - "groups": [], + "groups": [ + { + "id": "1d271cc0-96b8-434d-9c87-375cd789897a", + "name": "ADMIN", + "path": "/ADMIN", + "attributes": {}, + "realmRoles": ["ADMIN"], + "clientRoles": { + "realm-management": ["manage-users", "realm-admin"] + }, + "subGroups": [] + }, + { + "id": "3385cd0b-1ddf-460e-8c67-c4cf138868ec", + "name": "USER", + "path": "/USER", + "attributes": {}, + "realmRoles": ["USER"], + "clientRoles": {}, + "subGroups": [] + } + ], "defaultRole": { "id": "998ccb10-fd0a-4e8a-9e56-e1c7962bf20c", "name": "default-roles-cvmanager", @@ -5524,9 +5180,8 @@ "clientRole": false, "containerId": "d0dc22ea-63fe-42ee-81d0-beed32b15ea7" }, - "requiredCredentials": [ - "password" - ], + "defaultGroups": ["/USER"], + "requiredCredentials": ["password"], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, @@ -5534,15 +5189,9 @@ "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, "otpPolicyCodeReusable": false, - "otpSupportedApplications": [ - "totpAppMicrosoftAuthenticatorName", - "totpAppGoogleName", - "totpAppFreeOTPName" - ], + "otpSupportedApplications": ["totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName"], "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicySignatureAlgorithms": ["ES256"], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", @@ -5552,9 +5201,7 @@ "webAuthnPolicyAvoidSameAuthenticatorRegister": false, "webAuthnPolicyAcceptableAaguids": [], "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], "webAuthnPolicyPasswordlessRpId": "", "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", @@ -5586,29 +5233,22 @@ ], "disableableCredentialTypes": [], "requiredActions": [], - "realmRoles": [ - "default-roles-cvmanager" - ], + "realmRoles": ["default-roles-cvmanager"], "notBefore": 0, - "groups": [] + "groups": ["/ADMIN"] } ], "scopeMappings": [ { "clientScope": "offline_access", - "roles": [ - "offline_access" - ] + "roles": ["offline_access"] } ], "clientScopeMappings": { "account": [ { "client": "account-console", - "roles": [ - "manage-account", - "view-groups" - ] + "roles": ["manage-account", "view-groups"] } ] }, @@ -5623,9 +5263,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/cvmanager/account/*" - ], + "redirectUris": ["/realms/cvmanager/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -5643,19 +5281,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "48417936-3262-4cc6-a53d-69809be217ec", @@ -5667,9 +5294,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/cvmanager/account/*" - ], + "redirectUris": ["/realms/cvmanager/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -5698,19 +5323,8 @@ "config": {} } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "14c1acec-de65-4f71-a62e-dfc20eb42a11", @@ -5738,19 +5352,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "a48352c8-caf6-4175-8f10-b889a6815067", @@ -5778,19 +5381,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "02c60f24-1909-4d39-bab5-1db615e1b224", @@ -5805,12 +5397,8 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "${KEYCLOAK_API_CLIENT_SECRET_KEY}", - "redirectUris": [ - "*" - ], - "webOrigins": [ - "*" - ], + "redirectUris": ["*"], + "webOrigins": ["*"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -5833,20 +5421,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "acr", - "openid", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "openid", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "8232316f-728b-4e89-90ca-c91907d7718d", @@ -5860,14 +5436,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "http://localhost:3000/*", - "${WEBAPP_ORIGIN}/*" - ], - "webOrigins": [ - "http://localhost:3000", - "${WEBAPP_ORIGIN}" - ], + "redirectUris": ["http://localhost:3000/*", "${WEBAPP_ORIGIN}/*"], + "webOrigins": ["http://localhost:3000", "${WEBAPP_ORIGIN}"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -5895,20 +5465,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "acr", - "openid", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "openid", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "b2cdce72-e870-4473-99c2-5dba6ebee37e", @@ -5936,19 +5494,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "71948848-7dc7-4c8f-9437-ffb5e27662d6", @@ -5960,12 +5507,8 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/cvmanager/console/*" - ], - "webOrigins": [ - "+" - ], + "redirectUris": ["/admin/cvmanager/console/*"], + "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, @@ -6000,19 +5543,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "acr", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] } ], "clientScopes": [ @@ -6537,20 +6069,8 @@ ] } ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins", - "acr" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], + "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins", "acr"], + "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", @@ -6563,9 +6083,7 @@ "smtpServer": {}, "loginTheme": "cv-manager", "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], + "eventsListeners": ["jboss-logging"], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, @@ -6604,12 +6122,8 @@ "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] } }, { @@ -6638,9 +6152,7 @@ "subType": "authenticated", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -6650,9 +6162,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { @@ -6670,9 +6180,7 @@ "subType": "anonymous", "subComponents": {}, "config": { - "max-clients": [ - "200" - ] + "max-clients": ["200"] } }, { @@ -6713,15 +6221,11 @@ "privateKey": [ "MIIEowIBAAKCAQEAsnxqBGGqOEoyQ0Dq3iWSf+H+7C/xbh/ROakF2+oEfGemWVjsfrDXBUZhm9wWPQnW/Ow8qioG0H//1Nv1dryzfScLm49luGfxbieUbRNDb+OvcDTf6i/8r1T+mCEW/vUDon7PNTpOckrP8rRUcbvTSqX0HGo9weMrYlnBcIZxYzkflYNmBrbj46Fp7wrmG1TzoeEIVBviq/jYBRdnCoJE2mLbCnXupJErnQeuhRezfw6uCMASjRkZb+HGk1mGQrykE3/U3eeGXLlVA0G+Fe34SoXStTCuENYFb/zXMskjC2L3EgqXatuPNt+pNZuJyFz8jKEtbgkBKGMLeDkm5NNr1QIDAQABAoIBAE+ZEoKvt4Tw+edqTRQS93mWpORaITZ2dA1d5qIDhEqiwtn3wUhivxG4KJGknjpMaBdVl1xf77gOTV51VcvFLdqzjgaq9bc+i7oPZq8aNynwBW5p9i3vhqX+pqfbofDD/gH6wZfAT/nCiWh4qWwrUnho+Cuv6ajNEa0D0DPJkUmpEP5r0Eq1ZBpkV6I7brKtCPoMHIbofm7oqshRYxvzSVhlYJUSU0aMVw/7Dww6zT5vrX6QJWHLY9mBR3YxS/uMG/vYMKEu2hXbsdjtBRDl/u4aiAIk+OIulcmAlDnxC0dFHgw7rsDpK5QI2SJ1m3MF3lZ4AiOZs/o900US+431ryUCgYEA8LiMGI5kdKvEnVbYAqEzoo5EHjx1l9ha6F8Dzy/bKKY6noPePCC24j27Zv25OjBcq8MxFvo7grqDG+ZtWzes17pxmpe3ttaXtuf9RRf8wLw323USRrwJu52xD5STfombRKHwpKEy/NRewTnX6qK6QvEki/9YbEbgPEuA8sOvUHsCgYEAvdCeE7MdiBZEinV3+voBZuCJzlN7d2gSoGWv/ZZUNBMqE3Wiyr5dx85TiBkIbghO+t5EOZRQFL403nIpDPLVXLSsZxdAbtOtSGcc7zjUITpuz6malr4Tzlp+4d0+8Vjc+ooDTnIanCq0bx1vpBTitx3qGHZR/OCVkQ57Lsl2C+8CgYAhCvQQGtunODzQ7C7SjZYs5iJrlBkAMu6nnwNC2WrX9ZluUOOclVEFVTv4MzPNzP2rhiui385zb2630bWJI+dR5YHamqDZNDO3I7kcVuKXAj8YnMVZeE5NtqOrY9WrNPBfR2tk7cu18ODg3TPKPXQb5EYEAZT9p+z32dVlfX7/KQKBgQClei+1YNuH/lG2m34DsNx0AaBh3WmvyW0jpELvQpUZ6PMvj8hiE9/SBs/PwHMW6etgzVCRGflOfBu/Kasb/L+BWIlMPnsPoz5X9nzFGLfmV/iu1V9Nt1uw9DfVVHpBEYVkbdlAFD2ak6hFjlX7p7GWjl+8/7muSWRa11MQkNV2xQKBgC1Fq/L0+pcNSNNKd323mKjZaGClN7Pz4FzivT2sM9qYjZsyhoX/N5rVPRqK1w3Sovq9sUaPJGAHkluBwcULYT0/wa3EAsGfOkwrHNh1bBqO39bfvhuoXuDhmxg5AkyXyuvz130SoCc9WXw+iWIA1sEy2xcnrqrzPCWsk39zCCa9" ], - "keyUse": [ - "SIG" - ], + "keyUse": ["SIG"], "certificate": [ "MIICoTCCAYkCBgGIIHh1fTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAljdm1hbmFnZXIwHhcNMjMwNTE1MTczMDQ1WhcNMzMwNTE1MTczMjI1WjAUMRIwEAYDVQQDDAljdm1hbmFnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyfGoEYao4SjJDQOreJZJ/4f7sL/FuH9E5qQXb6gR8Z6ZZWOx+sNcFRmGb3BY9Cdb87DyqKgbQf//U2/V2vLN9Jwubj2W4Z/FuJ5RtE0Nv469wNN/qL/yvVP6YIRb+9QOifs81Ok5ySs/ytFRxu9NKpfQcaj3B4ytiWcFwhnFjOR+Vg2YGtuPjoWnvCuYbVPOh4QhUG+Kr+NgFF2cKgkTaYtsKde6kkSudB66FF7N/Dq4IwBKNGRlv4caTWYZCvKQTf9Td54ZcuVUDQb4V7fhKhdK1MK4Q1gVv/NcyySMLYvcSCpdq248236k1m4nIXPyMoS1uCQEoYwt4OSbk02vVAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD+vpYJuJ9ZG+nXcttflEs+FvUg4sFvQZVxLwHxusLKb095K9/UTbOi13bp/XyB/04SGZDZkb/RRjCXfLGbwAhlLvta83Y7D9egmYL4qT+4aQrctOdjX7qIjec8nklWSm/E5zAxF6qZ4pB8msK1X1d9vWJPtTY+y4uWWE/SO01acRPInazEtkwAVYaquZqZaP0d9/G7+a2DZ+0OMInaPBNF/Vw2PM1hItnJG8HzrqUMm5Y8zH76OpTBvZ6OxzhX4k1Z+mGnD3qv97vgfk65fpWVTjzymIyjPuVMOYXSuC4GWhQJQHlfoxATE3Rqs78Spa+y/4lkNWGmqgFcHVHoS6jw=" ], - "priority": [ - "100" - ] + "priority": ["100"] } }, { @@ -6730,15 +6234,9 @@ "providerId": "aes-generated", "subComponents": {}, "config": { - "kid": [ - "e3118a62-6142-4a6d-8676-77b02f80bc94" - ], - "secret": [ - "5XzP4ih0trhxh8R-lDT1gA" - ], - "priority": [ - "100" - ] + "kid": ["e3118a62-6142-4a6d-8676-77b02f80bc94"], + "secret": ["5XzP4ih0trhxh8R-lDT1gA"], + "priority": ["100"] } }, { @@ -6747,18 +6245,10 @@ "providerId": "hmac-generated", "subComponents": {}, "config": { - "kid": [ - "7bd6a6e5-2dc7-4274-a1a5-a5983aa82444" - ], - "secret": [ - "9rFpOZF24rMuJGtf2eBLOYsPg73OJYYWMZn-MJsLSD06G6BaBeVx8q8i_kG_07qx5Dd2W33-Yst36qzkXOFP_Q" - ], - "priority": [ - "100" - ], - "algorithm": [ - "HS256" - ] + "kid": ["7bd6a6e5-2dc7-4274-a1a5-a5983aa82444"], + "secret": ["9rFpOZF24rMuJGtf2eBLOYsPg73OJYYWMZn-MJsLSD06G6BaBeVx8q8i_kG_07qx5Dd2W33-Yst36qzkXOFP_Q"], + "priority": ["100"], + "algorithm": ["HS256"] } }, { @@ -6770,18 +6260,12 @@ "privateKey": [ "MIIEpAIBAAKCAQEA0MQIbGxBHMkPp/yI6dSj3/SWWsyQrGe+ftdg8CCUMrlFRAq34wamnbP5k0GozWqAY6paTPrPGjcuXQ0EaMwL5OKdFaFWGXwLLjrmTicCXIEyb8lo3Ty8/izPxVlHLs+iZtZm5uP9xfL7Nw6Ja9gXhQStHfZPnt7Q31Ht4GfN08FrKAoGTsy+EyLv4o/4LoSUwRyuyvNpva3fuwTUoZPP7Y3Mmo/Ijk3L1R7cXelxNkh5ApN6whzhcec3yPQrveBed39mZ0WXyslSvVHCDQ5uvGj5fk45tRCXsOVTz01EOmAbm2W6/OOs8Ixw2Ll3l33+5U+M8uL+neEKg1aqDUew7wIDAQABAoIBACS4Bh9D3yP3/Uf3tAEkxHoUpAluZ5fbW3cl3Mf/gvF1AsjX9cX5mn6sdB5BczZGIDTndqCJkLm0sPPu4TKpiQIGFckDKoiq97B27aEbXV/13XAqBca78yXlrdmxPULvhEoANfMwcKdLeIITjXopdOGRk/1sIE76M9TDrUpGF77BufUrQUcg7DCDBOVjGTt83RMQpXVxobEu/PIYwyhUH6tOgdVl9dvcCfaShQUNlW8kYYNlMYW0Giude9Aeu8pZAUM6UXTUtkyyFdarerhWFVSTT0x1u7EbqhdeqANmDbkpQ/sNCBCF4qwj2ff7ZN9M/oqf6wyoHzOV0RTaHJtpawECgYEA8h7beSfOZ0q/QVYymfGGSEnl99twbX+y0IrrfaudX0410kXGrxRrplxq9UfOfnVCl/LmOqr36Ne1lqDiGPRQWJfNp74NIMzs8z0pLLZJYfhd2yiUh4wtpMh+dV8Hrnm6lLvNhT9QR7bnm0OhESv3N0MgMHsch7AxhV0l21cu9YECgYEA3Luw3L1opSXRHgyeyAFiDD1YD3rTM0HaJKO/DjmmC10AlVmnYYZ0rvlvnrIE82d1kxG0Std691ILnvHHw9Vh1mLprGFCszwgJGicNy2Tr/DeXuaXmifVtUFxNaxYTeFcPCJPL2BjmQ9Naxhin/g4w5maK5LduGJTUR8aDjd2Pm8CgYBMGeTT/O4ES1s39xbqih6x5ABTWnbJBAU5RSDlnCZXyWZjVCkx6JI5dPztYYeG+eZXijJRKGHJnttln+XRACGs5vHuEm9f6uljPssNUbJZB87ATs34mNfT3mzZCWiJr5s0mp7rjc327Id5ptUeZ5pJlWCtvFRoVboK+A8pFQsegQKBgQDJHF0NEanJZkY8iaUVd2Uc37tfBzpsZiBZ57NIQ7AMhGTmrnO5gKbJUUyom2u1VVsjbysEUYWg1ujtnT60J7NngGGFBGygHzTt1z4Va/o2gFAqyQ/xjT/CUGjUTT17X8wIof3hnYHBT9bqr6IUPDWDyWxVLQ/EUhm1PJAhydh7EwKBgQDNgKsBjTrTpDtcewfYarAlyLnmsJ1Fd7t4ICpKl0zwOcBacN+IqHwKZ/CTnYbpuwbURm6B0uQe6ApJ49RRFIa9Zdi9hQ4MkGEvDmAYRxuzqYPm65ssYVwcPPVYNpo8TfY3ulCPhR0zKKuZS968SKUlVWxhlqovZslFZD01OAYRrQ==" ], - "keyUse": [ - "ENC" - ], + "keyUse": ["ENC"], "certificate": [ "MIICoTCCAYkCBgGIIHh17TANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAljdm1hbmFnZXIwHhcNMjMwNTE1MTczMDQ1WhcNMzMwNTE1MTczMjI1WjAUMRIwEAYDVQQDDAljdm1hbmFnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQxAhsbEEcyQ+n/Ijp1KPf9JZazJCsZ75+12DwIJQyuUVECrfjBqads/mTQajNaoBjqlpM+s8aNy5dDQRozAvk4p0VoVYZfAsuOuZOJwJcgTJvyWjdPLz+LM/FWUcuz6Jm1mbm4/3F8vs3Dolr2BeFBK0d9k+e3tDfUe3gZ83TwWsoCgZOzL4TIu/ij/guhJTBHK7K82m9rd+7BNShk8/tjcyaj8iOTcvVHtxd6XE2SHkCk3rCHOFx5zfI9Cu94F53f2ZnRZfKyVK9UcINDm68aPl+Tjm1EJew5VPPTUQ6YBubZbr846zwjHDYuXeXff7lT4zy4v6d4QqDVqoNR7DvAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGGOT5zqUXa3vj683lB77y81VszT/raGg/GOMxOuL+9jDRnqKukY4keqT+O0q8xHn4SxQ2r0P+1dQ4WOUL91Nmi5lAC31O411qgbRvNF2WjYiqwjYr4FxJxiCruCx0fnDjl/eCfQeojXt0jmOwkRdSeZW3oyxwSv1g2p4jVe5ICczDLAmCgF+3SCfudSMzdf533s9FiB2rYeslePYr9+/ukWAN9eZH7Gz7c6qEEpYmfs9IgK0MbYwmgjNIPvSu1R3Rg5O9Zbi3pC0NcLy5hTiFB2M68fsM/ZRz4xsh2K5bLDZdiqbZCgTbrw10yoZZ8YhqKcIYVtyizzs9zAqqNqBLk=" ], - "priority": [ - "100" - ], - "algorithm": [ - "RSA-OAEP" - ] + "priority": ["100"], + "algorithm": ["RSA-OAEP"] } } ] @@ -7497,4 +6981,4 @@ "policies": [] } } -] \ No newline at end of file +] diff --git a/resources/kubernetes/cv-manager-api.yaml b/resources/kubernetes/cv-manager-api.yaml index e0c99eb02..d3b851ec5 100644 --- a/resources/kubernetes/cv-manager-api.yaml +++ b/resources/kubernetes/cv-manager-api.yaml @@ -156,8 +156,6 @@ spec: secretKeyRef: name: some_email_secret_password key: some_email_secret_key - - name: CSM_EMAILS_TO_SEND_TO - value: "" - name: CSM_TARGET_SMTP_SERVER_ADDRESS value: "" - name: CSM_TARGET_SMTP_SERVER_PORT diff --git a/resources/kubernetes/cv-manager-postgres.yaml b/resources/kubernetes/cv-manager-postgres.yaml index 2a8904c47..9992340bf 100644 --- a/resources/kubernetes/cv-manager-postgres.yaml +++ b/resources/kubernetes/cv-manager-postgres.yaml @@ -351,7 +351,6 @@ data: first_name character varying(128) NOT NULL, last_name character varying(128) NOT NULL, super_user bit(1) NOT NULL, - receive_error_emails bit(1) NOT NULL, CONSTRAINT users_pkey PRIMARY KEY (user_id), CONSTRAINT users_email UNIQUE (email) ); diff --git a/resources/kubernetes/firmware-manager.yaml b/resources/kubernetes/firmware-manager.yaml index d72462387..9aaec97b2 100644 --- a/resources/kubernetes/firmware-manager.yaml +++ b/resources/kubernetes/firmware-manager.yaml @@ -84,8 +84,6 @@ spec: value: "" - name: LOGGING_LEVEL value: "INFO" - - name: FW_EMAIL_RECIPIENTS - value: "" - name: SMTP_SERVER_IP value: "" - name: SMTP_EMAIL diff --git a/resources/kubernetes/obu-ota-server.yaml b/resources/kubernetes/obu-ota-server.yaml new file mode 100644 index 000000000..92727c8db --- /dev/null +++ b/resources/kubernetes/obu-ota-server.yaml @@ -0,0 +1,184 @@ +# K8s ManagedCertificate for using SSL/TLS with a domain +# Domain and DNS forwarding to the Ingress endpoint must be configured +apiVersion: networking.gke.io/v1 +kind: ManagedCertificate +metadata: + name: obu-ota-server-managed-cert +spec: + domains: + - your.domain.com +--- +# K8s FrontendConfig for applying SSL certificate to Ingress +# Requires 'obu-ota-server-ssl-policy' SSL policy to exist +apiVersion: networking.gke.io/v1beta1 +kind: FrontendConfig +metadata: + name: obu-ota-server-frontend + labels: + app: obu-ota-server +spec: + redirectToHttps: + enabled: true + sslPolicy: obu-ota-server-ssl-policy +--- +# NodePort to expose CV Manager web application +apiVersion: v1 +kind: Service +metadata: + labels: + app: obu-ota-server + name: obu-ota-server-service-internal +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: obu-ota-server + type: NodePort +--- +# External HTTP/HTTPS Ingress to internal NodePort +# Requires 'cv-manager-ip' as a global static external IP to be reserved +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: obu-ota-server-ingress + labels: + app: obu-ota-server + annotations: + kubernetes.io/ingress.regional-static-ip-name: 'cv-manager-ip' + networking.gke.io/managed-certificates: 'obu-ota-server-managed-cert' + networking.gke.io/v1beta1.FrontendConfig: 'obu-ota-server-frontend' +spec: + defaultBackend: + service: + name: obu-ota-server-service-internal + port: + number: 80 + + ingressClassName: nginx + rules: + - host: foo.bar.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: http-svc + port: + number: 80 +--- +# Persistent Volume for Downloaded fmwr +apiVersion: v1 +kind: PersistentVolume +metadata: + name: obu-ota-server-volume +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + storageClassName: standard-rwo + gcePersistentDisk: + pdName: obu-ota-server-disk + fsType: ext4 +--- +# Persistent Volume Claim for Downloaded fmwr +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: obu-ota-server-claim +spec: + storageClassName: standard-rwo + volumeName: obu-ota-server-volume + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +--- +apiVersion: 'apps/v1' +kind: 'Deployment' +metadata: + name: obu-ota-server +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: obu-ota-server + template: + metadata: + labels: + app: obu-ota-server + spec: + containers: + - name: obu-ota-server + imagePullPolicy: Always + image: 'iss-health-check-image' + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 1Gi + cpu: 1 + tty: true + stdin: true + ports: + - containerPort: 8085 + env: + - name: SERVER_HOST + value: '' + - name: LOGGING_LEVEL + value: '' + - name: BLOB_STORAGE_PROVIDER + value: '' + - name: GCP_PROJECT + value: '' + - name: BLOB_STORAGE_BUCKET + value: '' + - name: BLOB_STORAGE_PATH + value: '' + - name: GOOGLE_APPLICATION_CREDENTIALS + value: '' + - name: OTA_USERNAME + valueFrom: + secretKeyRef: + name: some-api-username-name + key: some-api-username-key + - name: OTA_PASSWORD + valueFrom: + secretKeyRef: + name: some-api-password-name + key: some-api-password-key + - name: MAX_COUNT + value: '' + - name: PG_DB_HOST + value: '' + - name: PG_DB_NAME + value: '' + - name: PG_DB_USER + valueFrom: + secretKeyRef: + name: some-postgres-secret-user + key: some-postgres-secret-key + - name: PG_DB_PASS + valueFrom: + secretKeyRef: + name: some-postgres-secret-password + key: some-postgres-secret-key + volumeMounts: + - name: cv-manager-service-key + mountPath: /home/secret + - name: firmware-folder + mountPath: '/firmwares/' + volumes: + - name: cv-manager-service-key + secret: + secretName: cv-manager-service-key + - name: firmware-folder + persistentVolumeClaim: + claimName: obu-ota-server-claim diff --git a/resources/mongo_scripts/create_indexes.js b/resources/mongo_scripts/create_indexes.js new file mode 100644 index 000000000..dbb07dc12 --- /dev/null +++ b/resources/mongo_scripts/create_indexes.js @@ -0,0 +1,197 @@ +// Create indexes on all collections + +/* +This script is responsible for initializing the replica set, creating collections, adding indexes and TTLs +*/ +console.log('Running create_indexes.js') + +const cv_manager_db = process.env.MONGO_DB_NAME +const cv_manager_user = process.env.MONGO_CV_MANAGER_DB_USER +const cv_manager_pass = process.env.MONGO_CV_MANAGER_DB_PASS + +const ttlInDays = process.env.MONGO_COLLECTION_TTL // TTL in days +const expire_seconds = ttlInDays * 24 * 60 * 60 +const retry_milliseconds = 5000 + +console.log('CV MANAGER DB Name: ' + cv_manager_db) + +try { + console.log('Initializing replica set...') + + var config = { + _id: 'rs0', + version: 1, + members: [ + { + _id: 0, + host: 'mongo:27017', + priority: 2, + }, + ], + } + rs.initiate(config, { force: true }) + rs.status() +} catch (e) { + rs.status().ok +} + +// name -> collection name +// ttlField -> field to perform ttl on +// timeField -> field to index for time queries + +const collections = [ + { name: 'OdeBsmJson', ttlField: 'recordGeneratedAt', timeField: 'metadata.odeReceivedAt' }, + { name: 'OdeRawEncodedBSMJson', ttlField: 'recordGeneratedAt', timeField: 'none' }, + + { name: 'OdeMapJson', ttlField: 'recordGeneratedAt', timeField: 'metadata.odeReceivedAt' }, + { name: 'OdeRawEncodedMAPJson', ttlField: 'recordGeneratedAt', timeField: 'none' }, + + { name: 'OdeSpatJson', ttlField: 'recordGeneratedAt', timeField: 'metadata.odeReceivedAt' }, + { name: 'OdeRawEncodedSPATJson', ttlField: 'recordGeneratedAt', timeField: 'none' }, + + { name: 'OdeTimJson', ttlField: 'recordGeneratedAt', timeField: 'metadata.odeReceivedAt' }, + { name: 'OdeRawEncodedTIMJson', ttlField: 'recordGeneratedAt', timeField: 'none' }, + + { name: 'OdePsmJson', ttlField: 'recordGeneratedAt', timeField: 'metadata.odeReceivedAt' }, + { name: 'OdeRawEncodedPsmJson', ttlField: 'recordGeneratedAt', timeField: 'none' }, +] + +// Function to check if the replica set is ready +function isReplicaSetReady() { + let status + try { + status = rs.status() + } catch (error) { + console.error('Error getting replica set status: ' + error) + return false + } + + // Check if the replica set has a primary + if (!status.hasOwnProperty('myState') || status.myState !== 1) { + console.log('Replica set is not ready yet') + return false + } + + console.log('Replica set is ready') + return true +} + +try { + // Wait for the replica set to be ready + while (!isReplicaSetReady()) { + sleep(retry_milliseconds) + } + sleep(retry_milliseconds) + // creates another user + console.log('Creating CV MANAGER user...') + admin = db.getSiblingDB('admin') + // Check if user already exists + var user = admin.getUser(cv_manager_user) + if (user == null) { + admin.createUser({ + user: cv_manager_user, + pwd: cv_manager_pass, + roles: [ + { role: 'readWrite', db: cv_manager_db }, + { role: 'readWrite', db: 'admin' }, + ], + }) + } else { + console.log('User "' + cv_manager_user + '" already exists.') + } +} catch (error) { + print('Error connecting to the MongoDB instance: ' + error) +} + +// Wait for the collections to exist in mongo before trying to create indexes on them +let missing_collection_count +const db = db.getSiblingDB(cv_manager_db) +do { + try { + missing_collection_count = 0 + const collection_names = db.getCollectionNames() + for (collection of collections) { + console.log('Creating Indexes for Collection' + collection['name']) + // Create Collection if It doesn't exist + let created = false + if (!collection_names.includes(collection.name)) { + created = createCollection(collection) + // created = true; + } else { + created = true + } + + if (created) { + if (collection.hasOwnProperty('ttlField') && collection.ttlField !== 'none') { + createTTLIndex(collection) + } + } else { + missing_collection_count++ + console.log('Collection ' + collection.name + ' does not exist yet') + } + } + if (missing_collection_count > 0) { + print( + 'Waiting on ' + + missing_collection_count + + ' collections to be created...will try again in ' + + retry_milliseconds + + ' ms' + ) + sleep(retry_milliseconds) + } + } catch (err) { + console.log('Error while setting up TTL indexes in collections') + console.log(rs.status()) + console.error(err) + sleep(retry_milliseconds) + } +} while (missing_collection_count > 0) + +console.log('Finished Creating All TTL indexes') + +function createCollection(collection) { + try { + db.createCollection(collection.name) + return true + } catch (err) { + console.log('Unable to Create Collection: ' + collection.name) + console.log(err) + return false + } +} + +// Create TTL Indexes +function createTTLIndex(collection) { + if (ttlIndexExists(collection)) { + console.log('TTL index already exists for ' + collection.name) + return + } + + const collection_name = collection.name + const timeField = collection.ttlField + + console.log('Creating TTL index for ' + collection_name + ' to remove documents after ' + expire_seconds + ' seconds') + + try { + var index_json = {} + index_json[timeField] = 1 + db[collection_name].createIndex(index_json, { expireAfterSeconds: expire_seconds }) + console.log('Created TTL index for ' + collection_name + ' using the field: ' + timeField + ' as the timestamp') + } catch (err) { + var pattern_json = {} + pattern_json[timeField] = 1 + db.runCommand({ + collMod: collection_name, + index: { + keyPattern: pattern_json, + expireAfterSeconds: expire_seconds, + }, + }) + console.log('Updated TTL index for ' + collection_name + ' using the field: ' + timeField + ' as the timestamp') + } +} + +function ttlIndexExists(collection) { + return db[collection.name].getIndexes().find((idx) => idx.hasOwnProperty('expireAfterSeconds')) !== undefined +} diff --git a/resources/mongo_scripts/insert_data.js b/resources/mongo_scripts/insert_data.js new file mode 100644 index 000000000..ec4ab10af --- /dev/null +++ b/resources/mongo_scripts/insert_data.js @@ -0,0 +1,212 @@ +const cv_manager_user = process.env.MONGO_ADMIN_DB_USER +const cv_manager_pass = process.env.MONGO_ADMIN_DB_PASS + +db = connect('mongodb://' + cv_manager_user + ':' + cv_manager_pass + '@mongo:27017/admin') +if (db === null) { + print('Error connecting to the MongoDB instance') +} else { + print('Successfully connected to the MongDB instance') +} + +var currentDate = new Date() +var currentDateIso = currentDate.toISOString().slice(0, -5) + +var datePreviousHour = new Date() +datePreviousHour.setHours(currentDate.getHours() - 1) +var datePreviousHourIso = datePreviousHour.toISOString().slice(0, -5) + +ConflictMonitor = db.getSiblingDB('ConflictMonitor') + +// Insert CVCounts documents +ConflictMonitor.CVCounts.insertMany([ + { + messageType: 'Map', + rsuIp: '10.0.0.180', + timestamp: new Date(currentDateIso.slice(0, -5) + '00:00Z'), + count: 153, + }, + { + messageType: 'Map', + rsuIp: '10.0.0.78', + timestamp: new Date(currentDateIso.slice(0, -5) + '00:00Z'), + count: 12, + }, + { + messageType: 'SPaT', + rsuIp: '10.0.0.180', + timestamp: new Date(currentDateIso.slice(0, -5) + '00:00Z'), + count: 24, + }, + { + messageType: 'SPaT', + rsuIp: '10.0.0.78', + timestamp: new Date(currentDateIso.slice(0, -5) + '00:00Z'), + count: 15, + }, + { + messageType: 'SPaT', + rsuIp: '10.0.0.180', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '00:00Z'), + count: 17, + }, + { + messageType: 'SPaT', + rsuIp: '10.0.0.78', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '00:00Z'), + count: 12, + }, + { + messageType: 'Map', + rsuIp: '10.0.0.180', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '00:00Z'), + count: 145, + }, + { + messageType: 'Map', + rsuIp: '10.0.0.78', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '00:00Z'), + count: 10, + }, +]) + +// insert V2XGeoJson documents +ConflictMonitor.V2XGeoJson.insertMany([ + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-104.997663, 39.708309], + }, + properties: { + id: '10.0.0.180', + timestamp: new Date(currentDateIso.slice(0, -5) + '15:00Z'), + msg_type: 'Bsm', + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-105.014169, 39.740033], + }, + properties: { + id: '10.0.0.180', + timestamp: new Date(currentDateIso.slice(0, -5) + '00:00Z'), + msg_type: 'Bsm', + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-104.990622, 39.7718], + }, + properties: { + id: '10.0.0.180', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '45:00Z'), + msg_type: 'Bsm', + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-104.982867, 39.817387], + }, + properties: { + id: '10.0.0.78', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '30:00Z'), + msg_type: 'Bsm', + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-104.987132, 39.856097], + }, + properties: { + id: '10.0.0.78', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '15:00Z'), + msg_type: 'Bsm', + }, + }, + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [-104.98752, 39.882289], + }, + properties: { + id: '10.0.0.78', + timestamp: new Date(datePreviousHourIso.slice(0, -5) + '00:00Z'), + msg_type: 'Bsm', + }, + }, +]) + +// insert OdeSsmJson document +ConflictMonitor.OdeSsmJson.insertOne({ + metadata: { + logFileName: '', + recordType: 'ssmTx', + securityResultCode: 'success', + receivedMessageDetails: { rxSource: 'NA' }, + payloadType: 'us.dot.its.jpo.ode.model.OdeSsmPayload', + serialId: { + streamId: 'daccfc21-e356-4481-be37-5963a056bfc0', + bundleSize: 1, + bundleId: 0, + recordId: 0, + serialNumber: 0, + }, + odeReceivedAt: currentDate, + schemaVersion: 6, + maxDurationTime: 0, + recordGeneratedAt: '', + recordGeneratedBy: 'RSU', + sanitized: false, + odePacketID: '', + odeTimStartDateTime: '', + originIp: '10.0.0.78', + ssmSource: 'RSU', + }, + payload: { + data: { + second: 0, + status: { + signalStatus: [ + { + sequenceNumber: 0, + id: { + id: 12110, + }, + sigStatus: { + signalStatusPackage: [ + { + requester: { + id: { + stationID: 2366845094, + }, + request: 3, + sequenceNumber: 0, + typeData: { + role: 'publicTransport', + }, + }, + inboundOn: { + lane: 2, + }, + status: 'granted', + }, + ], + }, + }, + ], + }, + }, + dataType: 'us.dot.its.jpo.ode.plugin.j2735.J2735SSM', + }, +}) + +print('Successfully inserted sample data') diff --git a/resources/mongo_scripts/setup_mongo.sh b/resources/mongo_scripts/setup_mongo.sh new file mode 100644 index 000000000..85bc67caa --- /dev/null +++ b/resources/mongo_scripts/setup_mongo.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +until mongosh --host mongo:27017 --eval 'quit(db.runCommand({ ping: 1 }).ok ? 0 : 2)' &>/dev/null; do + sleep 1 +done + +echo "MongoDB is up and running!" + +cd / + +mongosh -u $MONGO_ADMIN_DB_USER -p $MONGO_ADMIN_DB_PASS --authenticationDatabase admin --host mongo:27017 /create_indexes.js + +if [ "$INSERT_SAMPLE_DATA" = "true" ]; then + echo "Inserting sample data" + mongosh -u $MONGO_ADMIN_DB_USER -p $MONGO_ADMIN_DB_PASS --authenticationDatabase admin --host mongo:27017 /insert_data.js +fi \ No newline at end of file diff --git a/resources/ota/firmwares/.gitignore b/resources/ota/firmwares/.gitignore new file mode 100644 index 000000000..df6ae8cdc --- /dev/null +++ b/resources/ota/firmwares/.gitignore @@ -0,0 +1 @@ +*.tar.sig diff --git a/resources/ota/firmwares/README.md b/resources/ota/firmwares/README.md new file mode 100644 index 000000000..6414fcba1 --- /dev/null +++ b/resources/ota/firmwares/README.md @@ -0,0 +1,3 @@ +# OTA Firmwares + +This folder is used as an attached volume to the OBU OTA server and stores the OBU firmware files either stored locally or downloaded from GCP. diff --git a/resources/ota/nginx/Dockerfile b/resources/ota/nginx/Dockerfile new file mode 100644 index 000000000..65aba528e --- /dev/null +++ b/resources/ota/nginx/Dockerfile @@ -0,0 +1,9 @@ +FROM nginx:1.25.4-alpine + +RUN apk update && apk add \ + apache2-utils \ + jq \ + openssl \ + bash + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/resources/ota/nginx/gen_dhparam.sh b/resources/ota/nginx/gen_dhparam.sh new file mode 100755 index 000000000..824c4cd2b --- /dev/null +++ b/resources/ota/nginx/gen_dhparam.sh @@ -0,0 +1 @@ +openssl dhparam -out /etc/nginx/dhparam.pem 2048 \ No newline at end of file diff --git a/resources/ota/nginx/nginx-plain.conf b/resources/ota/nginx/nginx-plain.conf new file mode 100644 index 000000000..b8d9f0092 --- /dev/null +++ b/resources/ota/nginx/nginx-plain.conf @@ -0,0 +1,50 @@ +events { + worker_connections 768; +} + +http { + + upstream api { + server jpo_ota_backend:8085; + } + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + + server { + listen 80; + server_name $SERVER_HOST; + + client_max_body_size 300M; + + location ~ /\.ht { + deny all; + } + + # location ^~ /.well-known/acme-challenge { + # default_type text/plain; + # root /var/www/letsencrypt; + # } + + location / { + proxy_pass http://api/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access_manifest.log; + } + } +} \ No newline at end of file diff --git a/resources/ota/nginx/nginx-ssl.conf b/resources/ota/nginx/nginx-ssl.conf new file mode 100644 index 000000000..976b0fb7b --- /dev/null +++ b/resources/ota/nginx/nginx-ssl.conf @@ -0,0 +1,89 @@ +events { + worker_connections 768; +} + +http { + + upstream api { + server jpo_ota_backend:8085; + } + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + ## + # SSL Settings + ## + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + ssl_dhparam "/etc/nginx/dhparam.pem"; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ARIA256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ARIA128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256; + add_header Strict-Transport-Security "max-age=31557600; includeSubDomains"; + ssl_stapling on; + ssl_stapling_verify on; + + ## + # General Logging Settings + ## + + access_log /var/log/nginx/access.log; + + + # HTTP forwards everything to HTTPS + # + server { + listen 80; + + server_name $SERVER_HOST; + + location / { + return 301 https://$host$request_uri; + } + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + location ~ /\.ht { + deny all; + } + } + + server { + listen 443 default_server ssl; + server_name $SERVER_HOST; + + ssl_certificate /etc/ssl/certs/ota_server.crt; + ssl_certificate_key /etc/ssl/private/ota_server.key; + + client_max_body_size 300M; + + location ~ /\.ht { + deny all; + } + + # location ^~ /.well-known/acme-challenge { + # default_type text/plain; + # root /var/www/letsencrypt; + # } + + location / { + proxy_pass http://api/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Range $http_range; + + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400; + + access_log /var/log/nginx/access.log; + } + } +} \ No newline at end of file diff --git a/resources/ota/nginx/ssl/.gitignore b/resources/ota/nginx/ssl/.gitignore new file mode 100644 index 000000000..592bac37d --- /dev/null +++ b/resources/ota/nginx/ssl/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.key \ No newline at end of file diff --git a/resources/ota/nginx/ssl/README.md b/resources/ota/nginx/ssl/README.md new file mode 100644 index 000000000..c91acd246 --- /dev/null +++ b/resources/ota/nginx/ssl/README.md @@ -0,0 +1,25 @@ +# SSL + +This folder is used as an attached volume to the NGINX service and must include the server's `.crt` and `.key` file. All `.crt` and `.key` files are not tracked through git. + +The `.crt` file must be in the following multi-line format: + +```.crt +-----BEGIN CERTIFICATE----- +EC_KEY_BASED_CERTIFICATE +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +CA_INTERMEDIATE_CERTIFICATE +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +CA_ROOT_CERTIFICATE +-----END CERTIFICATE----- +``` + +The `.key` file must be in the following multi-line format: + +```.key +-----BEGIN EC PRIVATE KEY----- +EC_PRIVATE_KEY_DATA +-----END EC PRIVATE KEY----- +``` diff --git a/resources/sql_scripts/CVManager_CreateTables.sql b/resources/sql_scripts/CVManager_CreateTables.sql index da02cf1d4..57a3c3f38 100644 --- a/resources/sql_scripts/CVManager_CreateTables.sql +++ b/resources/sql_scripts/CVManager_CreateTables.sql @@ -237,7 +237,6 @@ CREATE TABLE IF NOT EXISTS public.users first_name character varying(128) NOT NULL, last_name character varying(128) NOT NULL, super_user bit(1) NOT NULL, - receive_error_emails bit(1) NOT NULL, CONSTRAINT users_pkey PRIMARY KEY (user_id), CONSTRAINT users_email UNIQUE (email) ); @@ -253,6 +252,7 @@ CREATE TABLE IF NOT EXISTS public.organizations ( organization_id integer NOT NULL DEFAULT nextval('organizations_organization_id_seq'::regclass), name character varying(128) COLLATE pg_catalog.default NOT NULL, + email character varying(128) COLLATE pg_catalog.default, CONSTRAINT organizations_pkey PRIMARY KEY (organization_id), CONSTRAINT organizations_name UNIQUE (name) ); @@ -401,4 +401,62 @@ CREATE TABLE IF NOT EXISTS public.snmp_msgfwd_config ON DELETE NO ACTION ); +CREATE SEQUENCE public.email_type_email_type_id_seq + INCREMENT 1 + START 1 + MINVALUE 1 + MAXVALUE 2147483647 + CACHE 1; + +CREATE TABLE IF NOT EXISTS public.email_type +( + email_type_id integer NOT NULL DEFAULT nextval('email_type_email_type_id_seq'::regclass), + CONSTRAINT email_type_pkey PRIMARY KEY (email_type_id), + email_type character varying(128) COLLATE pg_catalog.default NOT NULL, + CONSTRAINT email_type_unique UNIQUE (email_type) +); + +CREATE SEQUENCE public.user_email_notification_user_email_notification_id_seq + INCREMENT 1 + START 1 + MINVALUE 1 + MAXVALUE 2147483647 + CACHE 1; + +CREATE TABLE IF NOT EXISTS public.user_email_notification +( + user_email_notification_id integer NOT NULL DEFAULT nextval('user_email_notification_user_email_notification_id_seq'::regclass), + user_id integer NOT NULL, + email_type_id integer NOT NULL, + CONSTRAINT user_email_notification_pkey PRIMARY KEY (user_email_notification_id), + CONSTRAINT fk_user_id FOREIGN KEY (user_id) + REFERENCES public.users (user_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION, + CONSTRAINT fk_email_type_id FOREIGN KEY (email_type_id) + REFERENCES public.email_type (email_type_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +); + +CREATE SEQUENCE public.obu_ota_request_id_seq + INCREMENT 1 + START 1 + MINVALUE 1 + MAXVALUE 2147483647 + CACHE 1; + +CREATE TABLE IF NOT EXISTS public.obu_ota_requests ( + request_id integer NOT NULL DEFAULT nextval('obu_ota_request_id_seq'::regclass), + obu_sn character varying(128) NOT NULL, + request_datetime timestamp NOT NULL, + origin_ip inet NOT NULL, + obu_firmware_version varchar(128) NOT NULL, + requested_firmware_version varchar(128) NOT NULL, + error_status bit(1) NOT NULL, + error_message varchar(128) NOT NULL, + manufacturer int4 NOT NULL, + CONSTRAINT fk_manufacturer FOREIGN KEY (manufacturer) REFERENCES public.manufacturers(manufacturer_id) +); + CREATE SCHEMA IF NOT EXISTS keycloak; \ No newline at end of file diff --git a/resources/sql_scripts/CVManager_SampleData.sql b/resources/sql_scripts/CVManager_SampleData.sql index 3d8af39bf..cb4238084 100644 --- a/resources/sql_scripts/CVManager_SampleData.sql +++ b/resources/sql_scripts/CVManager_SampleData.sql @@ -49,8 +49,8 @@ INSERT INTO public.rsu_organization( -- Replace user with a real gmail to test GCP OAuth2.0 support INSERT INTO public.users( - email, first_name, last_name, super_user, receive_error_emails) - VALUES ('test@gmail.com', 'Test', 'User', '1', '1'); + email, first_name, last_name, super_user) + VALUES ('test@gmail.com', 'Test', 'User', '1'); INSERT INTO public.user_organization( user_id, organization_id, role_id) @@ -69,3 +69,7 @@ INSERT INTO public.snmp_msgfwd_config( (2, 2, 2, 'BSM', '10.0.0.81', 46800, '2024/04/01T00:00:00', '2034/04/01T00:00:00', '1'), (2, 3, 1, 'MAP', '10.0.0.80', 44920, '2024/04/01T00:00:00', '2034/04/01T00:00:00', '1'), (2, 3, 2, 'SPAT', '10.0.0.80', 44910, '2024/04/01T00:00:00', '2034/04/01T00:00:00', '1'); + +INSERT INTO public.email_type( + email_type) + VALUES ('Support Requests'), ('Firmware Upgrade Failures'), ('Daily Message Counts'); \ No newline at end of file diff --git a/resources/sql_scripts/update_scripts/email_notification_update.sql b/resources/sql_scripts/update_scripts/email_notification_update.sql new file mode 100644 index 000000000..840b2ab69 --- /dev/null +++ b/resources/sql_scripts/update_scripts/email_notification_update.sql @@ -0,0 +1,18 @@ +-- Run this SQL update script if you already have a deployed CV Manager PostgreSQL database +-- This file assumes that the email_type and user_email_notification tables exist, and that the email_type +-- table has an entry for 'Support Requests'. + +DO $$ +DECLARE + error_email_type_id INT; +BEGIN + SELECT email_type_id INTO error_email_type_id FROM public.email_type WHERE email_type = 'Support Requests'; + + INSERT INTO public.user_email_notification (user_id, email_type_id) + SELECT user_id, error_email_type_id + FROM public.users + WHERE receive_error_emails = B'1'; + + ALTER TABLE public.users DROP Column receive_error_emails; + +END $$; \ No newline at end of file diff --git a/resources/sql_scripts/update_scripts/obu_ota_requests.sql b/resources/sql_scripts/update_scripts/obu_ota_requests.sql new file mode 100644 index 000000000..bff2b4215 --- /dev/null +++ b/resources/sql_scripts/update_scripts/obu_ota_requests.sql @@ -0,0 +1,20 @@ +CREATE SEQUENCE public.obu_ota_request_id_seq + INCREMENT 1 + START 1 + MINVALUE 1 + MAXVALUE 2147483647 + CACHE 1; + + +CREATE TABLE IF NOT EXISTS public.obu_ota_requests ( + request_id integer NOT NULL DEFAULT nextval('obu_ota_request_id_seq'::regclass), + obu_sn character varying(128) NOT NULL, + request_datetime timestamp NOT NULL, + origin_ip inet NOT NULL, + obu_firmware_version varchar(128) NOT NULL, + requested_firmware_version varchar(128) NOT NULL, + error_status bit(1) NOT NULL, + error_message varchar(128) NOT NULL, + manufacturer int4 NOT NULL, + CONSTRAINT fk_manufacturer FOREIGN KEY (manufacturer) REFERENCES public.manufacturers(manufacturer_id) +); \ No newline at end of file diff --git a/resources/sql_scripts/update_scripts/org_email_notification_update.sql b/resources/sql_scripts/update_scripts/org_email_notification_update.sql new file mode 100644 index 000000000..48dd607ae --- /dev/null +++ b/resources/sql_scripts/update_scripts/org_email_notification_update.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.organizations + ADD COLUMN email character varying(128) COLLATE pg_catalog.default; diff --git a/resources/sql_scripts/update_scripts/receive_error_emails_update.sql b/resources/sql_scripts/update_scripts/receive_error_emails_update.sql deleted file mode 100644 index 8a5ea5c50..000000000 --- a/resources/sql_scripts/update_scripts/receive_error_emails_update.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE public.users - ADD receive_error_emails bit(1) NOT NULL - DEFAULT B'0'; \ No newline at end of file diff --git a/sample.env b/sample.env index 3cfb926c3..1ab17337b 100644 --- a/sample.env +++ b/sample.env @@ -4,7 +4,6 @@ DOCKER_HOST_IP= WEBAPP_HOST_IP=${DOCKER_HOST_IP} # Note if using WEBAPP_DOMAIN for the docker-compose-webapp-deployment.yml file you will need to include http:// or https:// WEBAPP_DOMAIN=cvmanager.local.com -WEBAPP_CM_DOMAIN=cimms.local.com KC_HOST_IP=${DOCKER_HOST_IP} # Firmware Manager connectivity in the format 'http://endpoint:port' @@ -15,10 +14,10 @@ FIRMWARE_MANAGER_ENDPOINT=http://${DOCKER_HOST_IP}:8089 # If using docker then this value should be set to: http://${WEBAPP_HOST_IP}:3000 # If running the webapp using npm then set it to: http://localhost:3000 # Leave as * to allow all domains access -CORS_DOMAIN = * +CORS_DOMAIN=* # PostgreSQL Database connection information -# this value may need to folow with the webapp host if debugging the applications +# this value may need to follow with the webapp host if debugging the applications PG_DB_HOST=${DOCKER_HOST_IP}:5432 PG_DB_NAME=postgres PG_DB_USER=postgres @@ -28,7 +27,7 @@ PG_DB_PASS= # If connecting to PGDB over websocket: INSTANCE_CONNECTION_NAME= -# Keycloak authentication crendentials +# Keycloak authentication credentials KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD= @@ -36,7 +35,6 @@ KEYCLOAK_ADMIN_PASSWORD= KEYCLOAK_REALM=cvmanager KEYCLOAK_API_CLIENT_ID=cvmanager-api KEYCLOAK_API_CLIENT_SECRET_KEY= -KEYCLOAK_CM_API_CLIENT_SECRET_KEY= KEYCLOAK_LOGIN_THEME_NAME=sample_theme # Note if using KEYCLOAK_DOMAIN for the docker-compose-webapp-deployment.yml file you will need to include http:// or https:// KEYCLOAK_DOMAIN=cvmanager.auth.com @@ -53,8 +51,14 @@ GOOGLE_APPLICATION_CREDENTIALS='./resources/google/sample_gcp_service_account.js MONGO_DB_URI= MONGO_DB_NAME="ode" +# If running MongoDB locally with docker-compose-mongo.yml uncomment the following line +MONGO_COLLECTION_TTL=7 # days + +# If true sample data will be inserted into the MongoDB CVCounts, V2XGeoJson, and OdeSsmJson collections. +INSERT_SAMPLE_DATA=true + # Set these variables if using either "MONGODB" or "BIGQUERY" -# COUNTS_MSG_TYPES: Comma seperated list of message types. +# COUNTS_MSG_TYPES: Comma separated list of message types. # COUNTS_MSG_TYPES must be set for the counts menu to correctly populate when building an image for deployment COUNTS_MSG_TYPES='BSM,SSM,SPAT,SRM,MAP' VIEWER_MSG_TYPES='BSM' @@ -71,17 +75,18 @@ WZDX_ENDPOINT= # Contact Support Menu Email Configuration CSM_EMAIL_TO_SEND_FROM= -CSM_EMAIL_APP_USERNAME= -CSM_EMAIL_APP_PASSWORD= CSM_EMAILS_TO_SEND_TO= CSM_TARGET_SMTP_SERVER_ADDRESS= CSM_TARGET_SMTP_SERVER_PORT= +CSM_TLS_ENABLED=true +CSM_AUTH_ENABLED=true +CSM_EMAIL_APP_USERNAME= +CSM_EMAIL_APP_PASSWORD= # Email configuration for sending firmware-manager failure emails SMTP_EMAIL= SMTP_USERNAME= SMTP_PASSWORD= -FW_EMAIL_RECIPIENTS= SMTP_SERVER_IP= # Python timezone for the CV Manager (You can list pytz timezones with the command 'pytz.all_timezones') @@ -97,6 +102,12 @@ MAPBOX_INIT_ZOOM="10" GCP_PROJECT_ID = '' +# CIMMS IntersectionMap Variables +CVIZ_API_SERVER_URL=http://${DOCKER_HOST_IP}:8089 +CVIZ_API_WS_URL=ws://${DOCKER_HOST_IP}:8089 +WEBAPP_CM_DOMAIN='cvmanager.local.com' +KEYCLOAK_CM_API_CLIENT_SECRET_KEY= + # --------------------------------------------------------------------- # Count Metric Addon: @@ -110,13 +121,8 @@ SMTP_SERVER_IP = '' SMTP_USERNAME = '' SMTP_PASSWORD = '' SMTP_EMAIL = '' -# Multiple emails can be delimited by a ',' -SMTP_EMAIL_RECIPIENTS = 'test1@gmail.com,test2@gmail.com' - -# If ENABLE_EMAILER is 'False', set the following environment variables COUNT_MESSAGE_TYPES = 'bsm' -ODE_KAFKA_BROKERS = {DOCKER_HOST_IP}:9092 # EITHER "MONGODB" or "BIGQUERY" COUNT_DESTINATION_DB = 'MONGODB' @@ -132,7 +138,6 @@ KAFKA_BIGQUERY_TABLENAME = '' # Firmware Manager Addon: BLOB_STORAGE_PROVIDER=DOCKER BLOB_STORAGE_BUCKET= -GCP_PROJECT= ## Docker volume mount point for BLOB storage (if using Docker) HOST_BLOB_STORAGE_DIRECTORY=./local_blob_storage # --------------------------------------------------------------------- @@ -144,11 +149,22 @@ GEO_TTL_DURATION=90 # --------------------------------------------------------------------- # ISS Health Check Addon + +# Key Storage +## Type of key storage, options: gcp, postgres +STORAGE_TYPE=postgres + +# ISS Account Authentication ISS_API_KEY= ISS_API_KEY_NAME= ISS_PROJECT_ID= ISS_SCMS_TOKEN_REST_ENDPOINT= ISS_SCMS_VEHICLE_REST_ENDPOINT= + +## Postgres Storage (Required if STORAGE_TYPE=postgres) +### Table name to store keys +ISS_KEY_TABLE_NAME= + # --------------------------------------------------------------------- # RSU Status Addon: @@ -177,6 +193,33 @@ ZABBIX_PASSWORD= STALE_PERIOD=24 # --------------------------------------------------------------------- +# OBU OTA Server Addon: + +# Routeable host name for the server +OBU_OTA_SERVER_HOST = "localhost" + +# For users using GCP cloud storage +OBU_OTA_BLOB_STORAGE_BUCKET="" +OBU_OTA_BLOB_STORAGE_PATH="" + +# Nginx basic auth username and password +OTA_USERNAME = "admin" +OTA_PASSWORD = "admin" + +# Nginx encryption options: "plain", "ssl" +# Note that this just changes the config file attached as a volume to the Nginx container +NGINX_ENCRYPTION="plain" + +# SSL file name in path /docker/nginx/ssl/ +SERVER_CERT_FILE="ota_server.crt" +SERVER_KEY_FILE="ota_server.key" + +# Max number of succesfull firmware upgrades to keep in the database per device SN +MAX_COUNT = 10 + +# --------------------------------------------------------------------- + + # Levels are "DEBUG", "INFO", "WARNING", and "ERROR" API_LOGGING_LEVEL="INFO" FIRMWARE_MANAGER_LOGGING_LEVEL="INFO" @@ -184,5 +227,46 @@ GEO_LOGGING_LEVEL="INFO" ISS_LOGGING_LEVEL="INFO" RSU_STATUS_LOGGING_LEVEL="INFO" COUNTS_LOGGING_LEVEL="INFO" +OBU_OTA_LOGGING_LEVEL="INFO" # Supported log levels are "ALL", "DEBUG", "ERROR", "FATAL", "INFO", "OFF", "TRACE" and "WARN" -KC_LOGGING_LEVEL="INFO" \ No newline at end of file +KC_LOGGING_LEVEL="INFO" + +########################################## +# JPO ConflictMonitor Docker Environment # +# Documentation: https://github.com/usdot-jpo-ode/jpo-conflictmonitor +########################################## +DB_HOST_IP=${DOCKER_HOST_IP} +KAFKA_BROKER_IP=${DOCKER_HOST_IP} +CM_STARTUP_DELAY_SECONDS=90 + +# The Username and passwords to use for accessing mongoDB. +MONGO_INITDB_ROOT_USERNAME=root +MONGO_INITDB_ROOT_PASSWORD= +CM_MONGO_CONNECTOR_USERNAME=connector +CM_MONGO_CONNECTOR_PASSWORD= +CM_MONGO_API_USERNAME=api +CM_MONGO_API_PASSWORD= +CM_MONGO_USER_USERNAME=user +CM_MONGO_USER_PASSWORD= + +CM_DATABASE_NAME=ConflictMonitor +CM_DATABASE_STORAGE_COLLECTION_NAME=MongoStorage +CM_DATABASE_SIZE_GB=50 +CM_DATABASE_SIZE_TARGET_PERCENT=0.8 +CM_DATABASE_DELETE_THRESHOLD_PERCENT=0.9 +CM_DATABASE_MAX_TTL_RETENTION_SECONDS=5184000 # 60 days +CM_DATABASE_MIN_TTL_RETENTION_SECONDS=604800 # 7 days +CM_DATABASE_COMPACTION_TRIGGER_PERCENT=0.5 + + +############################################################## +# jpo-conflictvisualizer cvmanager api environment variables # +# Documentation: https://github.com/usdot-jpo-ode/jpo-conflictvisualizer/tree/cvmgr-cimms-integration/api +############################################################## +# Set the HOST IP of where the containers are running +KAFKA_BROKER_PORT=9092 + +# May be different from docker host ip for production deployments. Change as appropriate. +CM_SERVER_URL=http://${DOCKER_HOST_IP}:8082 + +DB_HOST_PORT=27017 diff --git a/sample_no_cm.env b/sample_no_cm.env new file mode 100644 index 000000000..b9e80951b --- /dev/null +++ b/sample_no_cm.env @@ -0,0 +1,223 @@ +# Environment variable file for the JPO CV Manager API deployment through docker-compose +DOCKER_HOST_IP= +# Note if debugging, this webapp host IP should be set to the IP of the host machine running the webapp (localhost) +WEBAPP_HOST_IP=${DOCKER_HOST_IP} +# Note if using WEBAPP_DOMAIN for the docker-compose-webapp-deployment.yml file you will need to include http:// or https:// +WEBAPP_DOMAIN=cvmanager.local.com +KC_HOST_IP=${DOCKER_HOST_IP} + +# Firmware Manager connectivity in the format 'http://endpoint:port' +FIRMWARE_MANAGER_ENDPOINT=http://${DOCKER_HOST_IP}:8089 + +# Allowed CORS domain for accessing the CV Manager API from (set to the web application hostname) +# Make sure to include http:// or https:// +# If using docker then this value should be set to: http://${WEBAPP_HOST_IP}:3000 +# If running the webapp using npm then set it to: http://localhost:3000 +# Leave as * to allow all domains access +CORS_DOMAIN=* + +# PostgreSQL Database connection information +# this value may need to follow with the webapp host if debugging the applications +PG_DB_HOST=${DOCKER_HOST_IP}:5432 +PG_DB_NAME=postgres +PG_DB_USER=postgres +# If the PG_DB_PASS variable has special characters, make sure to wrap it in single quotes +PG_DB_PASS= + +# If connecting to PGDB over websocket: +INSTANCE_CONNECTION_NAME= + +# Keycloak authentication credentials +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD= + +# Keycloak Parameters - to generate secret key use a password generator such as: https://www.avast.com/en-us/random-password-generator#pc and set the length to 32 +KEYCLOAK_REALM=cvmanager +KEYCLOAK_API_CLIENT_ID=cvmanager-api +KEYCLOAK_API_CLIENT_SECRET_KEY= +KEYCLOAK_LOGIN_THEME_NAME=sample_theme +# Note if using KEYCLOAK_DOMAIN for the docker-compose-webapp-deployment.yml file you will need to include http:// or https:// +KEYCLOAK_DOMAIN=cvmanager.auth.com + + +# GCP OAuth2.0 client ID for SSO authentication in keycloak - if not specified the google SSO will not be functional +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# If "BIGQUERY", set the location of the GCP service account key attached as a volume +GOOGLE_APPLICATION_CREDENTIALS='./resources/google/sample_gcp_service_account.json' + +# If "MONGODB", set the MongoDB variables +MONGO_DB_URI= +MONGO_DB_NAME="ode" + +# If running MongoDB locally with docker-compose-mongo.yml uncomment the following line +MONGO_COLLECTION_TTL=7 # days + +# If true sample data will be inserted into the MongoDB CVCounts, V2XGeoJson, and OdeSsmJson collections. +INSERT_SAMPLE_DATA=true + +# Set these variables if using either "MONGODB" or "BIGQUERY" +# COUNTS_MSG_TYPES: Comma separated list of message types. +# COUNTS_MSG_TYPES must be set for the counts menu to correctly populate when building an image for deployment +COUNTS_MSG_TYPES='BSM,SSM,SPAT,SRM,MAP' +VIEWER_MSG_TYPES='BSM' +GEO_DB_NAME='V2XGeoJson' +SSM_DB_NAME= +SRM_DB_NAME= + +# Specifies the maximum number of V2x messages returned from the geo_query_geo_data_mongo method before filtering occurs +MAX_GEO_QUERY_RECORDS= + +# WZDx API key and endpoint for pulling WZDx data into the CV Manager +WZDX_API_KEY= +WZDX_ENDPOINT= + +# Contact Support Menu Email Configuration +CSM_EMAIL_TO_SEND_FROM= +CSM_EMAIL_APP_USERNAME= +CSM_EMAIL_APP_PASSWORD= +CSM_EMAILS_TO_SEND_TO= +CSM_TARGET_SMTP_SERVER_ADDRESS= +CSM_TARGET_SMTP_SERVER_PORT= + +# Python timezone for the CV Manager (You can list pytz timezones with the command 'pytz.all_timezones') +TIMEZONE="US/Mountain" + +# Mapbox token for map rendering in the webapp +MAPBOX_TOKEN= +# DOT_NAME must be set for the DOT name to correctly populate when building an image for deployment +DOT_NAME="CDOT" +MAPBOX_INIT_LATITUDE="39.7392" +MAPBOX_INIT_LONGITUDE="-104.9903" +MAPBOX_INIT_ZOOM="10" + +GCP_PROJECT_ID = '' + +# --------------------------------------------------------------------- + +# Count Metric Addon: +ENABLE_EMAILER = 'True' + +# If ENABLE_EMAILER is 'True', set the following environment variables +DEPLOYMENT_TITLE = 'JPO-ODE' + +# SMTP REQUIRED VARIABLES +SMTP_SERVER_IP = '' +SMTP_USERNAME = '' +SMTP_PASSWORD = '' +SMTP_EMAIL = '' +# Multiple emails can be delimited by a ',' +SMTP_EMAIL_RECIPIENTS = 'test1@gmail.com,test2@gmail.com' + +# If ENABLE_EMAILER is 'False', set the following environment variables + +COUNT_MESSAGE_TYPES = 'bsm' +ODE_KAFKA_BROKERS = {DOCKER_HOST_IP}:9092 + +# EITHER "MONGODB" or "BIGQUERY" +COUNT_DESTINATION_DB = 'MONGODB' + +# MONGODB REQUIRED VARIABLES +INPUT_COUNTS_MONGO_COLLECTION_NAME = '' +OUTPUT_COUNTS_MONGO_COLLECTION_NAME = '' + +# BIGQUERY REQUIRED VARIABLES +KAFKA_BIGQUERY_TABLENAME = '' +# --------------------------------------------------------------------- + +# Firmware Manager Addon: +BLOB_STORAGE_PROVIDER=DOCKER +BLOB_STORAGE_BUCKET= +## Docker volume mount point for BLOB storage (if using Docker) +HOST_BLOB_STORAGE_DIRECTORY=./local_blob_storage +# --------------------------------------------------------------------- + +# Geo-spatial message query Addon: +GEO_INPUT_COLLECTIONS='OdeBsmJson,OdePsmJson' +# TTL duration in days: +GEO_TTL_DURATION=90 +# --------------------------------------------------------------------- + +# ISS Health Check Addon + +# Key Storage +## Type of key storage, options: gcp, postgres +STORAGE_TYPE=postgres + +# ISS Account Authentication +ISS_API_KEY= +ISS_API_KEY_NAME= +ISS_PROJECT_ID= +ISS_SCMS_TOKEN_REST_ENDPOINT= +ISS_SCMS_VEHICLE_REST_ENDPOINT= + +## Postgres Storage (Required if STORAGE_TYPE=postgres) +### Table name to store keys +ISS_KEY_TABLE_NAME= + +# --------------------------------------------------------------------- + +# RSU Status Addon: + +# Services that can be toggled on or off +# 'True' or 'False' are the only legal values + +# Toggles monitoring of RSU online status +RSU_PING=True + +# Fetches ping data from Zabbix - alternatively the service will ping the RSUs on its own +# Only used when RSU_PING is 'True' +ZABBIX=False + +# Fetches SNMP configuration data for all RSUs +RSU_SNMP_FETCH=True + +# Zabbix endpoint and API authentication +# Only used when ZABBIX is 'True' +ZABBIX_ENDPOINT= +ZABBIX_USER= +ZABBIX_PASSWORD= + +# Customize the period at which the purger will determine a ping log is too old and will be deleted +# Number of hours +STALE_PERIOD=24 +# --------------------------------------------------------------------- + +# OBU OTA Server Addon: + +# Routeable host name for the server +OBU_OTA_SERVER_HOST = "localhost" + +# For users using GCP cloud storage +OBU_OTA_BLOB_STORAGE_BUCKET="" +OBU_OTA_BLOB_STORAGE_PATH="" + +# Nginx basic auth username and password +OTA_USERNAME = "admin" +OTA_PASSWORD = "admin" + +# Nginx encryption options: "plain", "ssl" +# Note that this just changes the config file attached as a volume to the Nginx container +NGINX_ENCRYPTION="plain" + +# SSL file name in path /docker/nginx/ssl/ +SERVER_CERT_FILE="ota_server.crt" +SERVER_KEY_FILE="ota_server.key" + +# Max number of succesfull firmware upgrades to keep in the database per device SN +MAX_COUNT = 10 + +# --------------------------------------------------------------------- + + +# Levels are "DEBUG", "INFO", "WARNING", and "ERROR" +API_LOGGING_LEVEL="INFO" +FIRMWARE_MANAGER_LOGGING_LEVEL="INFO" +GEO_LOGGING_LEVEL="INFO" +ISS_LOGGING_LEVEL="INFO" +RSU_STATUS_LOGGING_LEVEL="INFO" +COUNTS_LOGGING_LEVEL="INFO" +OBU_OTA_LOGGING_LEVEL="INFO" +# Supported log levels are "ALL", "DEBUG", "ERROR", "FATAL", "INFO", "OFF", "TRACE" and "WARN" +KC_LOGGING_LEVEL="INFO" \ No newline at end of file diff --git a/services/Dockerfile.obu_ota_server b/services/Dockerfile.obu_ota_server new file mode 100644 index 000000000..b3afbf2bb --- /dev/null +++ b/services/Dockerfile.obu_ota_server @@ -0,0 +1,11 @@ +FROM python:3.12-alpine + +WORKDIR /home + +ADD addons/images/obu_ota_server/requirements.txt . +ADD addons/images/obu_ota_server/*.py . +ADD common/*.py ./common/ + +RUN pip install --no-cache-dir --upgrade -r requirements.txt + +CMD ["uvicorn", "obu_ota_server:app", "--host", "0.0.0.0", "--port", "8085"] \ No newline at end of file diff --git a/services/addons/images/count_metric/README.md b/services/addons/images/count_metric/README.md index 5e2375f7e..71edd42ea 100644 --- a/services/addons/images/count_metric/README.md +++ b/services/addons/images/count_metric/README.md @@ -8,6 +8,8 @@ It is important to note that the count_metric service assumes Map and TIM messag Specifically includes the following message types: ["BSM", "TIM", "Map", "SPaT", "SRM", "SSM"] +Please note that the daily emailer relies on the user_email_notification PostgreSQL table to pull in the list of users that are subscribed to receive these emails. + To run this service, the following environment variables must be set: LOGGING_LEVEL: The logging level of the deployment. Options are: 'critical', 'error', 'warning', 'info' and 'debug'. If not specified, will default to 'info'. Refer to Python's documentation for more info: [Python logging](https://docs.python.org/3/howto/logging.html). @@ -36,8 +38,6 @@ To run this service, the following environment variables must be set: SMTP_EMAIL: The origin email that the count_metric will send the email from. This is usually associated with the SMTP server authentication. -SMTP_EMAIL_RECIPIENTS: Recipient emails, delimited by ','. - ## Daily Counter (MongoDB) The daily counter is a feature that aggregates JPO-ODE mongoDB message type counts for BSM, PSM, TIM, Map, SPaT, SRM and SSM and inserts them into a new collection in mongoDB. This new collection is named "CVCounts". This new collection is useful for the CV Manager to query the message counts in a performant manner. This script runs on a cron every 24 hours. diff --git a/services/addons/images/count_metric/daily_emailer.py b/services/addons/images/count_metric/daily_emailer.py index 5f3dcb8ec..4d3fc2069 100644 --- a/services/addons/images/count_metric/daily_emailer.py +++ b/services/addons/images/count_metric/daily_emailer.py @@ -3,6 +3,7 @@ import gen_email from common.emailSender import EmailSender import common.pgquery as pgquery +from common.email_util import get_email_list from datetime import datetime, timedelta from pymongo import MongoClient @@ -41,16 +42,9 @@ def query_mongo_in_counts(rsu_dict, start_dt, end_dt, mongo_db): logging.debug(f"{type.title()} In count received for {rsu_ip}: {count}") - # If a RSU that is not in PostgreSQL has counts recorded, add it to the rsu_dict and populate zeroes - if rsu_ip not in rsu_dict: - rsu_dict[rsu_ip] = { - "primary_route": "Unknown", - "counts": {}, - } - for t in message_types: - rsu_dict[rsu_ip]["counts"][t] = {"in": 0, "out": 0} - - rsu_dict[rsu_ip]["counts"][type]["in"] = count + # If the RSU is a part of the organization, add it to the rsu_dict + if rsu_ip in rsu_dict: + rsu_dict[rsu_ip]["counts"][type]["in"] = count # Modify the rsu_dict with the specified date range's mongoDB "out" counts for each message type @@ -85,25 +79,20 @@ def query_mongo_out_counts(rsu_dict, start_dt, end_dt, mongo_db): logging.debug(f"{type.title()} Out count received for {rsu_ip}: {count}") - # If a RSU that is not in PostgreSQL has counts recorded, add it to the rsu_dict and populate zeroes - if rsu_ip not in rsu_dict: - rsu_dict[rsu_ip] = { - "primary_route": "Unknown", - "counts": {}, - } - for t in message_types: - rsu_dict[rsu_ip]["counts"][t] = {"in": 0, "out": 0} - - rsu_dict[rsu_ip]["counts"][type]["out"] = count + # If the RSU is a part of the organization, add it to the rsu_dict + if rsu_ip in rsu_dict: + rsu_dict[rsu_ip]["counts"][type]["out"] = count -def prepare_rsu_dict(): +def prepare_org_rsu_dict(): query = ( "SELECT to_jsonb(row) " "FROM (" - "SELECT ipv4_address, primary_route " - "FROM public.rsus " - "ORDER BY primary_route ASC, milepost ASC" + "SELECT o.name org_name, r.ipv4_address, r.primary_route " + "FROM public.rsu_organization ro " + "JOIN public.organizations o ON ro.organization_id = o.organization_id " + "JOIN public.rsus r ON ro.rsu_id = r.rsu_id " + "ORDER BY o.name, r.primary_route ASC, r.milepost ASC" ") as row" ) @@ -113,21 +102,30 @@ def prepare_rsu_dict(): rsu_dict = {} for row in data: row = dict(row[0]) - rsu_dict[row["ipv4_address"]] = { + # If the organization name is new to the dictionary, make a new empty object + if row["org_name"] not in rsu_dict: + rsu_dict[row["org_name"]] = {} + + rsu_dict[row["org_name"]][row["ipv4_address"]] = { "primary_route": row["primary_route"], "counts": {}, } + for type in message_types: - rsu_dict[row["ipv4_address"]]["counts"][type] = {"in": 0, "out": 0} + rsu_dict[row["org_name"]][row["ipv4_address"]]["counts"][type] = { + "in": 0, + "out": 0, + } + logging.debug(f"Created RSU dictionary: {rsu_dict}") return rsu_dict -def email_daily_counts(email_body): +def email_daily_counts(org_name, email_body): logging.info("Attempting to send the count emails...") try: - email_addresses = os.environ["SMTP_EMAIL_RECIPIENTS"].split(",") + email_addresses = get_email_list("Daily Message Counts", org_name) for email_address in email_addresses: emailSender = EmailSender( @@ -137,7 +135,7 @@ def email_daily_counts(email_body): emailSender.send( sender=os.environ["SMTP_EMAIL"], recipient=email_address, - subject=f"{str(os.environ['DEPLOYMENT_TITLE']).upper()} Counts", + subject=f"{org_name} {str(os.environ['DEPLOYMENT_TITLE'])} Counts", message=email_body, replyEmail="", username=os.environ["SMTP_USERNAME"], @@ -149,7 +147,8 @@ def email_daily_counts(email_body): def run_daily_emailer(): - rsu_dict = prepare_rsu_dict() + client = MongoClient(os.getenv("MONGO_DB_URI")) + mongo_db = client[os.getenv("MONGO_DB_NAME")] # Grab today's date and yesterday's date for a 24 hour range start_dt = (datetime.now() - timedelta(1)).replace( @@ -157,17 +156,19 @@ def run_daily_emailer(): ) end_dt = (datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0) - client = MongoClient(os.getenv("MONGO_DB_URI")) - mongo_db = client[os.getenv("MONGO_DB_NAME")] - # Populate rsu_dict with counts from mongoDB - query_mongo_in_counts(rsu_dict, start_dt, end_dt, mongo_db) - query_mongo_out_counts(rsu_dict, start_dt, end_dt, mongo_db) + # Grab the RSU dictionary for each CV Manager organization to build separate reports + org_rsu_dict = prepare_org_rsu_dict() - # Generate the email content with the populated rsu_dict - email_body = gen_email.generate_email_body( - rsu_dict, start_dt, end_dt, message_types - ) - email_daily_counts(email_body) + for org_name, rsu_dict in org_rsu_dict.items(): + # Populate rsu_dict with counts from mongoDB + query_mongo_in_counts(rsu_dict, start_dt, end_dt, mongo_db) + query_mongo_out_counts(rsu_dict, start_dt, end_dt, mongo_db) + + # Generate the email content with the populated rsu_dict + email_body = gen_email.generate_email_body( + org_name, rsu_dict, start_dt, end_dt, message_types + ) + email_daily_counts(org_name, email_body) if __name__ == "__main__": diff --git a/services/addons/images/count_metric/gen_email.py b/services/addons/images/count_metric/gen_email.py index 955647b19..5988ac3b0 100644 --- a/services/addons/images/count_metric/gen_email.py +++ b/services/addons/images/count_metric/gen_email.py @@ -77,14 +77,14 @@ def generate_count_table(rsu_dict, message_type_list): return html -def generate_email_body(rsu_dict, start_dt, end_dt, message_type_list): +def generate_email_body(org_name, rsu_dict, start_dt, end_dt, message_type_list): start = datetime.strftime(start_dt, "%Y-%m-%d 00:00:00") end = datetime.strftime(end_dt, "%Y-%m-%d 00:00:00") # DEPLOYMENT_TITLE is a contextual title for where these counts apply. ie. "GCP prod" # This is generalized to support any deployment environment html = ( - f'
This is an automated email to report yesterday's ODE message counts for J2735 messages going in and out of the ODE. "
"In counts are the number of encoded messages received by the ODE from the load balancer. "
"Out counts are the number of decoded messages that have come out of the ODE in JSON form and "
diff --git a/services/addons/images/count_metric/mongo_counter.py b/services/addons/images/count_metric/mongo_counter.py
index bb18a49ee..2d64a3ece 100644
--- a/services/addons/images/count_metric/mongo_counter.py
+++ b/services/addons/images/count_metric/mongo_counter.py
@@ -6,45 +6,58 @@
message_types = ["BSM", "TIM", "Map", "SPaT", "SRM", "SSM", "PSM"]
def write_counts(mongo_db, counts):
- output_collection = mongo_db["CVCounts"]
- output_collection.insert_many(counts)
+ try:
+ if not counts:
+ logging.info("Counts list is empty")
+ else:
+ output_collection = mongo_db["CVCounts"]
+ output_collection.insert_many(counts)
+ logging.debug(f"Inserted {len(counts)} records into CVCounts collection")
+ except Exception as e:
+ logging.error(f"Error writing counts to MongoDB: {e}")
def count_query(mongo_db, message_type, start_dt, end_dt):
collection = mongo_db[f"Ode{message_type.capitalize()}Json"]
- # Perform mongoDB aggregate query
- agg_result = collection.aggregate(
- [
- {
- "$match": {
- "recordGeneratedAt": {
- "$gte": start_dt,
- "$lt": end_dt,
+ logging.debug(f"Counting {message_type} messages in {collection.name}")
+ counts = []
+ try:
+ # Perform mongoDB aggregate query
+ agg_result = collection.aggregate(
+ [
+ {
+ "$match": {
+ "recordGeneratedAt": {
+ "$gte": start_dt,
+ "$lt": end_dt,
+ }
}
- }
- },
- {
- "$group": {
- "_id": "$metadata.originIp",
- "count": {"$sum": 1},
- }
- },
- ]
- )
+ },
+ {
+ "$group": {
+ "_id": "$metadata.originIp",
+ "count": {"$sum": 1},
+ }
+ },
+ ]
+ )
- counts = []
- for record in agg_result:
- if not record["_id"]:
- continue
- count_record = {
- "messageType": message_type,
- "rsuIp": record["_id"],
- "timestamp": start_dt,
- "count": record["count"],
- }
- counts.append(count_record)
+ for record in agg_result:
+ if not record["_id"]:
+ continue
+ count_record = {
+ "messageType": message_type,
+ "rsuIp": record["_id"],
+ "timestamp": start_dt,
+ "count": record["count"],
+ }
+ counts.append(count_record)
- return counts
+ logging.debug(f"Found {len(counts)} {message_type} records")
+ return counts
+ except Exception as e:
+ logging.error(f"Error counting {message_type} messages: {e}")
+ return counts
def run_mongo_counter(mongo_db):
diff --git a/services/addons/images/count_metric/sample.env b/services/addons/images/count_metric/sample.env
index f266b3fd4..4927c0ff9 100644
--- a/services/addons/images/count_metric/sample.env
+++ b/services/addons/images/count_metric/sample.env
@@ -21,8 +21,6 @@ SMTP_SERVER_IP = ''
SMTP_USERNAME = ''
SMTP_PASSWORD = ''
SMTP_EMAIL = ''
-# Multiple emails can be delimited by a ','
-SMTP_EMAIL_RECIPIENTS = 'test1@gmail.com,test2@gmail.com'
# ---------------------------------------------------------------------
# If ENABLE_EMAILER is 'False', set the following environment variables
diff --git a/services/addons/images/firmware_manager/README.md b/services/addons/images/firmware_manager/README.md
index 8842cfd08..ee6721dd0 100644
--- a/services/addons/images/firmware_manager/README.md
+++ b/services/addons/images/firmware_manager/README.md
@@ -57,12 +57,12 @@ The firmware_manager microservice expects the following environment variables to
- LOGGING_LEVEL (optional, defaults to 'info')
The Firmware Manager is capable of sending an email to the support team in the event that an online RSU experiences a firmware upgrade failure.
+This functionality relies on the user_email_notification PostgreSQL table to pull in the list of users that are subscribed to receive these emails.
To do so the following environment variables must be set:
- SMTP_EMAIL - Email to send from.
- SMTP_USERNAME - SMTP username for SMTP_EMAIL.
- SMTP_PASSWORD - SMTP password for SMTP_EMAIL.
-- FW_EMAIL_RECIPIENTS - Comma-separated list of emails to send failure notifications to.
- SMTP_SERVER_IP - Address of the SMTP server.
GCP Required environment variables:
@@ -71,6 +71,7 @@ GCP Required environment variables:
- GOOGLE_APPLICATION_CREDENTIALS - Service account location. Recommended to attach as a volume.
Docker volume required environment variables:
+
- HOST_BLOB_STORAGE_DIRECTORY - Directory mounted as a docker volume for firmware storage. A relative path can be specified here.
## Vendor Specific Requirements
diff --git a/services/addons/images/firmware_manager/commsignia_upgrader.py b/services/addons/images/firmware_manager/commsignia_upgrader.py
index e3b572a4b..f702b2bc5 100644
--- a/services/addons/images/firmware_manager/commsignia_upgrader.py
+++ b/services/addons/images/firmware_manager/commsignia_upgrader.py
@@ -22,7 +22,7 @@ def upgrade(self):
self.download_blob()
# Make connection with the target device
- logging.info("Making SSH connection with the device...")
+ logging.info("Making SSH connection with " + self.rsu_ip + "...")
ssh = SSHClient()
ssh.set_missing_host_key_policy(WarningPolicy)
ssh.connect(
@@ -34,13 +34,13 @@ def upgrade(self):
)
# Make SCP client to copy over the firmware installation package to the /tmp/ directory on the remote device
- logging.info("Copying installation package to the device...")
+ logging.info("Copying installation package to " + self.rsu_ip + "...")
scp = SCPClient(ssh.get_transport())
scp.put(self.local_file_name, remote_path="/tmp/")
scp.close()
# Run firmware upgrade and reboot
- logging.info("Running firmware upgrade...")
+ logging.info("Running firmware upgrade for " + self.rsu_ip + "...")
_stdin, _stdout, _stderr = ssh.exec_command(
f"signedUpgrade.sh /tmp/{self.install_package}"
)
@@ -65,7 +65,7 @@ def upgrade(self):
self.notify_firmware_manager(success=True)
except Exception as err:
# If something goes wrong, cleanup anything left and report failure if possible
- logging.error(f"Failed to perform firmware upgrade: {err}")
+ logging.error(f"Failed to perform firmware upgrade for {self.rsu_ip}: {err}")
self.cleanup()
self.notify_firmware_manager(success=False)
# send email to support team with the rsu and error
@@ -73,11 +73,11 @@ def upgrade(self):
def post_upgrade(self):
if self.wait_until_online() == -1:
- raise Exception("RSU offline for too long after firmware upgrade")
+ raise Exception("RSU " + self.rsu_ip + " offline for too long after firmware upgrade")
try:
time.sleep(60)
# Make connection with the target device
- logging.info("Making SSH connection with the device...")
+ logging.info("Making SSH connection with " + self.rsu_ip + "...")
ssh = SSHClient()
ssh.set_missing_host_key_policy(WarningPolicy)
ssh.connect(
@@ -89,13 +89,13 @@ def post_upgrade(self):
)
# Make SCP client to copy over the post upgrade script to the /tmp/ directory on the remote device
- logging.info("Copying post upgrade script to the device...")
+ logging.info("Copying post upgrade script to " + self.rsu_ip + "...")
scp = SCPClient(ssh.get_transport())
scp.put(self.post_upgrade_file_name, remote_path="/tmp/")
scp.close()
# Change permissions and execute post upgrade script
- logging.info("Running post upgrade script...")
+ logging.info("Running post upgrade script for " + self.rsu_ip + "...")
ssh.exec_command(
f"chmod +x /tmp/post_upgrade.sh"
)
diff --git a/services/addons/images/firmware_manager/download_blob.py b/services/addons/images/firmware_manager/download_blob.py
index 36901273d..97894b5ad 100644
--- a/services/addons/images/firmware_manager/download_blob.py
+++ b/services/addons/images/firmware_manager/download_blob.py
@@ -1,32 +1,6 @@
-from google.cloud import storage
import logging
import os
-
-
-def download_gcp_blob(blob_name, destination_file_name):
- """Download a file from a GCP Bucket Storage bucket to a local file.
-
- Args:
- blob_name (str): The name of the file in the bucket.
- destination_file_name (str): The name of the local file to download the bucket file to.
- """
-
- if not validate_file_type(blob_name):
- return False
-
- gcp_project = os.environ.get("GCP_PROJECT")
- bucket_name = os.environ.get("BLOB_STORAGE_BUCKET")
- storage_client = storage.Client(gcp_project)
- bucket = storage_client.get_bucket(bucket_name)
- blob = bucket.blob(blob_name)
-
- if blob.exists():
- blob.download_to_filename(destination_file_name)
- logging.info(
- f"Downloaded storage object {blob_name} from bucket {bucket_name} to local file {destination_file_name}."
- )
- return True
- return False
+from common.util import validate_file_type
def download_docker_blob(blob_name, destination_file_name):
@@ -47,23 +21,3 @@ def download_docker_blob(blob_name, destination_file_name):
f"Copied storage object {blob_name} from directory {directory} to local file {destination_file_name}."
)
return True
-
-
-def validate_file_type(file_name):
- """Validate the file type of the file to be downloaded.
-
- Args:
- file_name (str): The name of the file to be downloaded.
- """
- if not file_name.endswith(".tar"):
- logging.error(
- f"Unsupported file type for storage object {file_name}. Only .tar files are supported."
- )
- return False
- return True
-
-
-class UnsupportedFileTypeException(Exception):
- def __init__(self, message="Unsupported file type. Only .tar files are supported."):
- self.message = message
- super().__init__(self.message)
diff --git a/services/addons/images/firmware_manager/firmware_manager.py b/services/addons/images/firmware_manager/firmware_manager.py
index e390bbbc5..733f92652 100644
--- a/services/addons/images/firmware_manager/firmware_manager.py
+++ b/services/addons/images/firmware_manager/firmware_manager.py
@@ -14,8 +14,6 @@
log_level = os.environ.get("LOGGING_LEVEL", "INFO")
logging.basicConfig(format="%(levelname)s:%(message)s", level=log_level)
-ACTIVE_UPGRADE_LIMIT = os.environ.get("ACTIVE_UPGRADE_LIMIT", 20)
-
manufacturer_upgrade_scripts = {
"Commsignia": "commsignia_upgrader.py",
"Yunex": "yunex_upgrader.py",
@@ -38,6 +36,13 @@
upgrade_queue_info = {}
active_upgrades_lock = Lock()
+# Changed from a constant to a function to help with unit testing
+def get_upgrade_limit() -> int:
+ try:
+ upgrade_limit = int(os.environ.get("ACTIVE_UPGRADE_LIMIT", "1"))
+ return upgrade_limit
+ except ValueError:
+ raise ValueError("The environment variable 'ACTIVE_UPGRADE_LIMIT' must be an integer.")
# Function to query the CV Manager PostgreSQL database for RSUs that have:
# - A different target version than their current version
@@ -71,7 +76,7 @@ def get_rsu_upgrade_data(rsu_ip="all"):
def start_tasks_from_queue():
# Start the next process in the queue if there are less than ACTIVE_UPGRADE_LIMIT number of active upgrades occurring
- while len(active_upgrades) < ACTIVE_UPGRADE_LIMIT and len(upgrade_queue) > 0:
+ while len(active_upgrades) < get_upgrade_limit() and len(upgrade_queue) > 0:
rsu_to_upgrade = upgrade_queue.popleft()
try:
rsu_upgrade_info = upgrade_queue_info[rsu_to_upgrade]
diff --git a/services/addons/images/firmware_manager/requirements.txt b/services/addons/images/firmware_manager/requirements.txt
index 0484c7a7d..8411b9977 100644
--- a/services/addons/images/firmware_manager/requirements.txt
+++ b/services/addons/images/firmware_manager/requirements.txt
@@ -6,4 +6,6 @@ pg8000==1.30.2
requests==2.31.0
scp==0.14.5
sqlalchemy==2.0.21
-waitress==2.1.2
\ No newline at end of file
+waitress==2.1.2
+python-dateutil==2.8.2
+pytz==2023.3.post1
diff --git a/services/addons/images/firmware_manager/sample.env b/services/addons/images/firmware_manager/sample.env
index 8f045a07a..2d8d8069e 100644
--- a/services/addons/images/firmware_manager/sample.env
+++ b/services/addons/images/firmware_manager/sample.env
@@ -18,7 +18,6 @@ GCP_PROJECT=""
GOOGLE_APPLICATION_CREDENTIALS=""
# For sending failure emails
-FW_EMAIL_RECIPIENTS=
SMTP_SERVER_IP=
SMTP_EMAIL=
SMTP_USERNAME=
diff --git a/services/addons/images/firmware_manager/upgrader.py b/services/addons/images/firmware_manager/upgrader.py
index 88d871ad6..eca612486 100644
--- a/services/addons/images/firmware_manager/upgrader.py
+++ b/services/addons/images/firmware_manager/upgrader.py
@@ -2,12 +2,14 @@
import abc
import subprocess
import time
-import download_blob
+from common import gcs_utils
import logging
import os
import requests
import shutil
from common.emailSender import EmailSender
+from common.email_util import get_email_list_from_rsu
+import download_blob
class UpgraderAbstractClass(abc.ABC):
@@ -44,7 +46,7 @@ def download_blob(self, blob_name=None, local_file_name=None):
"BLOB_STORAGE_PROVIDER", "DOCKER"
).casefold()
if bspCaseInsensitive == "gcp":
- return download_blob.download_gcp_blob(blob_name, local_file_name)
+ return gcs_utils.download_gcp_blob(blob_name, local_file_name)
elif bspCaseInsensitive == "docker":
return download_blob.download_docker_blob(blob_name, local_file_name)
else:
@@ -55,7 +57,7 @@ def download_blob(self, blob_name=None, local_file_name=None):
# success is a boolean
def notify_firmware_manager(self, success):
status = "success" if success else "fail"
- logging.info(f"Firmware upgrade script completed with status: {status}")
+ logging.info(f"Firmware upgrade script completed for {self.rsu_ip} with status: {status}")
url = "http://127.0.0.1:8080/firmware_upgrade_completed"
body = {"rsu_ip": self.rsu_ip, "status": status}
@@ -96,7 +98,9 @@ def check_online(self):
def send_error_email(self, type="Firmware Upgrader", err=""):
try:
- email_addresses = os.environ.get("FW_EMAIL_RECIPIENTS").split(",")
+ email_addresses = get_email_list_from_rsu(
+ "Firmware Upgrade Failures", self.rsu_ip
+ )
subject = (
f"{self.rsu_ip} Firmware Upgrader Failure"
diff --git a/services/addons/images/firmware_manager/yunex_upgrader.py b/services/addons/images/firmware_manager/yunex_upgrader.py
index 2c520734d..7b42e870f 100644
--- a/services/addons/images/firmware_manager/yunex_upgrader.py
+++ b/services/addons/images/firmware_manager/yunex_upgrader.py
@@ -26,7 +26,7 @@ def run_xfer_upgrade(self, file_name):
# If the command ends with a non-successful status code, return -1
if code != 0:
- logging.error("Firmware not successful: " + stderr.decode("utf-8"))
+ logging.error("Firmware not successful for " + self.rsu_ip + ": " + stderr.decode("utf-8"))
return -1
output_lines = stdout.decode("utf-8").split("\n")[:-1]
@@ -35,7 +35,7 @@ def run_xfer_upgrade(self, file_name):
'TEXT: {"success":{"upload":"Processing OK. Rebooting now ..."}}'
not in output_lines
):
- logging.error("Firmware not successful: " + stderr.decode("utf-8"))
+ logging.error("Firmware not successful for " + self.rsu_ip + ": " + stderr.decode("utf-8"))
return -1
# If everything goes as expected, the XFER upgrade was complete
@@ -52,7 +52,7 @@ def upgrade(self):
# - SDK upgrade file
# - Application provision file
# - upgrade_info.json which defines the files as a single JSON object
- logging.info("Unpacking TAR file...")
+ logging.info("Unpacking TAR file prior to upgrading " + self.rsu_ip + "...")
with tarfile.open(self.local_file_name, "r") as tar:
tar.extractall(self.root_path)
@@ -62,7 +62,7 @@ def upgrade(self):
upgrade_info = json.load(json_file)
# Run Core upgrade
- logging.info("Running Core firmware upgrade...")
+ logging.info("Running Core firmware upgrade for " + self.rsu_ip + "...")
code = self.run_xfer_upgrade(f"{self.root_path}/{upgrade_info['core']}")
if code == -1:
raise Exception("Yunex RSU Core upgrade failed")
@@ -72,7 +72,7 @@ def upgrade(self):
time.sleep(60)
# Run SDK upgrade
- logging.info("Running SDK firmware upgrade...")
+ logging.info("Running SDK firmware upgrade for " + self.rsu_ip + "...")
code = self.run_xfer_upgrade(f"{self.root_path}/{upgrade_info['sdk']}")
if code == -1:
raise Exception("Yunex RSU SDK upgrade failed")
@@ -82,7 +82,7 @@ def upgrade(self):
time.sleep(60)
# Run application provision image
- logging.info("Running application provisioning...")
+ logging.info("Running application provisioning for " + self.rsu_ip + "...")
code = self.run_xfer_upgrade(
f"{self.root_path}/{upgrade_info['provision']}"
)
@@ -96,7 +96,7 @@ def upgrade(self):
# If something goes wrong, cleanup anything left and report failure if possible.
# Yunex RSUs can handle having the same firmware upgraded over again.
# There is no issue with starting from the beginning even with a partially complete upgrade.
- logging.error(f"Failed to perform firmware upgrade: {err}")
+ logging.error(f"Failed to perform firmware upgrade for {self.rsu_ip}: {err}")
self.cleanup()
self.notify_firmware_manager(success=False)
# send email to support team with the rsu and error
diff --git a/services/addons/images/iss_health_check/iss_health_checker.py b/services/addons/images/iss_health_check/iss_health_checker.py
index ec2d5a3d4..71dd6c8fe 100644
--- a/services/addons/images/iss_health_check/iss_health_checker.py
+++ b/services/addons/images/iss_health_check/iss_health_checker.py
@@ -11,6 +11,7 @@
# Set up logging
logger = logging.getLogger(__name__)
+
@dataclass
class RsuDataWrapper:
rsu_data: Dict[str, Dict[str, str]] = field(default_factory=dict)
@@ -23,7 +24,7 @@ def get_dict(self):
def set_provisioner_company(self, scms_id, provisioner_company):
self.rsu_data[scms_id]["provisionerCompany"] = provisioner_company
-
+
def set_entity_type(self, scms_id, entity_type):
self.rsu_data[scms_id]["entityType"] = entity_type
@@ -39,7 +40,7 @@ def set_expiration(self, scms_id, expiration):
def get_rsu_data() -> RsuDataWrapper:
"""Get RSU data from PostgreSQL and return it in a wrapper object"""
-
+
result = {}
query = (
"SELECT jsonb_build_object('rsu_id', rsu_id, 'iss_scms_id', iss_scms_id) "
@@ -89,14 +90,21 @@ def get_scms_status_data():
for enrollment_status in enrollment_list:
es_id = enrollment_status["_id"]
if es_id in rsu_data.get_dict():
- rsu_data.set_provisioner_company(es_id, enrollment_status["provisionerCompany_id"])
+ rsu_data.set_provisioner_company(
+ es_id, enrollment_status["provisionerCompany_id"]
+ )
rsu_data.set_entity_type(es_id, enrollment_status["entityType"])
rsu_data.set_project_id(es_id, enrollment_status["project_id"])
rsu_data.set_device_health(es_id, enrollment_status["deviceHealth"])
# If the device has yet to download its first set of certs, set the expiration time to when it was enrolled
if "authorizationCertInfo" in enrollment_status["enrollments"][0]:
- rsu_data.set_expiration(es_id, enrollment_status["enrollments"][0]["authorizationCertInfo"]["expireTimeOfLatestDownloadedCert"])
+ rsu_data.set_expiration(
+ es_id,
+ enrollment_status["enrollments"][0]["authorizationCertInfo"][
+ "expireTimeOfLatestDownloadedCert"
+ ],
+ )
else:
rsu_data.set_expiration(es_id, None)
@@ -119,24 +127,34 @@ def insert_scms_data(data):
if validate_scms_data(value) is False:
continue
- health = "1" if value["deviceHealth"] == "Healthy" else "0"
- if value["expiration"]:
- query = (
- query
- + f" ('{now_ts}', '{health}', '{value['expiration']}', {value['rsu_id']}),"
- )
+ # If deviceHealth isn't included in the device's SCMS profile, assume unhealthy
+ if "deviceHealth" in value:
+ health = "1" if value["deviceHealth"] == "Healthy" else "0"
else:
- query = query + f" ('{now_ts}', '{health}', NULL, {value['rsu_id']}),"
+ health = "0"
+
+ # If expiration isn't included in the device's SCMS profile, assume None
+ if "expiration" in value:
+ # Check if the expiration field is None
+ if value["expiration"]:
+ expiration = f"'{value["expiration"]}'"
+ else:
+ expiration = "NULL"
+ else:
+ expiration = "NULL"
+
+ query = query + f" ('{now_ts}', '{health}', {expiration}, {value['rsu_id']}),"
- query = query[:-1] # remove comma
+ query = query[:-1] # remove comma
pgquery.write_db(query)
logger.info(
"SCMS data inserted {} messages into PostgreSQL...".format(len(data.values()))
)
+
def validate_scms_data(value):
"""Validate the SCMS data
-
+
Args:
value (dict): SCMS data
"""
@@ -144,21 +162,31 @@ def validate_scms_data(value):
try:
value["rsu_id"]
except KeyError as e:
- logger.warning("rsu_id not found in data, is it real data? exception: {}".format(e))
+ logger.warning(
+ "rsu_id not found in data, is it real data? exception: {}".format(e)
+ )
return False
try:
value["deviceHealth"]
except KeyError as e:
- logger.warning("deviceHealth not found in data for RSU with id {}, is it real data? exception: {}".format(value["rsu_id"], e))
+ logger.warning(
+ "deviceHealth not found in data for RSU with id {}, is it real data? exception: {}".format(
+ value["rsu_id"], e
+ )
+ )
return False
try:
value["expiration"]
except KeyError as e:
- logger.warning("expiration not found in data for RSU with id {}, is it real data? exception: {}".format(value["rsu_id"], e))
+ logger.warning(
+ "expiration not found in data for RSU with id {}, is it real data? exception: {}".format(
+ value["rsu_id"], e
+ )
+ )
return False
-
+
return True
@@ -170,4 +198,4 @@ def validate_scms_data(value):
logging.basicConfig(format="%(levelname)s:%(message)s", level=log_level)
scms_statuses = get_scms_status_data()
- insert_scms_data(scms_statuses)
\ No newline at end of file
+ insert_scms_data(scms_statuses)
diff --git a/services/addons/images/obu_ota_server/README.md b/services/addons/images/obu_ota_server/README.md
new file mode 100644
index 000000000..65b5529f3
--- /dev/null
+++ b/services/addons/images/obu_ota_server/README.md
@@ -0,0 +1,79 @@
+# OBU OTA Server
+
+## Table of Contents
+
+- [OBU OTA Server](#obu-ota-server)
+ - [Table of Contents](#table-of-contents)
+ - [About ](#about-)
+ - [Requirements ](#requirements-)
+ - [Common required variables ](#common-required-variables-)
+ - [GCP required variables ](#gcp-required-variables-)
+ - [Local Required variables ](#local-required-variables-)
+
+## About
+
+This directory contains a microservice that runs within the CV Manager GKE Cluster. This service can take either local or GCP stored firmware files and serves them for OBU devices to receive Over the Air (OTA) updates. OBU devices will query the OTA server for a manifest of available firmware files and will send another request for the full firmware file if it has a newer version number. For local deployments the OBU OTA server is deployed behind a NGINX proxy to allow for TLS encryption. In k8's an ingress resource is used to handle certificates and TLS.
+
+List of currently supported vendors:
+
+- Commsignia
+
+Available REST endpoints:
+
+- / [ **GET** ]
+ - Used as a health check path for K8 deployment. This path is not secured with basic authentication
+- /firmwares/commsignia [ **GET** ]
+ - Secured with basic authentication.
+ - Firmware file name must comply with the following regex naming filter:
+ `(?P This is an automated email to report yesterday's ODE message counts for J2735 messages going in and out of the ODE. "
"In counts are the number of encoded messages received by the ODE from the load balancer. "
"Out counts are the number of decoded messages that have come out of the ODE in JSON form and "
diff --git a/services/addons/tests/count_metric/test_mongo_counter.py b/services/addons/tests/count_metric/test_mongo_counter.py
index acacacb7f..377be9670 100644
--- a/services/addons/tests/count_metric/test_mongo_counter.py
+++ b/services/addons/tests/count_metric/test_mongo_counter.py
@@ -13,6 +13,15 @@ def test_write_counts():
mock_collection.insert_many.assert_called_with(["test"])
+def test_write_counts_empty():
+ mock_collection = MagicMock()
+ mock_mongo_db = {"CVCounts": mock_collection}
+
+ mongo_counter.write_counts(mock_mongo_db, [])
+
+ mock_collection.insert_many.assert_not_called()
+
+
@patch.dict(os.environ, {"MONGO_DB_URI": "uri", "MONGO_DB_NAME": "name"})
def test_count_query_bsm():
mock_collection = MagicMock()
diff --git a/services/addons/tests/firmware_manager/test_commsignia_upgrader.py b/services/addons/tests/firmware_manager/test_commsignia_upgrader.py
index b738a05d6..cf107fc89 100644
--- a/services/addons/tests/firmware_manager/test_commsignia_upgrader.py
+++ b/services/addons/tests/firmware_manager/test_commsignia_upgrader.py
@@ -31,9 +31,10 @@ def test_commsignia_upgrader_init():
assert test_commsignia_upgrader.ssh_password == "test-psw"
+@patch("addons.images.firmware_manager.commsignia_upgrader.logging")
@patch("addons.images.firmware_manager.commsignia_upgrader.SCPClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.SSHClient")
-def test_commsignia_upgrader_upgrade_success_no_post_update(mock_sshclient, mock_scpclient):
+def test_commsignia_upgrader_upgrade_success_no_post_update(mock_sshclient, mock_scpclient, mock_logging):
# Mock SSH Client and successful firmware upgrade return value
sshclient_obj = mock_sshclient.return_value
_stdout = MagicMock()
@@ -78,10 +79,22 @@ def test_commsignia_upgrader_upgrade_success_no_post_update(mock_sshclient, mock
# Assert notified success value
notify.assert_called_with(success=True)
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying installation package to 8.8.8.8..."),
+ call("Running firmware upgrade for 8.8.8.8..."),
+ call("ALL OK")
+ ]
+ )
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.commsignia_upgrader.logging")
@patch("addons.images.firmware_manager.commsignia_upgrader.SCPClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.SSHClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.time")
-def test_commsignia_upgrader_upgrade_success_post_update(mock_time, mock_sshclient, mock_scpclient):
+def test_commsignia_upgrader_upgrade_success_post_update(mock_time, mock_sshclient, mock_scpclient, mock_logging):
# Mock SSH Client and successful firmware upgrade return value
sshclient_obj = mock_sshclient.return_value
_stdout = MagicMock()
@@ -135,6 +148,22 @@ def test_commsignia_upgrader_upgrade_success_post_update(mock_time, mock_sshclie
# Assert notified success value
notify.assert_called_with(success=True)
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying installation package to 8.8.8.8..."),
+ call("Running firmware upgrade for 8.8.8.8..."),
+ call("ALL OK"),
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying post upgrade script to 8.8.8.8..."),
+ call("Running post upgrade script for 8.8.8.8..."),
+ call("ALL OK"),
+ call("Post upgrade script executed successfully for rsu: 8.8.8.8.")
+ ]
+ )
+ mock_logging.error.assert_not_called()
+
@patch("addons.images.firmware_manager.commsignia_upgrader.SCPClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.SSHClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.time")
@@ -192,13 +221,29 @@ def test_commsignia_upgrader_upgrade_post_update_fail(mock_logging, mock_time, m
]
)
sshclient_obj.close.assert_called_with()
+
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying installation package to 8.8.8.8..."),
+ call("Running firmware upgrade for 8.8.8.8..."),
+ call("ALL OK"),
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying post upgrade script to 8.8.8.8..."),
+ call("Running post upgrade script for 8.8.8.8..."),
+ call("NOT OK TEST"),
+ ]
+ )
mock_logging.error.assert_called_with("Failed to execute post upgrade script for rsu 8.8.8.8: NOT OK TEST")
+
# Assert notified success value
notify.assert_called_with(success=True)
+@patch("addons.images.firmware_manager.commsignia_upgrader.logging")
@patch("addons.images.firmware_manager.commsignia_upgrader.SCPClient")
@patch("addons.images.firmware_manager.commsignia_upgrader.SSHClient")
-def test_commsignia_upgrader_upgrade_fail(mock_sshclient, mock_scpclient):
+def test_commsignia_upgrader_upgrade_fail(mock_sshclient, mock_scpclient, mock_logging):
# Mock SSH Client and failed firmware upgrade return value
sshclient_obj = mock_sshclient.return_value
_stdout = MagicMock()
@@ -240,6 +285,17 @@ def test_commsignia_upgrader_upgrade_fail(mock_sshclient, mock_scpclient):
)
sshclient_obj.close.assert_called_with()
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Making SSH connection with 8.8.8.8..."),
+ call("Copying installation package to 8.8.8.8..."),
+ call("Running firmware upgrade for 8.8.8.8..."),
+ call("NOT OK TEST")
+ ]
+ )
+ mock_logging.error.assert_not_called()
+
# Assert notified success value
notify.assert_called_with(success=False)
@@ -280,9 +336,10 @@ def test_commsignia_upgrader_upgrade_exception(
# Assert SSH firmware upgrade run doesn't occur
sshclient_obj.exec_command.assert_not_called()
+ # Assert logging
+ mock_logging.info.assert_called_with("Making SSH connection with 8.8.8.8...")
+ mock_logging.error.assert_called_with("Failed to perform firmware upgrade for 8.8.8.8: Exception occurred during upgrade")
+
# Assert exception was cleaned up and firmware manager was notified of upgrade failure
- mock_logging.error.assert_called_with(
- "Failed to perform firmware upgrade: Exception occurred during upgrade"
- )
cleanup.assert_called_with()
notify.assert_called_with(success=False)
diff --git a/services/addons/tests/firmware_manager/test_download_blob.py b/services/addons/tests/firmware_manager/test_download_blob.py
index 970ec8f97..a266c44d4 100644
--- a/services/addons/tests/firmware_manager/test_download_blob.py
+++ b/services/addons/tests/firmware_manager/test_download_blob.py
@@ -3,52 +3,6 @@
import pytest
from addons.images.firmware_manager import download_blob
-from addons.images.firmware_manager.download_blob import UnsupportedFileTypeException
-
-
-@patch.dict(
- os.environ, {"GCP_PROJECT": "test-project", "BLOB_STORAGE_BUCKET": "test-bucket"}
-)
-@patch("addons.images.firmware_manager.download_blob.logging")
-@patch("addons.images.firmware_manager.download_blob.storage.Client")
-def test_download_gcp_blob(mock_storage_client, mock_logging):
- # mock
- mock_client = mock_storage_client.return_value
- mock_bucket = mock_client.get_bucket.return_value
- mock_blob = mock_bucket.blob.return_value
-
- # run
- download_blob.download_gcp_blob(
- blob_name="test.tar", destination_file_name="/home/test/"
- )
-
- # validate
- mock_storage_client.assert_called_with("test-project")
- mock_client.get_bucket.assert_called_with("test-bucket")
- mock_bucket.blob.assert_called_with("test.tar")
- mock_blob.download_to_filename.assert_called_with("/home/test/")
- mock_logging.info.assert_called_with(
- "Downloaded storage object test.tar from bucket test-bucket to local file /home/test/."
- )
-
-
-@patch.dict(
- os.environ, {"GCP_PROJECT": "test-project", "BLOB_STORAGE_BUCKET": "test-bucket"}
-)
-@patch("addons.images.firmware_manager.download_blob.logging")
-def test_download_gcp_blob_unsupported_file_type(mock_logging):
- # prepare
- blob_name = "test.blob"
- destination_file_name = "/home/test/"
-
- # run
- result = download_blob.download_gcp_blob(blob_name, destination_file_name)
-
- # validate
- mock_logging.error.assert_called_with(
- f"Unsupported file type for storage object {blob_name}. Only .tar files are supported."
- )
- assert result == False
@patch("addons.images.firmware_manager.download_blob.logging")
@@ -70,7 +24,7 @@ def test_download_docker_blob(mock_logging):
)
-@patch("addons.images.firmware_manager.download_blob.logging")
+@patch("common.util.logging")
def test_download_docker_blob_unsupported_file_type(mock_logging):
# prepare
os.system = MagicMock()
@@ -82,6 +36,6 @@ def test_download_docker_blob_unsupported_file_type(mock_logging):
# validate
mock_logging.error.assert_called_with(
- f"Unsupported file type for storage object {blob_name}. Only .tar files are supported."
+ f'Unsupported file type for storage object {blob_name}. Only ".tar" files are supported.'
)
assert result == False
diff --git a/services/addons/tests/firmware_manager/test_firmware_manager.py b/services/addons/tests/firmware_manager/test_firmware_manager.py
index 11b19b5f3..bd760a322 100644
--- a/services/addons/tests/firmware_manager/test_firmware_manager.py
+++ b/services/addons/tests/firmware_manager/test_firmware_manager.py
@@ -1,8 +1,8 @@
-from unittest.mock import patch, MagicMock
+from unittest.mock import call, patch, MagicMock
from subprocess import DEVNULL
from collections import deque
import test_firmware_manager_values as fmv
-
+import pytest
from addons.images.firmware_manager import firmware_manager
@@ -72,11 +72,15 @@ def test_start_tasks_from_queue_popen_fail(mock_popen, mock_logging):
["python3", f"/home/commsignia_upgrader.py", expected_json_str],
stdout=DEVNULL,
)
+
+ # Assert logging
+ mock_logging.info.assert_not_called()
mock_logging.error.assert_called_with(
f"Encountered error of type {Exception} while starting automatic upgrade process for 8.8.8.8: Process failed to start"
)
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
@patch(
"addons.images.firmware_manager.firmware_manager.upgrade_queue", deque(["8.8.8.8"])
@@ -97,7 +101,7 @@ def test_start_tasks_from_queue_popen_fail(mock_popen, mock_logging):
},
)
@patch("addons.images.firmware_manager.firmware_manager.Popen")
-def test_start_tasks_from_queue_popen_success(mock_popen):
+def test_start_tasks_from_queue_popen_success(mock_popen, mock_logging):
mock_popen_obj = mock_popen.return_value
firmware_manager.start_tasks_from_queue()
@@ -115,12 +119,16 @@ def test_start_tasks_from_queue_popen_success(mock_popen):
# Assert the process reference is successfully tracked in the active_upgrades dictionary
assert firmware_manager.active_upgrades["8.8.8.8"]["process"] == mock_popen_obj
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
# init_firmware_upgrade tests
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
-def test_init_firmware_upgrade_missing_rsu_ip():
+def test_init_firmware_upgrade_missing_rsu_ip(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {}
mock_flask_jsonify = MagicMock()
@@ -138,11 +146,15 @@ def test_init_firmware_upgrade_missing_rsu_ip():
)
assert code == 400
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades", {"8.8.8.8": {}}
)
-def test_init_firmware_upgrade_already_running():
+def test_init_firmware_upgrade_already_running(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8"}
mock_flask_jsonify = MagicMock()
@@ -162,13 +174,18 @@ def test_init_firmware_upgrade_already_running():
)
assert code == 500
+ # Assert logging
+ mock_logging.info.assert_called_with("Checking if existing upgrade is running or queued for '8.8.8.8'")
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
@patch(
"addons.images.firmware_manager.firmware_manager.get_rsu_upgrade_data",
MagicMock(return_value=[]),
)
-def test_init_firmware_upgrade_no_eligible_upgrade():
+def test_init_firmware_upgrade_no_eligible_upgrade(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8"}
mock_flask_jsonify = MagicMock()
@@ -188,14 +205,24 @@ def test_init_firmware_upgrade_no_eligible_upgrade():
)
assert code == 500
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Checking if existing upgrade is running or queued for '8.8.8.8'"),
+ call("Querying RSU data for '8.8.8.8'")
+ ]
+ )
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
@patch(
"addons.images.firmware_manager.firmware_manager.get_rsu_upgrade_data",
MagicMock(return_value=[fmv.rsu_info]),
)
@patch("addons.images.firmware_manager.firmware_manager.start_tasks_from_queue")
-def test_init_firmware_upgrade_success(mock_stfq):
+def test_init_firmware_upgrade_success(mock_stfq, mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8"}
mock_flask_jsonify = MagicMock()
@@ -220,14 +247,25 @@ def test_init_firmware_upgrade_success(mock_stfq):
)
assert code == 201
+ # Assert logging
+ mock_logging.info.assert_has_calls(
+ [
+ call("Checking if existing upgrade is running or queued for '8.8.8.8'"),
+ call("Querying RSU data for '8.8.8.8'"),
+ call("Adding '8.8.8.8' to the firmware manager upgrade queue")
+ ]
+ )
+ mock_logging.error.assert_not_called()
+
firmware_manager.upgrade_queue = deque([])
# firmware_upgrade_completed tests
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
-def test_firmware_upgrade_completed_missing_rsu_ip():
+def test_firmware_upgrade_completed_missing_rsu_ip(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {}
mock_flask_jsonify = MagicMock()
@@ -245,9 +283,14 @@ def test_firmware_upgrade_completed_missing_rsu_ip():
)
assert code == 400
+ # Assert logging
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch("addons.images.firmware_manager.firmware_manager.active_upgrades", {})
-def test_firmware_upgrade_completed_unknown_process():
+def test_firmware_upgrade_completed_unknown_process(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {
"rsu_ip": "8.8.8.8",
@@ -270,12 +313,17 @@ def test_firmware_upgrade_completed_unknown_process():
)
assert code == 400
+ # Assert logging
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades",
{"8.8.8.8": fmv.upgrade_info},
)
-def test_firmware_upgrade_completed_missing_status():
+def test_firmware_upgrade_completed_missing_status(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8"}
mock_flask_jsonify = MagicMock()
@@ -293,12 +341,17 @@ def test_firmware_upgrade_completed_missing_status():
)
assert code == 400
+ # Assert logging
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades",
{"8.8.8.8": fmv.upgrade_info},
)
-def test_firmware_upgrade_completed_illegal_status():
+def test_firmware_upgrade_completed_illegal_status(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8", "status": "frog"}
mock_flask_jsonify = MagicMock()
@@ -318,12 +371,17 @@ def test_firmware_upgrade_completed_illegal_status():
)
assert code == 400
+ # Assert logging
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades",
{"8.8.8.8": fmv.upgrade_info},
)
-def test_firmware_upgrade_completed_fail_status():
+def test_firmware_upgrade_completed_fail_status(mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {"rsu_ip": "8.8.8.8", "status": "fail"}
mock_flask_jsonify = MagicMock()
@@ -342,13 +400,18 @@ def test_firmware_upgrade_completed_fail_status():
)
assert code == 204
+ # Assert logging
+ mock_logging.info.assert_called_with("Marking firmware upgrade as complete for '8.8.8.8'")
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades",
{"8.8.8.8": fmv.upgrade_info},
)
@patch("addons.images.firmware_manager.firmware_manager.pgquery.write_db")
-def test_firmware_upgrade_completed_success_status(mock_writedb):
+def test_firmware_upgrade_completed_success_status(mock_writedb, mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {
"rsu_ip": "8.8.8.8",
@@ -373,7 +436,12 @@ def test_firmware_upgrade_completed_success_status(mock_writedb):
)
assert code == 204
+ # Assert logging
+ mock_logging.info.assert_called_with("Marking firmware upgrade as complete for '8.8.8.8'")
+ mock_logging.error.assert_not_called()
+
+@patch("addons.images.firmware_manager.firmware_manager.logging")
@patch(
"addons.images.firmware_manager.firmware_manager.active_upgrades",
{"8.8.8.8": fmv.upgrade_info},
@@ -382,7 +450,7 @@ def test_firmware_upgrade_completed_success_status(mock_writedb):
"addons.images.firmware_manager.firmware_manager.pgquery.write_db",
side_effect=Exception("Failure to query PostgreSQL"),
)
-def test_firmware_upgrade_completed_success_status_exception(mock_writedb):
+def test_firmware_upgrade_completed_success_status_exception(mock_writedb, mock_logging):
mock_flask_request = MagicMock()
mock_flask_request.get_json.return_value = {
"rsu_ip": "8.8.8.8",
@@ -408,15 +476,21 @@ def test_firmware_upgrade_completed_success_status_exception(mock_writedb):
)
assert code == 500
+
+ # Assert logging
+ mock_logging.info.assert_not_called()
+ mock_logging.error.assert_called_with("Encountered error of type
- You are receiving this email because your user is marked to receive error emails on the CV Manager Admin page.
- You are receiving this email because you have been included in the CV Manager developer group. This error originated in the ##_ENVIRONMENT_## environment CV Manager API Error occurred at: ##_ERROR_TIME_## View this error in Logs: rsu-manager-api logs --
- If you no longer wish to receive these emails, please uncheck the "receive error emails" checkbox on the CV
- manager portal Admin page, or use this link to unsubscribe: unsubscribe
- TEST Count Report {expected_start_string} UTC - {expected_end_string} UTC
"
+ f"Test Org Test Count Report {expected_start_string} UTC - {expected_end_string} UTC
"
"
@@ -47,11 +45,5 @@
-