Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve Merge Conflicts With Upstream #74

Merged
merged 31 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fcdebe2
Merge pull request #10 from CDOT-CV/develop
drewjj Feb 2, 2024
06f0caa
Merge pull request #12 from usdot-jpo-ode/candidate_r1
dan-du-car Feb 26, 2024
cc2d1f1
Merge pull request #13 from usdot-jpo-ode/master
dan-du-car Feb 28, 2024
a969604
Added support for storing ISS keys in postgres database
dmccoystephenson Jan 25, 2024
6fa9485
Updated `test_iss_token.py` to account for new env var
dmccoystephenson Jan 25, 2024
d3a3193
Implemented unit tests for postgres ISS key storage
dmccoystephenson Jan 26, 2024
090e8d2
Updated ISS Health Check README to include postgres storage info
dmccoystephenson Jan 26, 2024
9ac4d8e
Modified 'About' section in ISS Health Check README
dmccoystephenson Jan 26, 2024
43eb3c1
Modified `iss_token.py` to exit if STORAGE_TYPE is invalid or unset
dmccoystephenson Jan 26, 2024
7a80e62
Made STORAGE_TYPE env var for ISS Health Check addon case insensitive
dmccoystephenson Jan 26, 2024
e16d8a3
Added docker storage support for the Firmware Manager
dmccoystephenson Feb 2, 2024
f608bb7
Improved clarity of documentation for docker firmware storage.
dmccoystephenson Feb 16, 2024
99a9326
Printed useful error message upon failure to decode JSON response fro…
dmccoystephenson Feb 9, 2024
42dc0d1
Added new environment variables to ihc manifest
dmccoystephenson Feb 21, 2024
79852e8
Used pytest.raises() instead of try/catch in `test_iss_token.py`
dmccoystephenson Feb 23, 2024
97fee13
Updated multidict dependency to 6.0.5 in requirements.txt
dmccoystephenson Mar 26, 2024
c534751
Merge pull request #8 from Trihydro/requirements/update-multidict-ver…
payneBrandon Mar 29, 2024
691f8b4
Replaced single-line comments with docstrings in `download_blob.py`
dmccoystephenson Apr 25, 2024
d59f431
Improved clarity of comments in `sample.env` for firmware_manager
dmccoystephenson Apr 25, 2024
755538b
Modified `upgrader.py` to raise an exception if an unsupported blob s…
dmccoystephenson Apr 25, 2024
5934f84
Added module-specific logger for ISS Health Check addon
dmccoystephenson Apr 25, 2024
5c4fcf0
Added file type validation checks to `download_blob.py`
dmccoystephenson Apr 25, 2024
1cf0ea8
Added validation checks for SCMS data to `iss_health_checker.py`
dmccoystephenson Apr 25, 2024
da7a4ba
Added a comment to explain why the last character is ignored in the i…
dmccoystephenson Apr 25, 2024
ec3d455
Created RsuDataWrapper class in `iss_health_checker.py` to improve cl…
dmccoystephenson Apr 25, 2024
2ca5153
Fixed logging configuration setup in `iss_health_checker.py`
dmccoystephenson Apr 25, 2024
dbee31a
Merge pull request #10 from Trihydro/pr/addressing-usdot-comments
payneBrandon Apr 26, 2024
79ad929
Merge pull request #15 from Trihydro/dev
dan-du-car Apr 29, 2024
0bb0535
Merge branch 'develop' into merge_wydot_firmware_manager
drewjj Jun 3, 2024
0f640f5
Update firmware manager to support the wydot changes and update tests
drewjj Jun 3, 2024
e0cfd87
Remove duplicate sample env var
drewjj Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ cov.xml
.venv
cov_html
htmlcov
.pytest_cache
.pytest_cache
local_blob_storage
3 changes: 2 additions & 1 deletion docker-compose-addons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ services:
LOGGING_LEVEL: ${FIRMWARE_MANAGER_LOGGING_LEVEL}
volumes:
- ${GOOGLE_APPLICATION_CREDENTIALS}:/google/gcp_credentials.json
- ${HOST_BLOB_STORAGE_DIRECTORY}:/mnt/blob_storage
logging:
options:
max-size: '10m'
max-file: '5'
max-file: '5'
2 changes: 1 addition & 1 deletion resources/kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The webapp and API both utilize a K8s Ingress to handle external access to the a

The YAML files use GCP specific specifications for various values such as "networking.gke.io/managed-certificates". These values will not work on AWS and Azure but there should be equivalent fields that these specifications can be updated to if needing to deploy in another cloud environment.

The environment variables must be set according to the README documentation for each application. The iss-health-check application only supports GCP.
The environment variables must be set according to the README documentation for each application. The iss-health-check application supports GCP or postgres for storing keys. The environment variables for the iss-health-check application must be set according to the README documentation for the iss-health-check application.

## Useful Links

Expand Down
6 changes: 5 additions & 1 deletion resources/kubernetes/iss-health-check.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This deployment is only usable in a GCP environment due to the GCP Secret Manager dependency
# If GCP is being used to store keys, this deployment will only be usable in a GCP environment due to the GCP Secret Manager dependency
apiVersion: 'apps/v1'
kind: 'Deployment'
metadata:
Expand Down Expand Up @@ -27,6 +27,8 @@ spec:
ports:
- containerPort: 8080
env:
- name: STORAGE_TYPE
value: GCP
- name: GOOGLE_APPLICATION_CREDENTIALS
value: '/home/secret/cv_credentials.json'
- name: PROJECT_ID
Expand All @@ -41,6 +43,8 @@ spec:
value: ''
- name: ISS_SCMS_VEHICLE_REST_ENDPOINT
value: ''
- name: ISS_KEY_TABLE_NAME
value: ''
- name: DB_USER
value: ''
- name: DB_PASS
Expand Down
15 changes: 15 additions & 0 deletions resources/sql_scripts/CVManager_CreateTables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ SELECT ro.rsu_id, org.name
FROM public.rsu_organization AS ro
JOIN public.organizations AS org ON ro.organization_id = org.organization_id;

-- Create iss keys table (id, iss_key, creation_date, expiration_date)
CREATE SEQUENCE public.iss_keys_iss_key_id_seq
INCREMENT 1
START 1
MINVALUE 1
MAXVALUE 2147483647
CACHE 1;

CREATE TABLE IF NOT EXISTS public.iss_keys
(
iss_key_id integer NOT NULL DEFAULT nextval('iss_keys_iss_key_id_seq'::regclass),
common_name character varying(128) COLLATE pg_catalog.default NOT NULL,
token character varying(128) COLLATE pg_catalog.default NOT NULL
);

-- Create scms_health table
CREATE SEQUENCE public.scms_health_scms_health_id_seq
INCREMENT 1
Expand Down
4 changes: 3 additions & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ KAFKA_BIGQUERY_TABLENAME = ''
# ---------------------------------------------------------------------

# Firmware Manager Addon:
BLOB_STORAGE_PROVIDER=GCP
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
# ---------------------------------------------------------------------

# Geo-spatial message query Addon:
Expand Down
7 changes: 5 additions & 2 deletions services/addons/images/firmware_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This directory contains a microservice that runs within the CV Manager GKE Clust

An RSU is determined to be ready for upgrade if its entry in the "rsus" table in PostgreSQL has its "target_firmware_version" set to be different than its "firmware_version". The Firmware Manager will ignore all devices with incompatible firmware upgrades set as their target firmware based on the "firmware_upgrade_rules" table. The CV Manager API will only offer CV Manager webapp users compatible options so this generally is a precaution.

Hosting firmware files is recommended to be done via the cloud. GCP cloud storage is the currently supported method. Alternatives can be added via the [download_blob.py](download_blob.py) script. Firmware storage must be organized by: `vendor/rsu-model/firmware-version/install_package`.
Hosting firmware files is recommended to be done via the cloud. GCP cloud storage is the currently supported method, but a directory mounted as a docker volume can also be used. Alternative cloud support can be added via the [download_blob.py](download_blob.py) script. Firmware storage must be organized by: `vendor/rsu-model/firmware-version/install_package`.

Firmware upgrades have unique procedures based on RSU vendor/manufacturer. To avoid requiring a unique bash script for every single firmware upgrade, the Firmware Manager has been written to use vendor based upgrade scripts that have been thoroughly tested. An interface-like abstract class, [base_upgrader.py](base_upgrader.py), has been made for helping create upgrade scripts for vendors not yet supported. The Firmware Manager selects the script to use based off the RSU's "model" column in the "rsus" table. These scripts report back to the Firmware Manager on completion with a status of whether the upgrade was a success or failure. Regardless, the Firmware Manager will remove the process from its tracking and update the PostgreSQL database accordingly.

Expand All @@ -40,7 +40,7 @@ Available REST endpoints:

To properly run the firmware_manager microservice the following services are also required:

- Cloud based blob storage
- Blob storage (cloud-based or otherwise)
- Firmware storage must be organized by: `vendor/rsu-model/firmware-version/install_package`.
- CV Manager PostgreSQL database with data in the "rsus", "rsu_models", "manufacturers", "firmware_images", and "firmware_upgrade_rules" tables
- Network connectivity from the environment the firmware_manager is deployed into to the blob storage and the RSUs
Expand Down Expand Up @@ -70,6 +70,9 @@ GCP Required environment variables:
- GCP_PROJECT - GCP project for the firmware cloud storage bucket
- 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

### Commsignia
Expand Down
52 changes: 51 additions & 1 deletion services/addons/images/firmware_manager/download_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,67 @@
import os


# Only supports GCP Bucket Storage for downloading blobs
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


def download_docker_blob(blob_name, destination_file_name):
"""Copy a file from a directory mounted as a volume in a Docker container to a local file.

Args:
blob_name (str): The name of the file in the directory.
destination_file_name (str): The name of the local file to copy the directory file to.
"""

if not validate_file_type(blob_name):
return False

directory = "/mnt/blob_storage"
source_file_name = f"{directory}/{blob_name}"
os.system(f"cp {source_file_name} {destination_file_name}")
logging.info(
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)
8 changes: 5 additions & 3 deletions services/addons/images/firmware_manager/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ PG_DB_NAME=""
PG_DB_USER=""
PG_DB_PASS=""

# Blob storage variables
BLOB_STORAGE_PROVIDER="GCP"
BLOB_STORAGE_BUCKET=""
# Blob storage variables (only 'GCP' and 'DOCKER' are supported at this time)
BLOB_STORAGE_PROVIDER=DOCKER
BLOB_STORAGE_BUCKET=
## Docker volume mount point for BLOB storage (if using DOCKER)
HOST_BLOB_STORAGE_DIRECTORY=./local_blob_storage

# For users using GCP cloud storage
GCP_PROJECT=""
Expand Down
30 changes: 23 additions & 7 deletions services/addons/images/firmware_manager/upgrader.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,22 @@ def download_blob(self, blob_name=None, local_file_name=None):
# Create parent rsu_ip directory
path = self.local_file_name[: self.local_file_name.rfind("/")]
Path(path).mkdir(exist_ok=True)
blob_name = self.blob_name if blob_name is None else blob_name
local_file_name = (
self.local_file_name if local_file_name is None else local_file_name
)

# Download blob, defaults to GCP blob storage
bsp = os.environ.get("BLOB_STORAGE_PROVIDER", "GCP")
if bsp == "GCP":
blob_name = self.blob_name if blob_name is None else blob_name
local_file_name = self.local_file_name if local_file_name is None else local_file_name
bspCaseInsensitive = os.environ.get(
"BLOB_STORAGE_PROVIDER", "DOCKER"
).casefold()
if bspCaseInsensitive == "gcp":
return download_blob.download_gcp_blob(blob_name, local_file_name)
elif bspCaseInsensitive == "docker":
return download_blob.download_docker_blob(blob_name, local_file_name)
else:
logging.error("Unsupported blob storage provider")
raise StorageProviderNotSupportedException

# Notifies the firmware manager of the completion status for the upgrade
# success is a boolean
Expand Down Expand Up @@ -72,7 +79,7 @@ def wait_until_online(self):
iter += 1
# 3 minutes pass with no response
return -1

def check_online(self):
iter = 0
# Ping once every second for 5 seconds to verify RSU is online
Expand All @@ -86,12 +93,16 @@ def check_online(self):
time.sleep(1)
# 5 seconds pass with no response
return False

def send_error_email(self, type="Firmware Upgrader", err=""):
try:
email_addresses = os.environ.get("FW_EMAIL_RECIPIENTS").split(",")

subject = f"{self.rsu_ip} Firmware Upgrader Failure" if type == "Firmware Upgrader" else f"{self.rsu_ip} Firmware Upgrader Post Upgrade Script Failure"
subject = (
f"{self.rsu_ip} Firmware Upgrader Failure"
if type == "Firmware Upgrader"
else f"{self.rsu_ip} Firmware Upgrader Post Upgrade Script Failure"
)

for email_address in email_addresses:
emailSender = EmailSender(
Expand All @@ -115,3 +126,8 @@ def send_error_email(self, type="Firmware Upgrader", err=""):
@abc.abstractclassmethod
def upgrade(self):
pass


class StorageProviderNotSupportedException(Exception):
def __init__(self):
super().__init__("Unsupported blob storage provider")
10 changes: 6 additions & 4 deletions services/addons/images/iss_health_check/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,25 @@

This directory contains a microservice that runs within the CV Manager GKE Cluster. The iss_health_checker application populates the CV Manager PostGreSQL database's 'scms_health' table with the current ISS SCMS statuses of all RSUs recorded in the 'rsus' table. These statuses are queried by this application from a provided ISS Green Hills SCMS API endpoint.

The application schedules the iss_health_checker script to run every 6 hours. A new SCMS API access key is generated every run of the script to ensure the access never expires. This is due to a limitation of the SCMS API not allowing permanent access keys. Access keys are stored in GCP Secret Manager to allow for versioning and encrypted storage. The application removes the previous access key from the SCMS API after runtime to reduce clutter of access keys on the API service account.
The application schedules the iss_health_checker script to run every 6 hours. A new SCMS API access key is generated every run of the script to ensure the access never expires. This is due to a limitation of the SCMS API not allowing permanent access keys. Access keys can be stored in GCP Secret Manager to allow for versioning and encrypted storage. The application removes the previous access key from the SCMS API after runtime to reduce clutter of access keys on the API service account.

Currently only GCP is supported to run this application due to a reliance on the GCP Secret Manager. Storing the access keys on a local volume is not recommended due to security vulnerabilities. Feel free to contribute to this application for secret manager equivalent support for other cloud environments.
Currently GCP & Postgres are the only supported storage solutions to run this application. Storing the access keys on a local volume is not recommended due to security vulnerabilities. Feel free to contribute to this application to support other storage solutions.

## Requirements <a name = "requirements"></a>

To properly run the iss_health_checker microservice the following services are also required:

- GCP project and service account with GCP Secret Manager access
- GCP project and service account with GCP Secret Manager access (only required if STORAGE_TYPE is set to 'gcp')
- CV Manager PostgreSQL database with at least one RSU inserted into the 'rsus' table
- Service agreement with ISS Green Hills to have access to the SCMS API REST service endpoint
- iss_health_checker must be deployed in the same environment or K8s cluster as the PostgreSQL database
- iss_health_checker deployment must have access to the internet or at least the SCMS API endpoint

The iss_health_checker microservice expects the following environment variables to be set:

- GOOGLE_APPLICATION_CREDENTIALS - file location for GCP JSON service account key.
- STORAGE_TYPE - Storage solution for the SCMS API access keys. Currently only 'gcp' & 'postgres' are supported.
- GOOGLE_APPLICATION_CREDENTIALS - File location for GCP JSON service account key. Only required if STORAGE_TYPE is set to 'gcp'.
- ISS_KEY_TABLE_NAME - Postgres table name for the ISS SCMS API access keys. Only required if STORAGE_TYPE is set to 'postgres'.
- PROJECT_ID - GCP project ID.
- ISS_API_KEY - Initial ISS SCMS API access key to perform the first run of the script. This access key must not expire before the first runtime.
- ISS_API_KEY_NAME - Human readable reference for the access key within ISS SCMS API. Generated access keys will utilize this same name.
Expand Down
Loading
Loading