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

Container support for freyja_apps #9

Merged
merged 12 commits into from
Oct 27, 2023
3 changes: 3 additions & 0 deletions .freyja/config/grpc_proxy_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"consumer_address": "0.0.0.0:60010"
}
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"cloud_connectors/azure/proto-build",
"freyja_adapters/cloud/azure_cloud_connector_adapter",
"freyja_adapters/digital_twin/ibeji_adapter",
"freyja_apps/ibeji_adapter",
"freyja_apps/in_memory",
"freyja_apps/e2e",
]
Expand Down
75 changes: 75 additions & 0 deletions Dockerfile.freyja_apps
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# SPDX-License-Identifier: MIT

# syntax=docker/dockerfile:1
devkelley marked this conversation as resolved.
Show resolved Hide resolved

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/engine/reference/builder/

################################################################################
# Create a stage for building the application.

ARG RUST_VERSION=1.72.1
devkelley marked this conversation as resolved.
Show resolved Hide resolved
FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build
ARG APP_NAME=freyja-in-memory-app
WORKDIR /sdv

COPY ./ .

# Add Build dependencies.
RUN apt update && apt upgrade -y && apt install -y \
libssl-dev \
pkg-config \
protobuf-compiler

# Check that APP_NAME argument is valid.
RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \
[ "$sanitized" = "${APP_NAME}" ] || { \
echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \
exit 1; \
}

# Build the application with the 'containerize' feature.
RUN cargo build --release -p "${APP_NAME}" --features containerize

# Copy the built application to working directory.
RUN cp ./target/release/"${APP_NAME}" /sdv/service

################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses the debian bullseye image as the foundation for running the app.
# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the
# most recent version of that tag when you build your Dockerfile. If
# reproducability is important, consider using a digest
# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57).
FROM docker.io/library/debian:bullseye-slim AS final

# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser

WORKDIR /sdv

# Copy the executable from the "build" stage.
COPY --from=build /sdv/service /sdv/
COPY --from=build /sdv/target/release/build/ /sdv/target/release/build/

ENV FREYJA_HOME=/sdv/.freyja

# What the container should run when it is started.
CMD ["/sdv/service"]
29 changes: 29 additions & 0 deletions container/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Container Examples

This document describes how to utilize the provided Dockerfiles for containerization.

## Dockerfile Selection

### Freyja Apps

To containerize the [Example Freyja Apps](./freyja_apps/), use
[Dockerfile.freyja_apps](../Dockerfile.freyja_apps). This dockerfile defaults to the
[In Memory Example Application](../freyja_apps/in_memory/).

Currently the following example applications can be containerized:
- [In Memory Example Application](../freyja_apps/in_memory/)
- [Ibeji Adapter Example Application](../freyja_apps/ibeji_adapter)

Each supported application has a README describing the steps to containerize the application.

### Freyja Cloud Connectors

Coming soon!

### Env Files

To run an application in Docker, ensure that the [docker.env](./config/docker.env) is passed in
when running the container.

To run an application in Podman, ensure that the [podman.env](./config/podman.env) is passed in
when running the container.
9 changes: 9 additions & 0 deletions container/config/docker.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# SPDX-License-Identifier: MIT

# DNS name used by the container to communicate with host.
HOST_GATEWAY=host.docker.internal

# Alias for localhost to be replaced by HOST_GATEWAY if run in a container.
LOCALHOST_ALIAS=0.0.0.0
9 changes: 9 additions & 0 deletions container/config/podman.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# SPDX-License-Identifier: MIT

# DNS name used by the container to communicate with host.
HOST_GATEWAY=host.containers.internal

# Alias for localhost to be replaced by HOST_GATEWAY if run in a container.
LOCALHOST_ALIAS=0.0.0.0
5 changes: 4 additions & 1 deletion freyja_adapters/digital_twin/ibeji_adapter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ tonic = { workspace = true }
tower = { workspace = true }

[build-dependencies]
freyja-build-common = { workspace = true }
freyja-build-common = { workspace = true }

[features]
containerize = []
25 changes: 25 additions & 0 deletions freyja_adapters/digital_twin/ibeji_adapter/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,32 @@
// Licensed under the MIT license.
// SPDX-License-Identifier: MIT

#![allow(unused_imports)]

use serde::{Deserialize, Serialize};
use std::env;

/// If feature 'containerize' is set, will modify a localhost uri to point to container's localhost
devkelley marked this conversation as resolved.
Show resolved Hide resolved
/// DNS alias. Otherwise, returns the uri as a String.
///
/// # Arguments
/// * `uri` - The uri to potentially modify.
pub fn get_uri(uri: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
#[cfg(feature = "containerize")]
let uri = {
// Container env variable names.
let host_gateway_env_var: &str = "HOST_GATEWAY";
ashbeitz marked this conversation as resolved.
Show resolved Hide resolved
let host_alias_env_var: &str = "LOCALHOST_ALIAS";

// Return an error if container env variables are not set.
let host_gateway = env::var(host_gateway_env_var)?;
let host_alias = env::var(host_alias_env_var)?;

uri.replace(&host_alias, &host_gateway)
};

Ok(uri.to_string())
}

/// Configuration for the Ibeji Adapter.
/// Supports two different schemas based on the service discovery method.
Expand Down
22 changes: 15 additions & 7 deletions freyja_adapters/digital_twin/ibeji_adapter/src/ibeji_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use freyja_contracts::{
provider_proxy::OperationKind,
};

use crate::config::{ChariottDiscoverRequest, Config};
use crate::config::{self, ChariottDiscoverRequest, Config};

const CONFIG_FILE_STEM: &str = "ibeji_adapter_config";
const GET_OPERATION: &str = "Get";
Expand All @@ -44,10 +44,12 @@ impl IbejiAdapter {
chariott_service_discovery_uri: &str,
chariott_discovery_request: ChariottDiscoverRequest,
) -> Result<String, DigitalTwinAdapterError> {
let mut service_registry_client =
ServiceRegistryClient::connect(String::from(chariott_service_discovery_uri))
.await
.map_err(DigitalTwinAdapterError::communication)?;
let chariott_uri =
config::get_uri(chariott_service_discovery_uri).map_err(DigitalTwinAdapterError::io)?;

let mut service_registry_client = ServiceRegistryClient::connect(chariott_uri)
.await
.map_err(DigitalTwinAdapterError::communication)?;

let discover_request = Request::new(DiscoverRequest {
namespace: chariott_discovery_request.namespace,
Expand Down Expand Up @@ -121,11 +123,14 @@ impl DigitalTwinAdapter for IbejiAdapter {
}
};

let invehicle_digital_twin_uri = config::get_uri(&invehicle_digital_twin_service_uri)
.map_err(DigitalTwinAdapterError::io)?;

let client = futures::executor::block_on(async {
execute_with_retry(
max_retries,
Duration::from_millis(retry_interval_ms),
|| InvehicleDigitalTwinClient::connect(invehicle_digital_twin_service_uri.clone()),
|| InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.clone()),
Some(String::from("Connection retry for connecting to Ibeji")),
)
.await
Expand Down Expand Up @@ -196,12 +201,15 @@ impl DigitalTwinAdapter for IbejiAdapter {

let operation =
OperationKind::from_str(&operation).map_err(DigitalTwinAdapterError::parse_error)?;

let entity_uri = config::get_uri(&endpoint.uri).map_err(DigitalTwinAdapterError::io)?;

let entity = Entity {
id: entity_id,
description: Some(entity_access_info.description),
name: Some(entity_access_info.name),
operation,
uri: endpoint.uri,
uri: entity_uri,
protocol: endpoint.protocol,
};

Expand Down
23 changes: 23 additions & 0 deletions freyja_apps/ibeji_adapter/Cargo.toml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this PR but something we should think about: we're starting to just go through permutations of adapters in this repo. It might be valuable to find a way to have a single app here that selects adapters based on config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though this would likely mean all of the adapters have to be statically linked, which is less than ideal

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# SPDX-License-Identifier: MIT

[package]
name = "freyja-ibeji-adapter-app"
version = "0.1.0"
edition = "2021"
license = "MIT"

[dependencies]
# These two dependencies are required for anyone implementing a Freyja application
freyja = { workspace = true }
tokio = { workspace = true, features = ["macros"] }

# Put any dependencies that you need for your adapters down here.
# This samples utilizes the in-memory mock adapters.
in-memory-mock-cloud-adapter = { workspace = true }
ibeji-adapter = { workspace = true }
in-memory-mock-mapping-client = { workspace = true }

[features]
containerize = ["ibeji-adapter/containerize"]
102 changes: 102 additions & 0 deletions freyja_apps/ibeji_adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Ibeji Adapter Freyja Example Application

This Freyja Example Application utilizes the [Ibeji Digital Twin Adapter](../../freyja_adapters/digital_twin/ibeji_adapter/) and an in-memory mock cloud connector adapter to show a minimal connected local example of how to retrieve data from the vehicle.

## Build and Run

To build and run the application, follow these steps:

1. (Optional) If necessary, author configuration overrides for the [`InMemoryMockMappingClient`](https://github.com/eclipse-ibeji/freyja/tree/main/mapping_clients/in_memory_mock_mapping_client). Refer to the adapter README files for instructions on how to do this. This repository provides overrides in the [`.freyja`](../../.freyja/) directory that can be used with the [`mixed` sample provided by Ibeji](https://github.com/eclipse-ibeji/ibeji/tree/main/samples/mixed).

1. Set the `$FREYJA_HOME` environment variable. If you are using the provided overrides, you can run the following command to set the variable:

export FREYJA_HOME={path-to-repo-root}/.freyja
devkelley marked this conversation as resolved.
Show resolved Hide resolved

Alternatively, you can set the variable in a [Cargo configuration file](https://doc.rust-lang.org/cargo/reference/config.html) to only enable the variable while running a Cargo command.

1. Run the following from the repo root:

cargo run -p freyja-ibeji-adapter-app

This will rebuild the application as necessary and then run it. Note that running Cargo commands without specifying the `-p` argument might target every package in the workspace! When working with this repository it's recommended to use the `-p` argument with Cargo commands.
devkelley marked this conversation as resolved.
Show resolved Hide resolved

## Containerize the Ibeji Adapter Freyja Example Application

To build and run the application in a container, follow the steps under [Docker](#docker) or
[Podman](#podman). Ensure that the `$FREYJA_HOME` environment variable is set.

### Docker

#### Prerequisites

[Install Docker](https://docs.docker.com/engine/install/)

#### Running in Docker

To run the service in a Docker container:

1. Run the following command in the project's root directory to build the docker container from the
Dockerfile:

```shell
docker build -t freyja_ibeji_adapter --build-arg APP_NAME=freyja-ibeji-adapter-app -f Dockerfile.freyja_apps .
```

The `APP_NAME` build arg needs to be set as `Dockerfile.freyja_apps` defaults to the
[In Memory Example Application](../in_memory/).

1. Once the container has been built, start the container in interactive mode with the following
command in the project's root directory:

```shell
docker run -v ${FREYJA_HOME}:/sdv/.freyja --name freyja_ibeji_adapter -p 60010:60010 --env-file=./container/config/docker.env --add-host=host.docker.internal:host-gateway -it --rm freyja_ibeji_adapter
```

`-v` mounts the `$FREYJA_HOME` path set above in the container allowing the application to use
the provided overrides for the mixed sample in Ibeji.

1. To detach from the container, enter:

<kbd>Ctrl</kbd> + <kbd>p</kbd>, <kbd>Ctrl</kbd> + <kbd>q</kbd>

1. To stop the container, enter:

```shell
docker stop freyja_ibeji_adapter
```

### Podman

#### Prerequisites

[Install Podman](https://podman.io/docs/installation)

#### Running in Podman

To run the service in a Podman container:

1. Run the following command in the project's root directory to build the podman container from the
Dockerfile:

```shell
podman build -t freyja_ibeji_adapter --build-arg=APP_NAME=freyja-ibeji-adapter-app -f Dockerfile.freyja_apps .
```

The `APP_NAME` build arg needs to be set as `Dockerfile.freyja_apps` defaults to the
[In Memory Example Application](../in_memory/).

1. Once the container has been built, start the container with the following command in the
project's root directory:

```shell
podman run --mount=type=bind,src=${FREYJA_HOME},dst=/sdv/.freyja,ro=true -p 60010:60010 --env-file=./container/config/podman.env --network=slirp4netns:allow_host_loopback=true localhost/freyja_ibeji_adapter
```

`-v` mounts the `$FREYJA_HOME` path set above in the container allowing the application to use
the provided overrides for the mixed sample in Ibeji.

1. To stop the container, run:

```shell
podman ps -f ancestor=localhost/freyja_ibeji_adapter:latest --format="{{.Names}}" | xargs podman stop
```
13 changes: 13 additions & 0 deletions freyja_apps/ibeji_adapter/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// SPDX-License-Identifier: MIT

use ibeji_adapter::ibeji_adapter::IbejiAdapter;
use in_memory_mock_cloud_adapter::in_memory_mock_cloud_adapter::InMemoryMockCloudAdapter;
use in_memory_mock_mapping_client::in_memory_mock_mapping_client::InMemoryMockMappingClient;

freyja::freyja_main! {
IbejiAdapter,
InMemoryMockCloudAdapter,
InMemoryMockMappingClient
}
Loading