Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 40 additions & 1 deletion edgehog-device-runtime-containers/src/docker/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,10 @@ pub(crate) struct Container {
pub(crate) cap_add: Vec<String>,
/// A list of kernel capabilities to drop from the container.
pub(crate) cap_drop: Vec<String>,
/// A list of deice to mount inside the container.
/// A list of device to mount inside the container.
pub(crate) device_mappings: Vec<DeviceMapping>,
/// A list of device to request for the container.
pub(crate) device_requests: Vec<DeviceRequest>,
/// The length of a CPU period in microseconds.
pub(crate) cpu_period: Option<i64>,
/// Microseconds of CPU time that the container can get in a CPU period.
Expand Down Expand Up @@ -492,6 +494,7 @@ impl From<&Container> for ContainerCreateBody {
cap_add,
cap_drop,
device_mappings,
device_requests,
privileged,
cpu_period,
cpu_quota,
Expand All @@ -516,6 +519,11 @@ impl From<&Container> for ContainerCreateBody {
.cloned()
.map(bollard::models::DeviceMapping::from)
.collect();
let device_requests = device_requests
.iter()
.cloned()
.map(bollard::models::DeviceRequest::from)
.collect();

let restart_policy = BollardRestartPolicy {
name: Some(RestartPolicyNameEnum::from(*restart_policy)),
Expand All @@ -535,6 +543,7 @@ impl From<&Container> for ContainerCreateBody {
cap_drop: Some(cap_drop.clone()),
privileged: Some(*privileged),
devices: Some(device_mappings),
device_requests: Some(device_requests),
cpu_period: *cpu_period,
cpu_quota: *cpu_quota,
cpu_realtime_period: *cpu_realtime_period,
Expand Down Expand Up @@ -720,6 +729,35 @@ impl From<DeviceMapping> for bollard::models::DeviceMapping {
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DeviceRequest {
pub driver: Option<String>,
pub count: i64,
pub device_ids: Vec<String>,
pub capabilities: Vec<Vec<String>>,
pub options: HashMap<String, String>,
}

impl From<DeviceRequest> for bollard::config::DeviceRequest {
fn from(value: DeviceRequest) -> Self {
let DeviceRequest {
driver,
count,
device_ids,
capabilities,
options,
} = value;

Self {
driver,
count: Some(count),
device_ids: Some(device_ids),
capabilities: Some(capabilities),
options: Some(options),
}
}
}

#[cfg(test)]
mod tests {
use mockall::predicate;
Expand All @@ -744,6 +782,7 @@ mod tests {
cap_add: Vec::new(),
cap_drop: Vec::new(),
device_mappings: Vec::new(),
device_requests: Vec::new(),
privileged: false,
cpu_period: None,
cpu_quota: None,
Expand Down
110 changes: 110 additions & 0 deletions edgehog-device-runtime-containers/src/properties/device_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// This file is part of Edgehog.
//
// Copyright 2024-2026 SECO Mind Srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Available [`Image`](crate::docker::image::Image) property.

use uuid::Uuid;

use super::AvailableProp;

const INTERFACE: &str = "io.edgehog.devicemanager.apps.AvailableDeviceRequests";

/// Available device_request property.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AvailableDeviceRequest<'a> {
id: &'a Uuid,
}

impl<'a> AvailableDeviceRequest<'a> {
pub(crate) fn new(id: &'a Uuid) -> Self {
Self { id }
}
}

impl AvailableProp for AvailableDeviceRequest<'_> {
type Data = bool;

fn interface() -> &'static str {
INTERFACE
}

fn id(&self) -> &Uuid {
self.id
}

fn field() -> &'static str {
"present"
}
}

#[cfg(test)]
mod tests {
use astarte_device_sdk::AstarteData;
use astarte_device_sdk::store::SqliteStore;
use astarte_device_sdk::transport::mqtt::Mqtt;
use astarte_device_sdk_mock::{MockDeviceClient, mockall::Sequence};
use mockall::predicate;
use uuid::Uuid;

use super::*;

#[tokio::test]
async fn should_store_device_request() {
let id = Uuid::new_v4();

let device_request = AvailableDeviceRequest::new(&id);

let mut client = MockDeviceClient::<Mqtt<SqliteStore>>::new();
let mut seq = Sequence::new();

client
.expect_set_property()
.once()
.in_sequence(&mut seq)
.with(
predicate::eq("io.edgehog.devicemanager.apps.AvailableDeviceRequests"),
predicate::eq(format!("/{id}/present")),
predicate::eq(AstarteData::Boolean(true)),
)
.returning(|_, _, _| Ok(()));

device_request.send(&mut client, true).await.unwrap();
}

#[tokio::test]
async fn should_unset_device_request() {
let id = Uuid::new_v4();

let device_request = AvailableDeviceRequest::new(&id);

let mut client = MockDeviceClient::<Mqtt<SqliteStore>>::new();
let mut seq = Sequence::new();

client
.expect_unset_property()
.once()
.in_sequence(&mut seq)
.with(
predicate::eq("io.edgehog.devicemanager.apps.AvailableDeviceRequests"),
predicate::eq(format!("/{id}/present")),
)
.returning(|_, _| Ok(()));

device_request.unset(&mut client).await.unwrap();
}
}
3 changes: 2 additions & 1 deletion edgehog-device-runtime-containers/src/properties/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of Edgehog.
//
// Copyright 2023 - 2025 SECO Mind Srl
// Copyright 2023-2026 SECO Mind Srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -27,6 +27,7 @@ pub use astarte_device_sdk::Client;
pub(crate) mod container;
pub(crate) mod deployment;
pub(crate) mod device_mapping;
pub(crate) mod device_request;
pub(crate) mod image;
pub(crate) mod network;
pub(crate) mod volume;
Expand Down
13 changes: 12 additions & 1 deletion edgehog-device-runtime-containers/src/requests/container.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of Edgehog.
//
// Copyright 2024 - 2025 SECO Mind Srl
// Copyright 2024-2026 SECO Mind Srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -49,6 +49,7 @@ pub struct CreateContainer {
pub(crate) network_ids: VecReqUuid,
pub(crate) volume_ids: VecReqUuid,
pub(crate) device_mapping_ids: VecReqUuid,
pub(crate) device_request_ids: VecReqUuid,
pub(crate) hostname: String,
pub(crate) restart_policy: String,
pub(crate) env: Vec<String>,
Expand Down Expand Up @@ -228,6 +229,7 @@ pub(crate) mod tests {
image: &str,
network_ids: &[S],
device_mapping_ids: &[impl Display],
device_request_ids: &[impl Display],
) -> DeviceEvent {
let fields = [
("id", AstarteData::String(id.to_string())),
Expand All @@ -243,6 +245,12 @@ pub(crate) mod tests {
device_mapping_ids.iter().map(|d| d.to_string()).collect(),
),
),
(
"deviceRequestIds",
AstarteData::StringArray(
device_request_ids.iter().map(|d| d.to_string()).collect(),
),
),
("image", AstarteData::String(image.to_string())),
("hostname", AstarteData::String("hostname".to_string())),
("restartPolicy", AstarteData::String("no".to_string())),
Expand Down Expand Up @@ -310,13 +318,15 @@ pub(crate) mod tests {
let image_id = ReqUuid(Uuid::new_v4());
let network_ids = VecReqUuid(vec![ReqUuid(Uuid::new_v4())]);
let device_mapping_ids = VecReqUuid(vec![ReqUuid(Uuid::new_v4())]);
let device_request_ids = VecReqUuid(vec![ReqUuid(Uuid::new_v4())]);
let event = create_container_request_event(
id,
deployment_id,
image_id,
"image",
&network_ids,
&device_mapping_ids,
&device_request_ids,
);

let request = CreateContainer::from_event(event).unwrap();
Expand All @@ -328,6 +338,7 @@ pub(crate) mod tests {
network_ids,
volume_ids: VecReqUuid(vec![]),
device_mapping_ids,
device_request_ids,
hostname: "hostname".to_string(),
restart_policy: "no".to_string(),
env: vec!["env".to_string()],
Expand Down
94 changes: 94 additions & 0 deletions edgehog-device-runtime-containers/src/requests/device_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// This file is part of Edgehog.
//
// Copyright 2026 SECO Mind Srl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Device Request sent from Astarte

use astarte_device_sdk::{FromEvent, IntoAstarteObject};

use super::{OptString, ReqUuid};

/// Request to pull a Docker Network.
#[derive(Debug, Clone, FromEvent, PartialEq, Eq, PartialOrd, Ord, IntoAstarteObject)]
#[from_event(
interface = "io.edgehog.devicemanager.apps.CreateDeviceRequest",
path = "/deviceRequest",
rename_all = "camelCase",
aggregation = "object"
)]
#[astarte_object(rename_all = "camelCase")]
pub struct CreateDeviceRequest {
pub(crate) id: ReqUuid,
pub(crate) deployment_id: ReqUuid,
pub(crate) driver: OptString,
pub(crate) count: i64,
pub(crate) device_ids: Vec<String>,
pub(crate) capabilities: Vec<String>,
pub(crate) option_keys: Vec<String>,
pub(crate) option_values: Vec<String>,
}

#[cfg(test)]
pub(crate) mod tests {

use astarte_device_sdk::aggregate::AstarteObject;
use astarte_device_sdk::chrono::Utc;
use astarte_device_sdk::{DeviceEvent, Value};
use pretty_assertions::assert_eq;
use uuid::Uuid;

use super::*;

pub fn create_device_request(id: Uuid, deployment_id: Uuid) -> CreateDeviceRequest {
CreateDeviceRequest {
id: id.into(),
deployment_id: deployment_id.into(),
driver: "nvidia".into(),
count: -1,
device_ids: ["0", "1", "GPU-fef8089b-4820-abfc-e83e-94318197576e"]
.map(str::to_string)
.to_vec(),
capabilities: vec![r#"["gpu","nvidia","compute"]"#.to_string()],
option_keys: ["property1", "property2"].map(str::to_string).to_vec(),
option_values: ["string", "string"].map(str::to_string).to_vec(),
}
}

pub fn create_device_request_event(id: Uuid, deployment_id: Uuid) -> DeviceEvent {
DeviceEvent {
interface: "io.edgehog.devicemanager.apps.CreateDeviceRequest".to_string(),
path: "/deviceRequest".to_string(),
data: Value::Object {
data: AstarteObject::try_from(create_device_request(id, deployment_id)).unwrap(),
timestamp: Utc::now(),
},
}
}

#[test]
fn device_request_from_event() {
let id = Uuid::new_v4();
let deployment_id = Uuid::new_v4();
let event = create_device_request_event(id, deployment_id);

let request = CreateDeviceRequest::from_event(event).unwrap();

let expect = create_device_request(id, deployment_id);

assert_eq!(request, expect);
}
}
Loading
Loading