diff --git a/Cargo.lock b/Cargo.lock index 4c332000..9f7b3039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -615,15 +615,16 @@ dependencies = [ "env_logger", "file-service-discovery-adapter", "freyja-common", - "http-mock-data-adapter", + "grpc-cloud-adapter", + "grpc-digital-twin-adapter", + "grpc-mapping-adapter", + "grpc-service-discovery-adapter", "in-memory-mock-cloud-adapter", "in-memory-mock-data-adapter", "in-memory-mock-digital-twin-adapter", "in-memory-mock-mapping-adapter", "log", "managed-subscribe-data-adapter", - "mock-digital-twin-adapter", - "mock-mapping-service-adapter", "mockall", "mqtt-data-adapter", "proc-macros", @@ -974,20 +975,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-mock-data-adapter" -version = "0.1.0" -dependencies = [ - "async-trait", - "axum 0.7.4", - "freyja-build-common", - "freyja-common", - "log", - "reqwest", - "serde", - "tokio", -] - [[package]] name = "httparse" version = "1.8.0" @@ -1311,28 +1298,17 @@ dependencies = [ name = "mock-digital-twin" version = "0.1.0" dependencies = [ - "axum 0.7.4", + "async-trait", + "core-protobuf-data-access", "env_logger", "freyja-build-common", "freyja-common", - "http-mock-data-adapter", "log", - "reqwest", - "serde", - "tokio", -] - -[[package]] -name = "mock-digital-twin-adapter" -version = "0.1.0" -dependencies = [ - "async-trait", - "freyja-build-common", - "freyja-common", - "mock-digital-twin", - "reqwest", + "samples-protobuf-data-access", "serde", "tokio", + "tokio-stream", + "tonic", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d1f5eb6b..f16528cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,14 +8,12 @@ resolver = "2" members = [ "adapters/cloud/grpc_cloud_adapter", "adapters/cloud/in_memory_mock_cloud_adapter", - "adapters/data/http_mock_data_adapter", "adapters/data/in_memory_mock_data_adapter", "adapters/data/managed_subscribe_data_adapter", "adapters/data/mqtt_data_adapter", "adapters/data/sample_grpc_data_adapter", "adapters/digital_twin/grpc_digital_twin_adapter", "adapters/digital_twin/in_memory_mock_digital_twin_adapter", - "adapters/digital_twin/mock_digital_twin_adapter", "adapters/mapping/grpc_mapping_adapter", "adapters/mapping/in_memory_mock_mapping_adapter", "adapters/mapping/mock_mapping_service_adapter", @@ -41,7 +39,6 @@ grpc-cloud-adapter = { path = "adapters/cloud/grpc_cloud_adapter" } grpc-digital-twin-adapter = { path = "adapters/digital_twin/grpc_digital_twin_adapter" } grpc-mapping-adapter = { path = "adapters/mapping/grpc_mapping_adapter" } grpc-service-discovery-adapter = { path = "adapters/service_discovery/grpc_service_discovery_adapter" } -http-mock-data-adapter = { path = "adapters/data/http_mock_data_adapter" } in-memory-mock-cloud-adapter = { path = "adapters/cloud/in_memory_mock_cloud_adapter" } in-memory-mock-data-adapter = { path = "adapters/data/in_memory_mock_data_adapter" } in-memory-mock-digital-twin-adapter = { path ="adapters/digital_twin/in_memory_mock_digital_twin_adapter" } @@ -49,7 +46,6 @@ in-memory-mock-mapping-adapter = { path = "adapters/mapping/in_memory_mock_mappi managed-subscribe-data-adapter = { path = "adapters/data/managed_subscribe_data_adapter" } mapping-service-proto = { path = "proto/mapping_service" } mock-digital-twin = { path = "mocks/mock_digital_twin" } -mock-digital-twin-adapter = { path = "adapters/digital_twin/mock_digital_twin_adapter" } mock-mapping-service-adapter = { path = "adapters/mapping/mock_mapping_service_adapter" } mqtt-data-adapter = { path = "adapters/data/mqtt_data_adapter" } proc-macros = { path = "proc_macros" } diff --git a/README.md b/README.md index 2fc13b93..c1699cdd 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,24 @@ This guide uses `apt` as the package manager in the examples. You may need to su ### Using Freyja -To use Freyja, you will need to implement some adapters and write the main executable that will run the Freyja application. +Freyja supports a default runtime that is integrated with a set of standard adapters. To build and run the Standard Freyja Runtime, run the following command: -For a guide on how to get started quickly by running some minimal examples, see the [Quickstart Guide](docs/tutorials/quickstart.md). +```shell +cargo run -p freyja +``` -For more advanced topics on how to implement and use your own adapters, see the [Custom Adapters Guide](docs/tutorials/custom-adapters.md). +This runtime will support the following set of standard adapters: + +- [gRPC Digital Twin Adapter](adapters/digital_twin/grpc_digital_twin_adapter/README.md) (which supports [Eclipse Ibeji](https://github.com/eclipse-ibeji/ibeji)) +- [gRPC Mapping Adapter](adapters/mapping/grpc_mapping_adapter/README.md) +- [gRPC Cloud Adapter](adapters/cloud/grpc_cloud_adapter/README.md) +- [Sample gRPC Data Adapter](adapters/data/sample_grpc_data_adapter/README.md) +- [MQTT Data Adapter](adapters/data/mqtt_data_adapter/README.md) +- [Managed Subscribe Data Adapter](adapters/data/managed_subscribe_data_adapter/README.md) (which supports [Eclipse Agemo](https://github.com/eclipse-chariott/agemo)) +- [File Service Discovery Adapter](adapters/service_discovery/file_service_discovery_adapter/README.md) +- [gRPC Service Discovery Adapter](adapters/service_discovery/grpc_service_discovery_adapter/README.md) (which supports [Eclipse Chariott](https://github.com/eclipse-chariott/chariott)) + +Freyja also supports custom adapter implementations for more specific scenarios. To learn about custom adapters and how to implement and use them, see the [Custom Adapters Guide](docs/tutorials/custom-adapters.md). ## Why "Freyja"? diff --git a/adapters/data/http_mock_data_adapter/Cargo.toml b/adapters/data/http_mock_data_adapter/Cargo.toml deleted file mode 100644 index dd312600..00000000 --- a/adapters/data/http_mock_data_adapter/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -[package] -name = "http-mock-data-adapter" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -async-trait = { workspace = true } -axum = { workspace = true } -freyja-build-common = { workspace = true } -freyja-common = { workspace = true } -log = { workspace = true } -reqwest = { workspace = true } -serde = { workspace = true } -tokio = { workspace = true } - -[build-dependencies] -freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/adapters/data/http_mock_data_adapter/README.md b/adapters/data/http_mock_data_adapter/README.md deleted file mode 100644 index 6017a562..00000000 --- a/adapters/data/http_mock_data_adapter/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# HTTP Mock Data Adapter - -The HTTP Mock Data Adapter mocks the behavior of an adapter which communicates with providers via HTTP. This is intended for use with the [Mock Digital Twin](../../../mocks/mock_digital_twin/). - -## Configuration - -This adapter supports the following configuration settings: - -- `callback_address`: The address for the adapter. This is the address that the Mock Digital Twin will use for callbacks. This should be a URI with no scheme and no port. -- `starting_port`: The starting port number to use when creating adapters. The factory will increment the port it uses each time an adapter is created. - -This adapter supports [config overrides](../../../docs/tutorials/config-overrides.md). The override filename is `http_mock_data_adapter_config.json`, and the default config is located at `res/http_mock_data_adapter_config.default.json`. diff --git a/adapters/data/http_mock_data_adapter/build.rs b/adapters/data/http_mock_data_adapter/build.rs deleted file mode 100644 index 03419593..00000000 --- a/adapters/data/http_mock_data_adapter/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use freyja_build_common::copy_config; - -const CONFIG_FILE_STEM: &str = "http_mock_data_adapter_config"; - -fn main() { - copy_config(CONFIG_FILE_STEM); -} diff --git a/adapters/data/http_mock_data_adapter/res/http_mock_data_adapter_config.default.json b/adapters/data/http_mock_data_adapter/res/http_mock_data_adapter_config.default.json deleted file mode 100644 index d0300019..00000000 --- a/adapters/data/http_mock_data_adapter/res/http_mock_data_adapter_config.default.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "callback_address": "127.0.0.1", - "starting_port": 8801 -} \ No newline at end of file diff --git a/adapters/data/http_mock_data_adapter/src/config.rs b/adapters/data/http_mock_data_adapter/src/config.rs deleted file mode 100644 index e39f500b..00000000 --- a/adapters/data/http_mock_data_adapter/src/config.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde::{Deserialize, Serialize}; - -/// Config for the http mock data adapter -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct Config { - /// The callback address for receiving signals from the mock digital twin - pub callback_address: String, - - /// The starting port number - pub starting_port: u16, -} diff --git a/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter.rs b/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter.rs deleted file mode 100644 index 83f290a2..00000000 --- a/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter.rs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use std::{ - collections::HashMap, - net::SocketAddr, - str::FromStr, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use axum::{ - extract::{Json, State}, - response::{IntoResponse, Response}, - routing::post, - Router, -}; -use log::{debug, error, info}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use tokio::net::TcpListener; - -use crate::{config::Config, GET_OPERATION, SUBSCRIBE_OPERATION}; -use freyja_build_common::config_file_stem; -use freyja_common::{ - config_utils, - data_adapter::{DataAdapter, DataAdapterError, DataAdapterErrorKind, EntityRegistration}, - entity::EntityEndpoint, - not_found, ok, out_dir, - signal_store::SignalStore, -}; - -const CALLBACK_FOR_VALUES_PATH: &str = "/value"; - -/// A request for an entity's value -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct EntityValueRequest { - /// The entity's ID - pub entity_id: String, - - /// The callback uri for a provider to send data back - pub callback_uri: String, -} - -/// A response for an entity's value -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct EntityValueResponse { - // The id of the entity - pub entity_id: String, - - /// The value of the entity - pub value: String, -} - -/// A data adapter that interfaces with the mock digital twin -pub struct HttpMockDataAdapter { - /// Async Reqwest HTTP Client - client: Client, - - /// Local cache for keeping track of which entities this data adapter contains - entity_operation_map: Mutex>, - - /// Shared signal store to push new signal values - signals: Arc, - - /// The adapter configuration - config: Config, - - /// The uri of the provider - provider_uri: String, - - /// The port for the callback server - callback_server_port: u16, -} - -impl HttpMockDataAdapter { - /// Constructs a callback uri using the configured address and port for this adapter - fn construct_callback_uri(&self) -> String { - format!( - "http://{}:{}{CALLBACK_FOR_VALUES_PATH}", // Devskim: ignore DS137138 - self.config.callback_address, self.callback_server_port, - ) - } - - /// Receive signal handler for the value listener to handle incoming values - /// - /// # Arguments - /// - `signals`: the shared signal store - /// - `value`: the value received from a provider - async fn receive_value_handler( - State(signals): State>, - Json(value): Json, - ) -> Response { - let EntityValueResponse { entity_id, value } = value; - - debug!("Received a response for entity id {entity_id} with the value {value}"); - - match signals.set_value(entity_id, value) { - Some(_) => ok!(), - None => not_found!(), - } - } - - /// Set the port for the callback server. - /// - /// # Arguments - /// - `port`: the new port to use - pub fn set_callback_server_port(&mut self, port: u16) { - self.callback_server_port = port; - } -} - -#[async_trait] -impl DataAdapter for HttpMockDataAdapter { - /// Creates a data adapter - /// - /// # Arguments - /// - `provider_uri`: the provider uri for accessing an entity's information - /// - `signals`: the shared signal store - fn create_new(provider_uri: &str, signals: Arc) -> Result - where - Self: Sized, - { - let config: Config = config_utils::read_from_files( - config_file_stem!(), - config_utils::JSON_EXT, - out_dir!(), - DataAdapterError::io, - DataAdapterError::deserialize, - )?; - - Ok(Self { - signals, - callback_server_port: config.starting_port, - config, - provider_uri: provider_uri.to_string(), - client: reqwest::Client::new(), - entity_operation_map: Mutex::new(HashMap::new()), - }) - } - - /// Starts a data adapter - async fn start(&self) -> Result<(), DataAdapterError> { - let address = format!( - "{}:{}", - self.config.callback_address, self.callback_server_port - ); - let server_endpoint_addr = - SocketAddr::from_str(&address).map_err(DataAdapterError::parse)?; - // Start a listener server to have a digital twin provider push data to the callback address - // http://{callback_address}:{callback_server_port}/value - // POST request where the json body is GetSignalValueResponse - // Set up router path - let app = Router::new() - .route(CALLBACK_FOR_VALUES_PATH, post(Self::receive_value_handler)) - .with_state(self.signals.clone()); - - // Run the listener - let listener = TcpListener::bind(&server_endpoint_addr) - .await - .map_err(DataAdapterError::communication)?; - - tokio::spawn(async move { - let _ = axum::serve(listener, app).await; - }); - - info!("Http Data Adapter listening at http://{address}"); // Devskim: ignore DS137138 - - Ok(()) - } - - /// Sends a request to a provider for obtaining the value of an entity - /// - /// # Arguments - /// - `entity_id`: the entity id that needs a value - async fn send_request_to_provider(&self, entity_id: &str) -> Result<(), DataAdapterError> { - let operation_result; - { - let lock = self.entity_operation_map.lock().unwrap(); - operation_result = lock.get(entity_id).cloned(); - } - - if operation_result.is_none() { - return Err(DataAdapterError::unknown(format!( - "Entity {entity_id} does not have an operation registered" - ))); - } - - // Only need to handle Get operations since subscribe has already happened - let operation = operation_result.unwrap(); - if operation == GET_OPERATION { - info!("Sending a get request to {entity_id}"); - - let request = EntityValueRequest { - entity_id: String::from(entity_id), - callback_uri: self.construct_callback_uri(), - }; - let server_endpoint = self.provider_uri.clone(); - - self.client - .post(&server_endpoint) - .json(&request) - .send() - .await - .map_err(DataAdapterError::communication)? - .error_for_status() - .map_err(DataAdapterError::unknown)?; - } - - Ok(()) - } - - /// Registers an entity id to a local cache inside a data adapter to keep track of which entities a data adapter contains. - /// If the operation is Subscribe for an entity, the expectation is subscribe will happen in this function after registering an entity. - /// - /// # Arguments - /// - `entity_id`: the entity id to add - /// - `endpoint`: the endpoint that this entity supports - async fn register_entity( - &self, - entity_id: &str, - endpoint: &EntityEndpoint, - ) -> Result { - // Prefer subscribe if present - let selected_operation = { - let mut result = None; - for operation in endpoint.operations.iter() { - if operation == SUBSCRIBE_OPERATION { - result = Some(SUBSCRIBE_OPERATION); - break; - } else if operation == GET_OPERATION { - // Set result, but don't break the loop in case there's a subscribe operation later in the list - result = Some(GET_OPERATION); - } - } - - result.ok_or::(DataAdapterErrorKind::OperationNotSupported.into())? - }; - - self.entity_operation_map - .lock() - .unwrap() - .insert(String::from(entity_id), String::from(selected_operation)); - - if selected_operation == SUBSCRIBE_OPERATION { - let request = EntityValueRequest { - entity_id: String::from(entity_id), - callback_uri: self.construct_callback_uri(), - }; - - let subscribe_endpoint_for_entity = self.provider_uri.clone(); - let result = self - .client - .post(&subscribe_endpoint_for_entity) - .json(&request) - .send() - .await - .map_err(DataAdapterError::communication)? - .error_for_status() - .map_err(DataAdapterError::unknown); - - // Remove from map if the subscribe operation fails - if result.is_err() { - error!("Cannot subscribe to {entity_id} due to {result:?}"); - self.entity_operation_map.lock().unwrap().remove(entity_id); - } - } - - Ok(EntityRegistration::Registered) - } -} diff --git a/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter_factory.rs b/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter_factory.rs deleted file mode 100644 index 8e7f041c..00000000 --- a/adapters/data/http_mock_data_adapter/src/http_mock_data_adapter_factory.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use std::sync::{ - atomic::{AtomicU16, Ordering}, - Arc, -}; - -use freyja_build_common::config_file_stem; -use freyja_common::{ - config_utils, - data_adapter::{DataAdapter, DataAdapterError, DataAdapterFactory}, - entity::{Entity, EntityEndpoint}, - out_dir, - signal_store::SignalStore, -}; - -use crate::{ - config::Config, http_mock_data_adapter::HttpMockDataAdapter, GET_OPERATION, HTTP_PROTOCOL, - SUBSCRIBE_OPERATION, -}; - -/// Factory for creating HttpMockDataAdapters -pub struct HttpMockDataAdapterFactory { - /// The current port to use for a new adapter - current_port: AtomicU16, -} - -impl DataAdapterFactory for HttpMockDataAdapterFactory { - /// Create a new `HttpMockDataAdapterFactory` - fn create_new() -> Result { - let config: Config = config_utils::read_from_files( - config_file_stem!(), - config_utils::JSON_EXT, - out_dir!(), - DataAdapterError::io, - DataAdapterError::deserialize, - )?; - - Ok(Self { - current_port: AtomicU16::new(config.starting_port), - }) - } - - /// Check to see whether this factory can create a data adapter for the requested entity. - /// Returns the first endpoint found that is supported by this factory. - /// - /// # Arguments - /// - `entity`: the entity to check for compatibility - fn is_supported(&self, entity: &Entity) -> Option { - entity.is_supported(&[HTTP_PROTOCOL], &[GET_OPERATION, SUBSCRIBE_OPERATION]) - } - - /// Create a new data adapter - /// - /// # Arguments - /// - `provider_uri`: the provider URI to associate with this adapter - /// - `signals`: the shared signal store - fn create_adapter( - &self, - provider_uri: &str, - signals: Arc, - ) -> Result, DataAdapterError> { - let mut adapter = HttpMockDataAdapter::create_new(provider_uri, signals)?; - adapter.set_callback_server_port(self.current_port.fetch_add(1, Ordering::SeqCst)); - Ok(Arc::new(adapter)) - } -} diff --git a/adapters/data/http_mock_data_adapter/src/lib.rs b/adapters/data/http_mock_data_adapter/src/lib.rs deleted file mode 100644 index f10efc74..00000000 --- a/adapters/data/http_mock_data_adapter/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -mod config; -pub mod http_mock_data_adapter; -pub mod http_mock_data_adapter_factory; - -const HTTP_PROTOCOL: &str = "http"; -const GET_OPERATION: &str = "Get"; -const SUBSCRIBE_OPERATION: &str = "Subscribe"; diff --git a/adapters/data/sample_grpc_data_adapter/README.md b/adapters/data/sample_grpc_data_adapter/README.md index ce6d9fd0..4c62de87 100644 --- a/adapters/data/sample_grpc_data_adapter/README.md +++ b/adapters/data/sample_grpc_data_adapter/README.md @@ -1,6 +1,6 @@ # Sample gRPC Data Adapter -The Sample gRPC Data Adapter interfaces with providers which support gRPC. It acts as a consumer for digital twin providers. This adapter supports the `Get` and `Subscribe` operations as defined for the [Ibeji mixed sample](https://github.com/eclipse-ibeji/ibeji/tree/main/samples/mixed). To use this adapter with other providers, those providers will need to support the same API(s) as the provider in that sample (see [Integrating with this Adapter](#integrating-with-this-adapter) for more information). +The Sample gRPC Data Adapter interfaces with providers which support gRPC. It acts as a consumer for digital twin providers. This adapter supports the `Get` and `Subscribe` operations as defined for the [Ibeji mixed sample](https://github.com/eclipse-ibeji/ibeji/tree/main/samples/mixed), which are also used in the [Mock Digital Twin](../../../mocks/mock_digital_twin/README.md). To use this adapter with other providers, those providers will need to support the same API(s) as the provider in that sample (see [Integrating with this Adapter](#integrating-with-this-adapter) for more information). ## Configuration diff --git a/adapters/digital_twin/mock_digital_twin_adapter/Cargo.toml b/adapters/digital_twin/mock_digital_twin_adapter/Cargo.toml deleted file mode 100644 index 2cdd356c..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -[package] -name = "mock-digital-twin-adapter" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -async-trait = { workspace = true } -freyja-build-common = { workspace = true } -freyja-common = { workspace = true } -mock-digital-twin = { workspace = true } -reqwest = { workspace = true } -serde = { workspace = true } -tokio = { workspace = true } - -[build-dependencies] -freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/adapters/digital_twin/mock_digital_twin_adapter/README.md b/adapters/digital_twin/mock_digital_twin_adapter/README.md deleted file mode 100644 index d6563bce..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Mock Digital Twin Adapter - -The Mock Digital Twin Adapter acts as a client for the [Mock Digital Twin](../../../mocks/mock_digital_twin/README.md) when getting entity info with the `find_by_id` API. This library contains an implementation of the `DigitalTwinAdapter` trait from the contracts. - -## Config - -This adapter supports the following configuration settings: - -- `digital_twin_service_uri`: the base uri for the Mock Digital Twin Service - -This adapter supports [config overrides](../../../docs/tutorials/config-overrides.md). The override filename is `mock_digital_twin_adapter_config.json`, and the default config is located at `res/mock_digital_twin_adapter_config.default.json`. diff --git a/adapters/digital_twin/mock_digital_twin_adapter/build.rs b/adapters/digital_twin/mock_digital_twin_adapter/build.rs deleted file mode 100644 index ced96fd8..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use freyja_build_common::copy_config; - -const CONFIG_FILE_STEM: &str = "mock_digital_twin_adapter_config"; - -fn main() { - copy_config(CONFIG_FILE_STEM); -} diff --git a/adapters/digital_twin/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json b/adapters/digital_twin/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json deleted file mode 100644 index 4786c773..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "digital_twin_service_uri": "http://127.0.0.1:8800" -} \ No newline at end of file diff --git a/adapters/digital_twin/mock_digital_twin_adapter/src/config.rs b/adapters/digital_twin/mock_digital_twin_adapter/src/config.rs deleted file mode 100644 index 30a7bc1f..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/src/config.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use serde::{Deserialize, Serialize}; - -/// Config for the mock digital twin adapter -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Config { - /// the base uri for the digital twin service - pub digital_twin_service_uri: String, -} diff --git a/adapters/digital_twin/mock_digital_twin_adapter/src/lib.rs b/adapters/digital_twin/mock_digital_twin_adapter/src/lib.rs deleted file mode 100644 index 1955a786..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -mod config; -pub mod mock_digital_twin_adapter; diff --git a/adapters/digital_twin/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs b/adapters/digital_twin/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs deleted file mode 100644 index b94fdb6e..00000000 --- a/adapters/digital_twin/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use std::sync::Arc; - -use async_trait::async_trait; -use reqwest::Client; -use tokio::sync::Mutex; - -use crate::config::Config; -use freyja_build_common::config_file_stem; -use freyja_common::{ - config_utils, - digital_twin_adapter::{ - DigitalTwinAdapter, DigitalTwinAdapterError, FindByIdRequest, FindByIdResponse, - }, - out_dir, - service_discovery_adapter_selector::ServiceDiscoveryAdapterSelector, -}; -use mock_digital_twin::ENTITY_QUERY_PATH; - -/// Mocks a Digital Twin Adapter that calls the mocks/mock_digital_twin -/// to get entity access info. -pub struct MockDigitalTwinAdapter { - /// The adapter config - config: Config, - - /// Async Reqwest HTTP Client - client: Client, -} - -impl MockDigitalTwinAdapter { - /// Creates a new MockDigitalTwinAdapter with the specified config - /// - /// # Arguments - /// - `config`: the config to use - pub fn from_config(config: Config) -> Result { - Ok(Self { - config, - client: Client::new(), - }) - } - - /// Helper to map HTTP error codes to our own error type - /// - /// # Arguments - /// - `error`: the HTTP error to translate - fn map_status_err(error: reqwest::Error) -> DigitalTwinAdapterError { - match error.status() { - Some(reqwest::StatusCode::NOT_FOUND) => { - DigitalTwinAdapterError::entity_not_found(error) - } - _ => DigitalTwinAdapterError::communication(error), - } - } -} - -#[async_trait] -impl DigitalTwinAdapter for MockDigitalTwinAdapter { - /// Creates a new instance of a MockDigitalTwinAdapter - /// - /// # Arguments - /// - `_selector`: the service discovery adapter selector to use (unused by this adapter) - fn create_new( - _selector: Arc>, - ) -> Result { - let config = config_utils::read_from_files( - config_file_stem!(), - config_utils::JSON_EXT, - out_dir!(), - DigitalTwinAdapterError::io, - DigitalTwinAdapterError::deserialize, - )?; - - Self::from_config(config) - } - - /// Gets the info of an entity via an HTTP request. - /// - /// # Arguments - /// - `request`: the request to send to the mock digital twin server - async fn find_by_id( - &self, - request: FindByIdRequest, - ) -> Result { - let target = format!( - "{}{ENTITY_QUERY_PATH}{}", - self.config.digital_twin_service_uri, request.entity_id - ); - - self.client - .get(&target) - .send() - .await - .map_err(DigitalTwinAdapterError::communication)? - .error_for_status() - .map_err(Self::map_status_err)? - .json::() - .await - .map_err(DigitalTwinAdapterError::deserialize) - } -} diff --git a/adapters/mapping/in_memory_mock_mapping_adapter/res/mock_mapping_config.default.json b/adapters/mapping/in_memory_mock_mapping_adapter/res/mock_mapping_config.default.json index aabbc275..710e236b 100644 --- a/adapters/mapping/in_memory_mock_mapping_adapter/res/mock_mapping_config.default.json +++ b/adapters/mapping/in_memory_mock_mapping_adapter/res/mock_mapping_config.default.json @@ -4,7 +4,7 @@ "begin": 2, "end": null, "value": { - "source": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", + "source": "dtmi:sdv:HVAC:AmbientAirTemperature;1", "target": { "model_id": "dtmi:sdv:Cloud:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", "instance_id": "hvac", @@ -22,7 +22,7 @@ "begin": 2, "end": null, "value": { - "source": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", + "source": "dtmi:sdv:HVAC:IsAirConditioningActive;1", "target": { "model_id": "dtmi:sdv:Cloud:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", "instance_id": "hvac", @@ -37,7 +37,7 @@ "begin": 4, "end": null, "value": { - "source": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", + "source": "dtmi:sdv:OBD:HybridBatteryRemaining;1", "target": { "model_id": "dtmi:sdv:Cloud:Vehicle:OBD:HybridBatteryRemaining;1", "instance_id": "obd", diff --git a/common/src/message_utils.rs b/common/src/message_utils.rs index f9ddde87..2d4538fc 100644 --- a/common/src/message_utils.rs +++ b/common/src/message_utils.rs @@ -32,7 +32,7 @@ pub fn parse_value(value: String) -> String { let property_map = match v.as_object() { Some(o) => o, None => { - warn!("Could not parse value as JSON object"); + debug!("Could not parse value as JSON object"); return value; } }; diff --git a/docs/tutorials/custom-adapters.md b/docs/tutorials/custom-adapters.md index c006fbc5..012b084d 100644 --- a/docs/tutorials/custom-adapters.md +++ b/docs/tutorials/custom-adapters.md @@ -2,8 +2,6 @@ Freyja allows users to bring their own implementations of various traits which interface with external components. This is achieved by exposing the core functionality of Freyja as a library function and requiring users to author the final binary package to link everything together. -For more examples of Freyja adapters and applications, see the [Ibeji Example Applications repository](https://github.com/eclipse-ibeji/ibeji-example-applications). - ## How to Author a Custom Adapter Freyja supports custom implementations of the `DigitalTwinAdapter`, `CloudAdapter`, `MappingAdapter`, `DataAdapter`, `DataAdapterFactory`, and `ServiceDiscoveryAdapter` interfaces. To refer to these traits in your implementation, you will need to take a dependency on the `freyja-common` crate. The following `Cargo.toml` snippet shows how you can include this dependency: @@ -25,6 +23,40 @@ freyja = { git = "https://github.com/eclipse-ibeji/freyja" } tokio = { version = "1.0", features = ["macros"] } ``` -In most cases, the `main.rs` file can be implemented using the `freyja_main!` macro which will take care of writing some boilerplate code for you. This macro only needs adapter type names as input and will generate the main function signature and body. For an example of how to use this macro, see the code for the [in-memory example](../../freyja/examples/in-memory.rs) or the [mock example](../../freyja/examples/mocks.rs). +In most cases the `main.rs` file can be implemented using the `freyja_main!` macro, which will take care of writing some boilerplate code for you. This macro only needs adapter type names as input and will generate the main function signature and body. For an example of how to use this macro, see the code for the [Standard Freyja Runtime](../../freyja/src/main.rs). If you have a more complex scenario that requires some additional setup before running the `freyja_main` function, you can instead invoke it manually without using the macro. For an example of how to use this function and how to manually author the main function, see the code for the [in-memory-with-fn example](../../freyja/examples/in-memory-with-fn.rs). + +For more examples of Freyja adapters and applications, see the [Ibeji Example Applications repository](https://github.com/eclipse-ibeji/ibeji-example-applications/tree/main/freyja_apps). + +## Appendix A + +This appendix lists the adapters that are provided in this repository. These can be used as samples for writing your own adapters, and can be mixed and matched with your custom adapters. + +### Digital Twin Adapters + +- [In-Memory Mock Digital Twin Adapter](../../adapters/digital_twin/in_memory_mock_digital_twin_adapter/README.md): Emulates a Digital Twin Service entirely within the memory of the Freyja application. +- [gRPC Digital Twin Adapter](../../adapters/digital_twin/grpc_digital_twin_adapter/README.md): Communicates with a digital twin service that implements the [Ibeji In-Vehicle Digital Twin Service API](https://github.com/eclipse-ibeji/ibeji/blob/main/interfaces/invehicle_digital_twin/v1/invehicle_digital_twin.proto). This is a "standard adapter" that is suitable for use in production scenarios. + +### Mapping Adapters + +- [In-Memory Mock Mapping Adapter](../../adapters/mapping/in_memory_mock_mapping_adapter/README.md): Emulates a mapping service entirely within the memory of the Freyja application. +- [Mock Mapping Service Adapter](../../adapters/mapping/mock_mapping_service_adapter/README.md): Communicates with the [Mock Mapping Service](../../mocks/mock_mapping_service/README.md), which is an executable that mocks a Mapping Service. The behavior is very similar to the in-memory mock, but the application is interactive and allows users to add or remove mappings by pressing Enter to advance through configurable states. +- [gRPC Mapping Adapter](../../adapters/mapping/grpc_mapping_adapter/README.md): Communicates with a mapping service that implements the [Mapping Service API](../../interfaces/mapping_service/v1/mapping_service.proto). This is a "standard adapter" that is suitable for use in production scenarios. + +### Cloud Adapters + +- [In-Memory Mock Cloud Adapter](../../adapters/cloud/in_memory_mock_cloud_adapter/README.md): Emulates a Cloud Connector entirely within the memory of the Freyja application. Data emitted to this adapter will be printed to the console window. +- [gRPC Cloud Adapter](../../adapters/cloud/grpc_cloud_adapter/README.md): Communicates with a cloud connector that implements the [Cloud Connector API](../../interfaces/cloud_connector/v1/cloud_connector.proto). This is a "standard adapter" that is suitable for use in production scenarios. + +### Data Adapters + +- [In-Memory Mock Data Adapter](../../adapters/data/in_memory_mock_data_adapter/README.md): Interfaces with the In-Memory Mock Digital Twin Adapter and intended for use with it. +- [Sample gRPC Data Adapter](../../adapters/data/sample_grpc_data_adapter/README.md): Interfaces with providers that communicate via gRPC. Integrated with specific Ibeji samples and the Mock Digital Twin. +- [MQTT Data Adapter](../../adapters/data/mqtt_data_adapter/README.md): Interfaces with providers that communicate via MQTT. +- [Managed Subscribe Data Adapter](../../adapters/data/managed_subscribe_data_adapter/README.md): Interfaces with providers that leverage the managed subscribe feature of Ibeji. This adapter typically requires the MQTT Data Adapter. + +### Service Discovery Adapters + +- [File Service Discovery Adapter](../../adapters/service_discovery/file_service_discovery_adapter/README.md): Uses a static config file to define service URIs. This is a "standard adapter" that is suitable for use in production scenarios. +- [gRPC Service Discovery Adapter](../../adapters/service_discovery/grpc_service_discovery_adapter/README.md): Communicates with a service discovery system that implements the [Chariott Service Registry API](https://github.com/eclipse-chariott/chariott/blob/main/service_discovery/proto/core/v1/service_registry.proto). This is a "standard adapter" that is suitable for use in production scenarios. diff --git a/docs/tutorials/quickstart.md b/docs/tutorials/quickstart.md deleted file mode 100644 index c739ad5e..00000000 --- a/docs/tutorials/quickstart.md +++ /dev/null @@ -1,80 +0,0 @@ - -# Freyja Quickstart Guide - -The Freyja project provides some example adapter implementations that can be used to get started quickly and experiment with Freyja without needing to write any code. For more information about the example adapters that Freyja provides, including links to documentation on how to configure them for more complex scenarios, see the [Appendix](#appendix-a). - -## Example Scenarios - -### In-Memory Example - -This standalone example uses the in-memory mock adapters to emulate the behavior of external components from entirely within the memory of the Freyja application. This example does not require any other services to be configured or running in order to function properly, and there will be no external API calls made by the Freyja core components. - -This example is ideal for getting started with minimal effort or configuration. However, it does not offer precise control over how the mocked interfaces behave during runtime. This example is most commonly used for testing scenarios. - -To run this sample, run the following command. This will build the example (if necessary) and then execute it: - -```shell -cargo run --example in-memory -``` - -Note that there is also an `in-memory-with-fn` example with identical behavior. The difference between these two examples is that they show different ways of integrating the same adapters with the Freyja core components, which is an advanced topic covered in the [Custom Adapters Guide](./custom-adapters.md). - -### Mock Services Example - -This example uses the Mock Digital Twin Service and Mock Mapping Service. The behavior is very similar to the in-memory example, but with two key differences: - -1. The mapping adapter and digital twin adapter function as clients to external services rather than handling everything in-memory. These external services are mock versions of the mapping and digital twin services which run as binaries on the same device. -1. The mock services allow for more precise control over when their state changes. Users can advance the state of the applications by interacting with their terminal interfaces. - -This example is ideal if you need to manually control when signals or mappings are added or removed from the application, thus affecting what data gets emitted by Freyja. This example is most commonly used for demo scenarios. - -To run this sample, follow these steps: - -1. Run the Mock Digital Twin Service. To do so, open a new terminal window and run the following: - - cargo run --bin mock-digital-twin - - Note that with the default configuration, the mock is initialized with no entities activated. Whenever you press Enter in the mock's terminal window, the mock's state will change to include additional entities that will be returned by the `find_by_id` API. Using the default configuration, up to three entities can be added one at a time when pressing Enter. - -1. Run the Mock Mapping Service. To do so, open a new terminal window and run the following: - - cargo run --bin mock-mapping-service - - Note that with the default configuration, the mock is initialized with no mappings activated. Whenever you press Enter in the mock's terminal window, the mock's state will change to include additional mappings that will be returned by the `get_mapping` API. Using the default configuration, up to three mappings can be added one at a time when pressing Enter. - -1. Run the example. To do so, run the following in the original terminal window. This will build the example (if necessary) and then execute it: - - cargo run --example mocks - -# Appendix A - -This appendix lists the adapters that are provided in this repository. - -## Digital Twin Adapters - -- [In-Memory Mock Digital Twin Adapter](../../adapters/digital_twin/in_memory_mock_digital_twin_adapter/README.md): Emulates a Digital Twin Service entirely within the memory of the Freyja application. -- [Mock Digital Twin Adapter](../../adapters/digital_twin/mock_digital_twin_adapter/README.md): Communicates with the [Mock Digital Twin](../../mocks/mock_digital_twin/README.md), which is an executable that mocks the Digital Twin Service. The behavior is very similar to the in-memory mock, but the application is interactive and allows users to add or remove entities from the mocked digital twin by pressing enter to advance through configurable states. -- [gRPC Digital Twin Adapter](../../adapters/digital_twin/grpc_digital_twin_adapter/README.md): Communicates with a digital twin service that implements the [Ibeji In-Vehicle Digital Twin Service API](https://github.com/eclipse-ibeji/ibeji/blob/main/interfaces/invehicle_digital_twin/v1/invehicle_digital_twin.proto). This is a "standard adapter" that is suitable for use in production scenarios. - -## Mapping Adapters - -- [In-Memory Mock Mapping Adapter](../../adapters/mapping/in_memory_mock_mapping_adapter/README.md): Emulates a mapping service entirely within the memory of the Freyja application. -- [Mock Mapping Service Adapter](../../adapters/mapping/mock_mapping_service_adapter/README.md): Communicates with the [Mock Mapping Service](../../mocks/mock_mapping_service/README.md), which is an executable that mocks a Mapping Service. The behavior is very similar to the in-memory mock, but the application is interactive and allows users to add or remove mappings by pressing enter to advance through configurable states. - -## Cloud Adapters - -- [In-Memory Mock Cloud Adapter](../../adapters/cloud/in_memory_mock_cloud_adapter/README.md): Emulates a Cloud Connector entirely within the memory of the Freyja application. When data is emitted to this adapter it will be printed to the console window. -- [gRPC Cloud Adapter](../../adapters/cloud/grpc_cloud_adapter/README.md): Communicates with a cloud connector that implements the [cloud connector protobuf service](../../interfaces/cloud_connector/v1/cloud_connector.proto). This is a "standard adapter" that is suitable for use in production scenarios. - -## Data Adapters - -- [In-Memory Mock Data Adapter](../../adapters/data/in_memory_mock_data_adapter/README.md): Interfaces with the In-Memory Mock Digital Twin Adapter and intended for use with it. -- [HTTP Mock Data Adapter](../../adapters/data/http_mock_data_adapter/README.md): Interfaces with the Mock Digital Twin Adapter and intended for use with it. -- [Sample gRPC Data Adapter](../../adapters/data/sample_grpc_data_adapter/README.md): Interfaces with providers that communicate via gRPC. Integrated with specific Ibeji samples. -- [MQTT Data Adapter](../../adapters/data/mqtt_data_adapter/README.md): Interfaces with providers that communicate via MQTT. -- [Managed Subscribe Data Adapter](../../adapters/data/managed_subscribe_data_adapter/README.md): Interfaces with providers that leverage the managed subscribe feature of Ibeji. - -## Service Discovery Adapters - -- [File Service Discovery Adapter](../../adapters/service_discovery/file_service_discovery_adapter/README.md): Uses a static config file to define service URIs. This is a "standard adapter" that is suitable for use in production scenarios. -- [gRPC Service Discovery Adapter](../../adapters/service_discovery/grpc_service_discovery_adapter/README.md): Communicates with a service discovery system that implements the [Chariott Service Registry API](https://github.com/eclipse-chariott/chariott/blob/main/service_discovery/proto/core/v1/service_registry.proto). This is a "standard adapter" that is suitable for use in production scenarios. diff --git a/freyja/Cargo.toml b/freyja/Cargo.toml index be331ac0..e604ddd4 100644 --- a/freyja/Cargo.toml +++ b/freyja/Cargo.toml @@ -11,9 +11,17 @@ license = "MIT" [dependencies] async-trait = { workspace = true } env_logger = { workspace = true } +file-service-discovery-adapter = { workspace = true } freyja-common = { workspace = true } +grpc-cloud-adapter = { workspace = true } +grpc-digital-twin-adapter = { workspace = true } +grpc-mapping-adapter = { workspace = true } +grpc-service-discovery-adapter = { workspace = true } log = { workspace = true } +managed-subscribe-data-adapter = { workspace = true } +mqtt-data-adapter = { workspace = true } proc-macros = { workspace = true } +sample-grpc-data-adapter = { workspace = true } time = { workspace = true } tokio = { workspace = true } @@ -23,14 +31,7 @@ mockall = { workspace = true } async-trait = { workspace = true } # Dependencies for examples -file-service-discovery-adapter = { workspace = true } in-memory-mock-cloud-adapter = { workspace = true } in-memory-mock-digital-twin-adapter = { workspace = true } -in-memory-mock-mapping-adapter = { workspace = true } -mock-digital-twin-adapter = { workspace = true } -mock-mapping-service-adapter = { workspace = true } -sample-grpc-data-adapter = { workspace = true } -http-mock-data-adapter = { workspace = true } in-memory-mock-data-adapter = { workspace = true } -managed-subscribe-data-adapter = { workspace = true } -mqtt-data-adapter = { workspace = true } \ No newline at end of file +in-memory-mock-mapping-adapter = { workspace = true } \ No newline at end of file diff --git a/freyja/examples/in-memory-with-fn.rs b/freyja/examples/in-memory-with-fn.rs index 0ba5a132..90058843 100644 --- a/freyja/examples/in-memory-with-fn.rs +++ b/freyja/examples/in-memory-with-fn.rs @@ -6,7 +6,6 @@ use file_service_discovery_adapter::file_service_discovery_adapter::FileServiceD use freyja_common::{ data_adapter::DataAdapterFactory, service_discovery_adapter::ServiceDiscoveryAdapter, }; -use http_mock_data_adapter::http_mock_data_adapter_factory::HttpMockDataAdapterFactory; use in_memory_mock_cloud_adapter::in_memory_mock_cloud_adapter::InMemoryMockCloudAdapter; use in_memory_mock_data_adapter::in_memory_mock_data_adapter_factory::InMemoryMockDataAdapterFactory; use in_memory_mock_digital_twin_adapter::in_memory_mock_digital_twin_adapter::InMemoryMockDigitalTwinAdapter; @@ -25,10 +24,6 @@ async fn main() -> Result<(), Box> { SampleGRPCDataAdapterFactory::create_new() .expect("Could not create SampleGRPCDataAdapterFactory"), ), - Box::new( - HttpMockDataAdapterFactory::create_new() - .expect("Could not create HttpMockDataAdapterFactory"), - ), Box::new( InMemoryMockDataAdapterFactory::create_new() .expect("Could not create InMemoryMockDataAdapterFactory"), diff --git a/freyja/examples/in-memory.rs b/freyja/examples/in-memory.rs deleted file mode 100644 index 3e7cb65c..00000000 --- a/freyja/examples/in-memory.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use file_service_discovery_adapter::file_service_discovery_adapter::FileServiceDiscoveryAdapter; -use in_memory_mock_cloud_adapter::in_memory_mock_cloud_adapter::InMemoryMockCloudAdapter; -use in_memory_mock_data_adapter::in_memory_mock_data_adapter_factory::InMemoryMockDataAdapterFactory; -use in_memory_mock_digital_twin_adapter::in_memory_mock_digital_twin_adapter::InMemoryMockDigitalTwinAdapter; -use in_memory_mock_mapping_adapter::in_memory_mock_mapping_adapter::InMemoryMockMappingAdapter; - -freyja::freyja_main! { - InMemoryMockDigitalTwinAdapter, - InMemoryMockCloudAdapter, - InMemoryMockMappingAdapter, - [InMemoryMockDataAdapterFactory], - [FileServiceDiscoveryAdapter], -} diff --git a/freyja/examples/mocks.rs b/freyja/examples/mocks.rs deleted file mode 100644 index e2e1f5a3..00000000 --- a/freyja/examples/mocks.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use file_service_discovery_adapter::file_service_discovery_adapter::FileServiceDiscoveryAdapter; -use http_mock_data_adapter::http_mock_data_adapter_factory::HttpMockDataAdapterFactory; -use in_memory_mock_cloud_adapter::in_memory_mock_cloud_adapter::InMemoryMockCloudAdapter; -use mock_digital_twin_adapter::mock_digital_twin_adapter::MockDigitalTwinAdapter; -use mock_mapping_service_adapter::mock_mapping_service_adapter::MockMappingServiceAdapter; - -freyja::freyja_main! { - MockDigitalTwinAdapter, - InMemoryMockCloudAdapter, - MockMappingServiceAdapter, - [HttpMockDataAdapterFactory], - [FileServiceDiscoveryAdapter], -} diff --git a/freyja/src/main.rs b/freyja/src/main.rs new file mode 100644 index 00000000..ab389da5 --- /dev/null +++ b/freyja/src/main.rs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use file_service_discovery_adapter::file_service_discovery_adapter::FileServiceDiscoveryAdapter; +use grpc_cloud_adapter::grpc_cloud_adapter::GRPCCloudAdapter; +use grpc_digital_twin_adapter::grpc_digital_twin_adapter::GRPCDigitalTwinAdapter; +use grpc_mapping_adapter::grpc_mapping_adapter::GRPCMappingAdapter; +use grpc_service_discovery_adapter::grpc_service_discovery_adapter::GRPCServiceDiscoveryAdapter; +use managed_subscribe_data_adapter::managed_subscribe_data_adapter_factory::ManagedSubscribeDataAdapterFactory; +use mqtt_data_adapter::mqtt_data_adapter_factory::MqttDataAdapterFactory; +use sample_grpc_data_adapter::sample_grpc_data_adapter_factory::SampleGRPCDataAdapterFactory; + +freyja::freyja_main! { + GRPCDigitalTwinAdapter, + GRPCCloudAdapter, + GRPCMappingAdapter, + [SampleGRPCDataAdapterFactory, MqttDataAdapterFactory, ManagedSubscribeDataAdapterFactory], + [GRPCServiceDiscoveryAdapter, FileServiceDiscoveryAdapter], +} diff --git a/mocks/mock_digital_twin/Cargo.toml b/mocks/mock_digital_twin/Cargo.toml index 9209b727..e2807808 100644 --- a/mocks/mock_digital_twin/Cargo.toml +++ b/mocks/mock_digital_twin/Cargo.toml @@ -9,15 +9,17 @@ edition = "2021" license = "MIT" [dependencies] -axum = { workspace = true } +async-trait = { workspace = true } +core-protobuf-data-access = { workspace = true } env_logger = { workspace = true } freyja-build-common = { workspace = true } freyja-common = { workspace = true } log = { workspace = true } -reqwest = { workspace = true } +samples-protobuf-data-access = { workspace = true } serde = { workspace = true } tokio = { workspace = true } -http-mock-data-adapter = { workspace = true } +tokio-stream = { workspace = true } +tonic = { workspace = true } [build-dependencies] freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/mocks/mock_digital_twin/README.md b/mocks/mock_digital_twin/README.md index 341b7fcf..aa3ed7fd 100644 --- a/mocks/mock_digital_twin/README.md +++ b/mocks/mock_digital_twin/README.md @@ -1,12 +1,14 @@ # Mock Digital Twin -The Mock Digital Twin mocks the behavior of the in-vehicle digital twin services (e.g. Ibeji) as a separate application. This enables functionality similar to the in-memory mock, but with finer control over the behavior of the mocked data. +The Mock Digital Twin mocks the behavior of an in-vehicle digital twin service (such as Ibeji) and providers. This enables functionality similar to the [In-Memory Mock Digital Twin Adapter](../../adapters/digital_twin/in_memory_mock_digital_twin_adapter/README.md), but with finer control over the behavior of the mocked data. + +The Mock Digital Twin is integrated with the [Sample gRPC Data Adapter](../../adapters/data/sample_grpc_data_adapter/README.md), which must be enabled when using this application with Freyja. ## Configuration This mock supports the following configuration: -- `digital_twin_server_authority`: The authority that will be used for hosting the mock digital twin service +- `digital_twin_server_authority`: The authority that will be used for hosting the mock digital twin service. Note that the default entry for this setting is the same as the default Ibeji authority, which facilitates the transition from the mock service to a live Ibeji service. - `entities`: A list of entities with the following properties: - `begin`: An integer indicating when to enable this entity (refer to [Behavior](#behavior) for more information on how this value is used) - `end`: An optional integer indicating when to disable this entity. Set to `null` if you never want the entity to "turn off" (refer to [Behavior](#behavior) for more information on how this value is used) @@ -25,24 +27,34 @@ This mock supports [config overrides](../../docs/tutorials/config-overrides.md). This mock service mocks the behavior of both the Ibeji digital twin service and providers that register with it. -This mock exposes the `/entity` endpoint which fulfills the `find_by_id` API. - -Entities that support the `Subscribe` operation will allow clients to send a request to the `/subscribe` endpoint, and the server will periodically publish the entity values to the provided callback. The communication protocol used by these mocked providers for this callback is HTTP. +Entities that support the `Subscribe` operation will allow clients to subscribe, and the server will periodically publish the entity values to the provided callback. The communication protocol used by these mocked providers for this callback is gRPC and is compatible with the [Sample gRPC Data Adapter](../../adapters/data/sample_grpc_data_adapter/README.md). -Similarly, providers that support the `Get` operation will allow clients to send a request to the `/request-value` endpoint. The server will publish the entity values a single time to the provided callback rather than setting up a recurring callback. If the client wishes to retrieve the values again, then the client would need to send another request. +Similarly, providers that support the `Get` operation will allow clients to request value with an "async get" operation. The server will publish the entity values a single time to the provided callback rather than setting up a recurring callback. If the client wishes to retrieve the values again, then the client would need to send another request. This mock maintains a count of the number of times the value of entity has been requested, and returns a value that is a function of this count. In this way, the behavior of the `generate_signal_value()` API is identical to that of the In-Memory Data Adapter. -### Interactive Mode +## Build and Run -To use interactive mode, pass the `--interactive` argument when running the application. +The Mock Digital Twin supports two modes: interactive and non-interactive. -In interactive mode, the application maintains an internal count, and only entities satisfying the condition `begin <= count [< end]` will be enabled for all APIs. To increment this count and potentially change the set of enabled entities, press Enter in the application's console. This allows manual control over when the entities are turned on or off and permits straightforward mocking of more complex scenarios. As a result of this behavior, it is recommended to write configs such that a state change happens each time Enter is pressed. For example, if a mock scenario has `n` different desired states, then all numbers in the range `0..n-1` should appear as values for at least one `begin` or `end` property. Otherwise pressing Enter will sometimes have no effect. +### Non-Interactive Mode -**Do not use interactive mode if running this service in a container!** This feature is not compatible with containers and will cause unexpected behavior, including very high resource consumption. +Non-interactive mode is the default behavior of this application. To run the Mock Digital Twin in non-interactive mode, run the following command: -### Non-Interactive Mode +```shell +cargo run -p mock-digital-twin +``` In non-interactive mode, the `begin` and `end` properties in the config are ignored, and all configured entities are always exposed in the mock's APIs. -This is the default behavior of this application. +### Interactive Mode + +To use interactive mode with the Mock Digital Twin, pass the `--interactive` flag when running the application: + +```shell +cargo run -p mock-digital-twin -- --interactive +``` + +In interactive mode, the application maintains an internal count, and only entities satisfying the condition `begin <= count [< end]` will be enabled for all APIs. To increment this count and potentially change the set of enabled entities, press Enter in the application's console. This allows manual control over when the entities are turned on or off and permits straightforward mocking of more complex scenarios. As a result of this behavior, it is recommended to write configs such that a state change happens each time Enter is pressed. For example, if a mock scenario has `n` different desired states, then all numbers in the range `0..n-1` should appear as values for at least one `begin` or `end` property. Otherwise pressing Enter will sometimes have no effect. + +**Do not use interactive mode if running this service in a container!** This feature is not compatible with containers and will cause unexpected behavior, including very high resource consumption. diff --git a/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json b/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json index 2174c081..5713d842 100644 --- a/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json +++ b/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json @@ -1,5 +1,5 @@ { - "digital_twin_server_authority": "127.0.0.1:8800", + "digital_twin_server_authority": "0.0.0.0:5010", "entities": [ { "begin": 1, @@ -10,9 +10,9 @@ "description": "The immediate surroundings air temperature (in Fahrenheit).", "endpoints": [ { - "protocol": "http", + "protocol": "grpc", "operations": ["Get"], - "uri": "http://127.0.0.1:8800/request-value", + "uri": "http://0.0.0.0:5010", "context": "context" } ] @@ -30,9 +30,9 @@ "description": "Is air conditioning active?", "endpoints": [ { - "protocol": "http", + "protocol": "grpc", "operations": ["Get"], - "uri": "http://127.0.0.1:8800/request-value", + "uri": "http://0.0.0.0:5010", "context": "context" } ] @@ -50,9 +50,9 @@ "description": "Percentage of the hybrid battery remaining", "endpoints": [ { - "protocol": "http", + "protocol": "grpc", "operations": ["Subscribe"], - "uri": "http://127.0.0.1:8800/subscribe", + "uri": "http://0.0.0.0:5010", "context": "context" } ] diff --git a/mocks/mock_digital_twin/src/lib.rs b/mocks/mock_digital_twin/src/lib.rs deleted file mode 100644 index 7edf67a4..00000000 --- a/mocks/mock_digital_twin/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -pub const ENTITY_PATH: &str = "/entity"; -pub const ENTITY_QUERY_PATH: &str = "/entity?id="; -pub const ENTITY_SUBSCRIBE_PATH: &str = "/subscribe"; -pub const ENTITY_GET_VALUE_PATH: &str = "/request-value"; diff --git a/mocks/mock_digital_twin/src/main.rs b/mocks/mock_digital_twin/src/main.rs index 81b28e52..bdbe8aa4 100644 --- a/mocks/mock_digital_twin/src/main.rs +++ b/mocks/mock_digital_twin/src/main.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: MIT mod config; +mod mock_digital_twin_impl; +mod mock_provider; use std::{ collections::{HashMap, HashSet}, @@ -12,49 +14,39 @@ use std::{ time::Duration, }; -use axum::{ - extract, - extract::State, - response::{IntoResponse, Response}, - routing::{get, post}, - Json, Router, -}; +use core_protobuf_data_access::invehicle_digital_twin::v1::invehicle_digital_twin_server::InvehicleDigitalTwinServer; use env_logger::Target; -use log::{debug, error, info, warn, LevelFilter}; -use reqwest::Client; -use serde::Deserialize; -use tokio::{ - net::TcpListener, - sync::{mpsc, mpsc::UnboundedSender}, +use log::{debug, info, warn, LevelFilter}; +use samples_protobuf_data_access::sample_grpc::v1::{ + digital_twin_consumer::{ + digital_twin_consumer_client::DigitalTwinConsumerClient, PublishRequest, + }, + digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer, }; +use tokio::sync::{mpsc, mpsc::UnboundedSender}; +use tonic::{transport::Server, Request}; -use crate::config::{Config, EntityConfig}; +use crate::{ + config::{Config, EntityConfig}, + mock_digital_twin_impl::MockDigitalTwinImpl, + mock_provider::MockProvider, +}; use freyja_build_common::config_file_stem; use freyja_common::{ cmd_utils::{get_log_level, parse_args}, - config_utils, - digital_twin_adapter::FindByIdResponse, - not_found, ok, out_dir, server_error, + config_utils, out_dir, }; -use http_mock_data_adapter::http_mock_data_adapter::{EntityValueRequest, EntityValueResponse}; -use mock_digital_twin::{ENTITY_GET_VALUE_PATH, ENTITY_PATH, ENTITY_SUBSCRIBE_PATH}; /// Stores the state of active entities, subscribers, and relays responses /// for getting/subscribing to an entity. -struct DigitalTwinAdapterState { +pub(crate) struct DigitalTwinAdapterState { count: u8, entities: Vec<(EntityConfig, u8)>, subscriptions: HashMap>, - response_channel_sender: UnboundedSender<(String, EntityValueResponse)>, + response_channel_sender: UnboundedSender<(String, PublishRequest)>, interactive: bool, } -/// Used for deserializing a query parameter for /entity?id=... -#[derive(Deserialize)] -struct EntityQuery { - id: String, -} - /// Starts the following threads and tasks: /// - A thread which listens for input from the command window /// - A task which handles async get responses @@ -82,7 +74,7 @@ async fn main() { ) .unwrap(); - let (sender, mut receiver) = mpsc::unbounded_channel::<(String, EntityValueResponse)>(); + let (sender, mut receiver) = mpsc::unbounded_channel::<(String, PublishRequest)>(); let state = Arc::new(Mutex::new(DigitalTwinAdapterState { count: 0, @@ -127,7 +119,6 @@ async fn main() { // Get responder setup tokio::spawn(async move { - let client = Client::new(); loop { let message = receiver.recv().await; if message.is_none() { @@ -137,16 +128,17 @@ async fn main() { let request = message.unwrap(); info!("Handling GET for request {request:?}..."); - let (callback_uri_for_signals, response_to_send) = request.clone(); + let (consumer_uri, request) = request.clone(); - let send_result = client - .post(&callback_uri_for_signals) - .json(&response_to_send) - .send() - .await - .and_then(|r| r.error_for_status()); + let mut client = match DigitalTwinConsumerClient::connect(consumer_uri).await { + Ok(client) => client, + Err(e) => { + log::error!("Error creating DigitalTwinConsumerClient: {e:?}"); + continue; + } + }; - match send_result { + match client.publish(Request::new(request.clone())).await { Ok(_) => info!("Successfully sent value for request {request:?}"), Err(e) => log::error!("Failed to send value to {request:?}: {e}"), } @@ -155,7 +147,6 @@ async fn main() { // Subscriber publish setup tokio::spawn(async move { - let client = Client::new(); loop { debug!("Beginning subscribe loop..."); @@ -177,25 +168,22 @@ async fn main() { } for subscriber in subscribers { - let request = EntityValueResponse { + let request = PublishRequest { entity_id: entity_id.clone(), value: value.clone(), }; - let send_result = client - .post(&subscriber) - .json(&request) - .send() - .await - .and_then(|r| r.error_for_status()); - - match send_result { - Ok(_) => debug!( - "Successfully sent value for request {request:?} to {subscriber}" - ), - Err(e) => error!( - "Failed to send value for request {request:?} to {subscriber}: {e}" - ), + let mut client = match DigitalTwinConsumerClient::connect(subscriber).await { + Ok(client) => client, + Err(e) => { + log::error!("Error creating DigitalTwinConsumerClient: {e:?}"); + continue; + } + }; + + match client.publish(Request::new(request.clone())).await { + Ok(_) => info!("Successfully sent value for request {request:?}"), + Err(e) => log::error!("Failed to send value to {request:?}: {e}"), } } } @@ -204,100 +192,31 @@ async fn main() { } }); - // HTTP server setup + // Server setup info!( - "Mock Digital Twin Adapter Server starting at {}", + "Mock Digital Twin Server starting at {}", config.digital_twin_server_authority ); - let app = Router::new() - .route(ENTITY_PATH, get(get_entity)) - .route(ENTITY_SUBSCRIBE_PATH, post(subscribe)) - .route(ENTITY_GET_VALUE_PATH, post(request_value)) - .with_state(state); - - let listener = TcpListener::bind(&config.digital_twin_server_authority) - .await - .expect("Unable to bind to server endpoint"); - - axum::serve(listener, app).await.unwrap(); -} - -/// Handles getting access info of an entity -/// -/// # Arguments -/// - `state`: the state of the DigitalTwinAdapter which consists of active entities and their subscriptions -/// - `query`: the entity query you wish to get access info on -async fn get_entity( - State(state): State>>, - extract::Query(query): extract::Query, -) -> Response { - info!("Received request to get entity: {}", query.id); - let state = state.lock().unwrap(); - find_entity(&state, &query.id) - .map(|(config_item, _)| { - ok!(FindByIdResponse { - entity: config_item.entity.clone() - }) - }) - .unwrap_or(not_found!()) -} + let addr = config + .digital_twin_server_authority + .parse() + .expect("Unable to parse server address"); -/// Handles subscribe requests to an entity -/// -/// # Arguments -/// - `state`: the state of the DigitalTwinAdapter which consists of active providers and their subscriptions -/// - `request`: the subscribe request to an entity -async fn subscribe( - State(state): State>>, - Json(request): Json, -) -> Response { - info!("Received subscribe request: {request:?}"); - let mut state = state.lock().unwrap(); - - match find_entity(&state, &request.entity_id) { - Some(_) => { - state - .subscriptions - .entry(request.entity_id) - .and_modify(|e| { - e.insert(request.callback_uri); - }); - ok!() - } - None => not_found!(), - } -} + let mock_digital_twin = MockDigitalTwinImpl { + state: state.clone(), + }; -/// Handles async get requests -/// -/// # Arguments -/// - `state`: the state of the DigitalTwinAdapter which consists of active providers -/// - `request`: the async get request to an entity -async fn request_value( - State(state): State>>, - Json(request): Json, -) -> Response { - info!("Received request to get value: {request:?}"); - let mut state = state.lock().unwrap(); - match get_entity_value(&mut state, &request.entity_id) { - Some(value) => { - let response = EntityValueResponse { - entity_id: request.entity_id, - value, - }; + let mock_provider = MockProvider { + state: state.clone(), + }; - info!("Submitting request..."); - match state - .response_channel_sender - .send((request.callback_uri, response)) - { - Ok(_) => ok!(), - Err(e) => server_error!(format!("Request value error: {e:?}")), - } - } - None => not_found!(), - } + Server::builder() + .add_service(InvehicleDigitalTwinServer::new(mock_digital_twin)) + .add_service(DigitalTwinProviderServer::new(mock_provider)) + .serve(addr) + .await + .unwrap(); } /// Checks if a value is within bounds diff --git a/mocks/mock_digital_twin/src/mock_digital_twin_impl.rs b/mocks/mock_digital_twin/src/mock_digital_twin_impl.rs new file mode 100644 index 00000000..38025420 --- /dev/null +++ b/mocks/mock_digital_twin/src/mock_digital_twin_impl.rs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::sync::{Arc, Mutex}; + +use async_trait::async_trait; +use core_protobuf_data_access::invehicle_digital_twin::v1::{ + invehicle_digital_twin_server::InvehicleDigitalTwin, EndpointInfo, EntityAccessInfo, + FindByIdRequest, FindByIdResponse, RegisterRequest, RegisterResponse, +}; +use log::info; +use tonic::{Request, Response, Status}; + +use crate::{find_entity, DigitalTwinAdapterState}; + +pub struct MockDigitalTwinImpl { + pub(crate) state: Arc>, +} + +#[async_trait] +impl InvehicleDigitalTwin for MockDigitalTwinImpl { + /// Find-by-id implementation. + /// + /// # Arguments + /// * `request` - Find-by-id request. + async fn find_by_id( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + info!("Received request to get entity: {}", request.id); + let state = self.state.lock().unwrap(); + find_entity(&state, &request.id) + .map(|(config_item, _)| { + let endpoint_info_list = config_item + .entity + .endpoints + .iter() + .map(|e| EndpointInfo { + protocol: e.protocol.clone(), + operations: e.operations.clone(), + uri: e.uri.clone(), + context: e.context.clone(), + }) + .collect(); + + let access_info = EntityAccessInfo { + name: config_item.entity.name.clone().unwrap_or_default(), + id: config_item.entity.id.clone(), + description: config_item.entity.description.clone().unwrap_or_default(), + endpoint_info_list, + }; + + Ok(Response::new(FindByIdResponse { + entity_access_info: Some(access_info), + })) + }) + .unwrap_or(Err(Status::not_found("Entity not found"))) + } + + /// Register implementation. + /// + /// # Arguments + /// * `request` - Publish request. + async fn register( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented( + "Register is not supported for the mock digital twin", + )) + } +} diff --git a/mocks/mock_digital_twin/src/mock_provider.rs b/mocks/mock_digital_twin/src/mock_provider.rs new file mode 100644 index 00000000..37497a2e --- /dev/null +++ b/mocks/mock_digital_twin/src/mock_provider.rs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::{ + pin::Pin, + sync::{Arc, Mutex}, +}; + +use async_trait::async_trait; +use log::info; +use samples_protobuf_data_access::sample_grpc::v1::{ + digital_twin_consumer::PublishRequest, + digital_twin_provider::{ + digital_twin_provider_server::DigitalTwinProvider, GetRequest, GetResponse, InvokeRequest, + InvokeResponse, SetRequest, SetResponse, StreamRequest, StreamResponse, SubscribeRequest, + SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, + }, +}; +use tokio_stream::Stream; +use tonic::{Request, Response, Status}; + +use crate::{find_entity, get_entity_value, DigitalTwinAdapterState}; + +pub struct MockProvider { + pub(crate) state: Arc>, +} + +#[async_trait] +impl DigitalTwinProvider for MockProvider { + type StreamStream = Pin> + Send>>; + + /// Subscribe implementation. + /// + /// # Arguments + /// * `request` - Subscribe request. + async fn subscribe( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + info!("Received subscribe request: {request:?}"); + let mut state = self.state.lock().unwrap(); + + match find_entity(&state, &request.entity_id) { + Some(_) => { + state + .subscriptions + .entry(request.entity_id) + .and_modify(|e| { + e.insert(request.consumer_uri); + }); + Ok(Response::new(SubscribeResponse {})) + } + None => Err(Status::not_found("Entity not found")), + } + } + + /// Get implementation. + /// + /// # Arguments + /// * `request` - Get request. + async fn get(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + info!("Received request to get value: {request:?}"); + let mut state = self.state.lock().unwrap(); + match get_entity_value(&mut state, &request.entity_id) { + Some(value) => { + let publish_request = PublishRequest { + entity_id: request.entity_id, + value, + }; + + info!("Submitting request..."); + match state + .response_channel_sender + .send((request.consumer_uri, publish_request)) + { + Ok(_) => Ok(Response::new(GetResponse {})), + Err(e) => Err(Status::internal(format!("Request value error: {e:?}"))), + } + } + None => Err(Status::not_found("Entity not found")), + } + } + + /// Unsubscribe implementation. + /// + /// # Arguments + /// * `request` - Unsubscribe request. + async fn unsubscribe( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented( + "Unsubscribe is not supported for the mock digital twin", + )) + } + + /// Set implementation. + /// + /// # Arguments + /// * `request` - Set request. + async fn set(&self, _request: Request) -> Result, Status> { + Err(Status::unimplemented( + "Set is not supported for the mock digital twin", + )) + } + + /// Invoke implementation. + /// + /// # Arguments + /// * `request` - Invoke request. + async fn invoke( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented( + "Invoke is not supported for the mock digital twin", + )) + } + + /// Stream implementation. + /// + /// # Arguments + /// * `request` - OpenStream request. + async fn stream( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented( + "Stream is not supported for the mock digital twin", + )) + } +}