diff --git a/.github/workflows/build_and_test_on_every_pr._yml b/.github/workflows/build_and_test_on_every_pr.yml similarity index 78% rename from .github/workflows/build_and_test_on_every_pr._yml rename to .github/workflows/build_and_test_on_every_pr.yml index 5e68aec34e..c1c32865e6 100644 --- a/.github/workflows/build_and_test_on_every_pr._yml +++ b/.github/workflows/build_and_test_on_every_pr.yml @@ -44,13 +44,11 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Bazel execute itf qnx_qemu tests - env: - SCORE_QNX_USER: ${{ secrets.SCORE_QNX_USER }} - SCORE_QNX_PASSWORD: ${{ secrets.SCORE_QNX_PASSWORD }} + - name: Bazel execute showcase examples run: | - cd qnx_qemu - bazel test --config=qemu-integration --test_output=streamed --credential_helper=*.qnx.com=${{ github.workspace }}/.github/tools/qnx_credential_helper.py -- \ - //:test_ssh_qemu \ - //:test_scrample_qemu \ - + set -e + bazel build --config bl-x86_64-linux //feature_showcase:all_examples + while read -r target; do + bazel run --config bl-x86_64-linux "$target" + done < ci/showcase_targets_run.txt + \ No newline at end of file diff --git a/.github/workflows/release_verification.yml b/.github/workflows/release_verification.yml index 013e06a426..b81fe00491 100644 --- a/.github/workflows/release_verification.yml +++ b/.github/workflows/release_verification.yml @@ -56,6 +56,9 @@ jobs: bazel test --config=qemu-integration --test_output=streamed --credential_helper=*.qnx.com=${{ github.workspace }}/.github/tools/qnx_credential_helper.py -- \ //:test_ssh_qemu \ //:test_scrample_qemu \ + - name: Bazel execute showcase examples + run: | + bazel build --config bl-x86_64-linux //feature_showcase:all_examples release_verification: runs-on: ubuntu-latest needs: [test_target] diff --git a/.gitignore b/.gitignore index 2ab8efb04c..a983076b26 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ _logs # Ruff .ruff_cache +target/ + +rust-project.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..a31c99d85d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + "rust-project.json" + ] +} diff --git a/MODULE.bazel b/MODULE.bazel index afc27c1571..247087c4c5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -22,6 +22,7 @@ include("//:score_modules.MODULE.bazel") # for building documentation, verifying traceability etc. bazel_dep(name = "score_platform", version = "0.3.0") bazel_dep(name = "score_bazel_platforms", version = "0.0.2") +bazel_dep(name = "score_crates", version = "0.0.3") # QNX toolchain bazel_dep(name = "score_toolchains_qnx", version = "0.0.2") @@ -35,8 +36,8 @@ use_repo(toolchains_qnx, "toolchains_qnx_sdp") use_repo(toolchains_qnx, "toolchains_qnx_qcc") #gcc toolchain for baselibs -bazel_dep(name = "score_toolchains_gcc", version = "0.5", dev_dependency=False) -gcc = use_extension("@score_toolchains_gcc//extentions:gcc.bzl", "gcc", dev_dependency=False) +bazel_dep(name = "score_toolchains_gcc", version = "0.5", dev_dependency = False) +gcc = use_extension("@score_toolchains_gcc//extentions:gcc.bzl", "gcc", dev_dependency = False) gcc.toolchain( url = "https://github.com/eclipse-score/toolchains_gcc_packages/releases/download/0.0.1/x86_64-unknown-linux-gnu_gcc12.tar.gz", sha256 = "457f5f20f57528033cb840d708b507050d711ae93e009388847e113b11bf3600", @@ -56,26 +57,17 @@ gcc.warning_flags( use_repo(gcc, "gcc_toolchain", "gcc_toolchain_gcc") register_toolchains("@gcc_toolchain//:all") -# LLVM Toolchains -bazel_dep(name = "toolchains_llvm", version = "1.2.0") # persistency module uses 1.2.0 and does not work with 1.4.0 yet -llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") -llvm.toolchain( - cxx_standard = {"": "c++17"}, - llvm_version = "19.1.0", -) -use_repo(llvm, "llvm_toolchain") -use_repo(llvm, "llvm_toolchain_llvm") - -register_toolchains("@llvm_toolchain//:all") - -## needed additions to build -bazel_dep(name = "testing-utils") +bazel_dep(name = "score_test_scenarios", version = "0.3.0") git_override( - module_name = "testing-utils", - commit = "a847c7464cfa47e000141631d1223b92560d2e58", # tag v0.2.0 - remote = "https://github.com/qorix-group/testing_tools.git", + module_name = "score_test_scenarios", + commit = "a2f9cded3deb636f5dc800bf7a47131487119721", # tag v0.3.0 + remote = "https://github.com/eclipse-score/testing_tools.git", ) +# Needed for feature integration tests +bazel_dep(name = "rules_rust", version = "0.61.0") +bazel_dep(name = "score_itf", version = "0.1.0") + # # TODO: What is this for? archive_override( module_name = "rules_boost", diff --git a/README.md b/README.md index 99cc231aeb..44430b3e70 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,18 @@ bazel build \ > Note: Python tests for `@score_persistency` cannot be built from this integration workspace due to Bazel external repository visibility limitations. The pip extension and Python dependencies must be accessed within their defining module. +### Orchestration and `kyron` - async runtime for Rust + +```bash +bazel build @score_orchestrator//src/... +``` + +## Feature showcase examples +The examples that are aiming to showcase features provided by S-CORE are located in `feature_showcase` folder. +You can run them currently for host platform using `--config bl-x86_64-linux`. + +Execute `bazel query //feature_showcase/...` to obtain list of targets that You can run. + ## ⚠️ Observed Issues ### communication: score/mw/com/requirements @@ -108,6 +120,12 @@ local_path_override(module_name = "score_tooling", path = "../tooling") - Unify LLVM toolchain versions across modules. - Introduce integration tests for `@itf` once build succeeds. +## IDE support + +### Rust + +Use `./generate_rust_analyzer_support.sh` to generate rust_analyzer settings that will let VS Code work. + ## 📌 Quick Reference | Area | Status | Action | diff --git a/ci/showcase_targets_run.txt b/ci/showcase_targets_run.txt new file mode 100644 index 0000000000..fcd499b36a --- /dev/null +++ b/ci/showcase_targets_run.txt @@ -0,0 +1,2 @@ +//feature_showcase/rust:kyron_example +//feature_showcase/rust:orch_per_example diff --git a/feature_integration_tests/rust_test_scenarios/BUILD b/feature_integration_tests/rust_test_scenarios/BUILD new file mode 100644 index 0000000000..78d0b3b53c --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +rust_binary( + name = "rust_test_scenarios", + srcs = glob(["src/**/*.rs"]), + tags = [ + "manual", + ], + deps = [ + "@score_orchestrator//src/kyron:libkyron", + "@score_orchestrator//src/kyron-foundation:libkyron_foundation", + "@score_orchestrator//src/orchestration:liborchestration", + "@score_persistency//src/rust/rust_kvs:rust_kvs", + "@score_test_scenarios//test_scenarios_rust:test_scenarios_rust", + "@score_crates//:tracing", + "@score_crates//:serde", + "@score_crates//:serde_json", + ] +) \ No newline at end of file diff --git a/feature_integration_tests/rust_test_scenarios/src/internals/mod.rs b/feature_integration_tests/rust_test_scenarios/src/internals/mod.rs new file mode 100644 index 0000000000..2938643029 --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/internals/mod.rs @@ -0,0 +1,13 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// +pub mod runtime_helper; diff --git a/feature_integration_tests/rust_test_scenarios/src/internals/runtime_helper.rs b/feature_integration_tests/rust_test_scenarios/src/internals/runtime_helper.rs new file mode 100644 index 0000000000..341d22be3c --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/internals/runtime_helper.rs @@ -0,0 +1,163 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// +use kyron::core::types::UniqueWorkerId; +use kyron::prelude::ThreadParameters as AsyncRtThreadParameters; +use kyron::runtime::*; +use kyron::scheduler::SchedulerType; +use serde::{de, Deserialize, Deserializer}; +use serde_json::Value; +use tracing::debug; + +fn deserialize_scheduler_type<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let value_opt: Option = Option::deserialize(deserializer)?; + if let Some(value_str) = value_opt { + let value = match value_str.as_str() { + "fifo" => SchedulerType::Fifo, + "round_robin" => SchedulerType::RoundRobin, + "other" => SchedulerType::Other, + _ => return Err(de::Error::custom("Unknown scheduler type")), + }; + return Ok(Some(value)); + } + + Ok(None) +} + +/// Thread parameters. +#[derive(Deserialize, Debug, Clone)] +pub struct ThreadParameters { + pub thread_priority: Option, + pub thread_affinity: Option>, + pub thread_stack_size: Option, + #[serde(default, deserialize_with = "deserialize_scheduler_type")] + pub thread_scheduler: Option, +} + +/// Dedicated worker configuration. +#[derive(Deserialize, Debug, Clone)] +pub struct DedicatedWorkerConfig { + pub id: String, + #[serde(flatten)] + pub thread_parameters: ThreadParameters, +} + +/// Execution engine configuration. +#[derive(Deserialize, Debug, Clone)] +pub struct ExecEngineConfig { + pub task_queue_size: u32, + pub workers: usize, + #[serde(flatten)] + pub thread_parameters: ThreadParameters, + pub dedicated_workers: Option>, +} + +fn deserialize_exec_engines<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum RuntimeConfig { + Object(ExecEngineConfig), + Array(Vec), + } + + let exec_engines = match RuntimeConfig::deserialize(deserializer)? { + RuntimeConfig::Object(exec_engine_config) => vec![exec_engine_config], + RuntimeConfig::Array(exec_engine_configs) => exec_engine_configs, + }; + Ok(exec_engines) +} + +#[derive(Deserialize, Debug)] +#[serde(transparent)] +pub struct Runtime { + #[serde(deserialize_with = "deserialize_exec_engines")] + exec_engines: Vec, +} + +impl Runtime { + /// Parse `Runtime` from JSON string. + /// JSON is expected to contain `runtime` field. + pub fn from_json(json_str: &str) -> Result { + let v: Value = serde_json::from_str(json_str).map_err(|e| e.to_string())?; + serde_json::from_value(v["runtime"].clone()).map_err(|e| e.to_string()) + } + + pub fn exec_engines(&self) -> &Vec { + &self.exec_engines + } + + pub fn build(&self) -> kyron::runtime::Runtime { + debug!("Creating kyron::Runtime with {} execution engines", self.exec_engines.len()); + + let mut async_rt_builder = kyron::runtime::RuntimeBuilder::new(); + for exec_engine in self.exec_engines.as_slice() { + debug!("Creating ExecutionEngine with: {:?}", exec_engine); + + let mut exec_engine_builder = ExecutionEngineBuilder::new() + .task_queue_size(exec_engine.task_queue_size) + .workers(exec_engine.workers); + + // Set thread parameters. + { + let thread_params = &exec_engine.thread_parameters; + if let Some(thread_priority) = thread_params.thread_priority { + exec_engine_builder = exec_engine_builder.thread_priority(thread_priority); + } + if let Some(thread_affinity) = &thread_params.thread_affinity { + exec_engine_builder = exec_engine_builder.thread_affinity(thread_affinity); + } + if let Some(thread_stack_size) = thread_params.thread_stack_size { + exec_engine_builder = exec_engine_builder.thread_stack_size(thread_stack_size); + } + if let Some(thread_scheduler) = thread_params.thread_scheduler { + exec_engine_builder = exec_engine_builder.thread_scheduler(thread_scheduler); + } + } + + // Set dedicated workers. + if let Some(dedicated_workers) = &exec_engine.dedicated_workers { + for dedicated_worker in dedicated_workers { + // Create thread parameters object. + let mut async_rt_thread_params = AsyncRtThreadParameters::default(); + if let Some(thread_priority) = dedicated_worker.thread_parameters.thread_priority { + async_rt_thread_params = async_rt_thread_params.priority(thread_priority); + } + if let Some(thread_affinity) = &dedicated_worker.thread_parameters.thread_affinity { + async_rt_thread_params = async_rt_thread_params.affinity(thread_affinity); + } + if let Some(thread_stack_size) = dedicated_worker.thread_parameters.thread_stack_size { + async_rt_thread_params = async_rt_thread_params.stack_size(thread_stack_size); + } + if let Some(thread_scheduler) = dedicated_worker.thread_parameters.thread_scheduler { + async_rt_thread_params = async_rt_thread_params.scheduler_type(thread_scheduler); + } + + // Create `UniqueWorkerId`. + let unique_worker_id = UniqueWorkerId::from(&dedicated_worker.id); + + exec_engine_builder = exec_engine_builder.with_dedicated_worker(unique_worker_id, async_rt_thread_params); + } + } + + let (builder, _) = async_rt_builder.with_engine(exec_engine_builder); + async_rt_builder = builder; + } + + async_rt_builder.build().expect("Failed to build async runtime") + } +} diff --git a/feature_integration_tests/rust_test_scenarios/src/main.rs b/feature_integration_tests/rust_test_scenarios/src/main.rs new file mode 100644 index 0000000000..4220d66f91 --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/main.rs @@ -0,0 +1,31 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +mod internals; +mod tests; + +use test_scenarios_rust::cli::run_cli_app; +use test_scenarios_rust::test_context::TestContext; + +use crate::tests::root_scenario_group; + +fn main() -> Result<(), String> { + let raw_arguments: Vec = std::env::args().collect(); + + // Root group. + let root_group = root_scenario_group(); + + // Run. + let test_context = TestContext::new(root_group); + run_cli_app(&raw_arguments, &test_context) +} diff --git a/feature_integration_tests/rust_test_scenarios/src/tests/basic/mod.rs b/feature_integration_tests/rust_test_scenarios/src/tests/basic/mod.rs new file mode 100644 index 0000000000..3c1634b160 --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/tests/basic/mod.rs @@ -0,0 +1,36 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// +use orchestration::prelude::*; +use test_scenarios_rust::scenario::{ScenarioGroup, ScenarioGroupImpl}; +use tracing::info; + +mod only_shutdown_sequence; + +fn simple_checkpoint(id: &str) { + info!(id = id); +} + +async fn basic_task() -> InvokeResult { + simple_checkpoint("basic_task"); + Ok(()) +} + +pub fn basic_scenario_group() -> Box { + Box::new(ScenarioGroupImpl::new( + "basic", + vec![ + Box::new(only_shutdown_sequence::OnlyShutdownSequence), + ], + vec![], + )) +} diff --git a/feature_integration_tests/rust_test_scenarios/src/tests/basic/only_shutdown_sequence.rs b/feature_integration_tests/rust_test_scenarios/src/tests/basic/only_shutdown_sequence.rs new file mode 100644 index 0000000000..0ff1910bcd --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/tests/basic/only_shutdown_sequence.rs @@ -0,0 +1,36 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// +use crate::internals::runtime_helper::Runtime; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +pub struct OnlyShutdownSequence; + +/// Checks (almost) empty program with only shutdown +impl Scenario for OnlyShutdownSequence { + fn name(&self) -> &str { + "only_shutdown" + } + + fn run(&self, input: &str) -> Result<(), String> { + let mut rt = Runtime::from_json(input)?.build(); + + rt.block_on(async move { + info!("Program entered engine"); + // TODO: Create a program with only shutdown sequence once it is supported. + info!("Program execution finished"); + }); + + Ok(()) + } +} diff --git a/feature_integration_tests/rust_test_scenarios/src/tests/mod.rs b/feature_integration_tests/rust_test_scenarios/src/tests/mod.rs new file mode 100644 index 0000000000..2a381791fa --- /dev/null +++ b/feature_integration_tests/rust_test_scenarios/src/tests/mod.rs @@ -0,0 +1,25 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// +use test_scenarios_rust::scenario::{ScenarioGroup, ScenarioGroupImpl}; + +mod basic; + +use basic::basic_scenario_group; + +pub fn root_scenario_group() -> Box { + Box::new(ScenarioGroupImpl::new( + "root", + vec![], + vec![basic_scenario_group()], + )) +} diff --git a/feature_showcase/BUILD b/feature_showcase/BUILD new file mode 100644 index 0000000000..6844e029a7 --- /dev/null +++ b/feature_showcase/BUILD @@ -0,0 +1,19 @@ +filegroup( + name = "all_examples", + srcs = [ + "//feature_showcase/rust:kyron_example", + "//feature_showcase/rust:orch_per_example", + ], +) + + + +genrule( + name = "build_all", + srcs = [ + "//feature_showcase/rust:kyron_example", + "//feature_showcase/rust:orch_per_example", + ], + outs = ["done.txt"], + cmd = "echo built > $@", +) \ No newline at end of file diff --git a/feature_showcase/rust/BUILD b/feature_showcase/rust/BUILD new file mode 100644 index 0000000000..13596103af --- /dev/null +++ b/feature_showcase/rust/BUILD @@ -0,0 +1,39 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +rust_binary( + name = "kyron_example", + srcs = glob(["kyron/**/*.rs"]), + deps = [ + "@score_orchestrator//src/kyron:libkyron", + "@score_orchestrator//src/kyron-foundation:libkyron_foundation", + "@score_crates//:tracing", + ], + visibility = ["//visibility:public"], +) + +rust_binary( + name = "orch_per_example", + srcs = glob(["orchestration_persistency/**/*.rs"]), + deps = [ + "@score_orchestrator//src/kyron:libkyron", + "@score_orchestrator//src/kyron-foundation:libkyron_foundation", + "@score_orchestrator//src/orchestration:liborchestration", + "@score_orchestrator//src/logging_tracing:liblogging_tracing", + "@score_crates//:tracing", + "@score_persistency//src/rust/rust_kvs:rust_kvs", + ], + visibility = ["//visibility:public"], +) diff --git a/feature_showcase/rust/kyron/main.rs b/feature_showcase/rust/kyron/main.rs new file mode 100644 index 0000000000..be8bfc5a09 --- /dev/null +++ b/feature_showcase/rust/kyron/main.rs @@ -0,0 +1,47 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +use kyron::channels::spsc::*; +use kyron::spawn; +use kyron_foundation::prelude::*; + +/// Example entry point demonstrating the usage of the kyron async runtime +/// +/// This example shows how to set up a Kyron async runtime with default engine parameters. +/// It spawns a sender and receiver task using Kyron's channel and spawn utilities. +/// Useful for learning how to structure Kyron async applications and macro usage. +/// For more visit https://github.com/eclipse-score/orchestrator +#[kyron::main] +async fn main() { + tracing_subscriber::fmt().with_target(false).with_max_level(Level::INFO).init(); + + let (sender, mut receiver) = create_channel_default::(); + + let r = spawn(async move { + while let Some(value) = receiver.recv().await { + info!("Received {}", value); + } + }); + + let s = spawn(async move { + info!("Hello from Kyron sender!"); + + for i in 0..5_u32 { + info!("Sending {}", i); + sender.send(&i).unwrap(); + } + }); + + s.await.unwrap(); + r.await.unwrap(); +} diff --git a/feature_showcase/rust/orchestration_persistency/main.rs b/feature_showcase/rust/orchestration_persistency/main.rs new file mode 100644 index 0000000000..e77c2d5a8b --- /dev/null +++ b/feature_showcase/rust/orchestration_persistency/main.rs @@ -0,0 +1,165 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::time::Duration; + +use kyron::runtime::*; +use kyron_foundation::prelude::*; +use logging_tracing::TracingLibraryBuilder; +use orchestration::{ + actions::{invoke::Invoke, sequence::SequenceBuilder, sync::SyncBuilder, trigger::TriggerBuilder}, + api::{design::Design, Orchestration}, + common::DesignConfig, + prelude::InvokeResult, +}; + +use rust_kvs::prelude::*; + +// Example Summary: +// This example demonstrates how to use the orchestration framework to coordinate two design programs: +// 1. Camera processing (simulates a camera producing images) +// 2. Object detection (simulates processing those images) +// +// The components communicate via events. The camera component triggers an event when an image is ready, +// which starts the object detection component. Persistency is demonstrated using a key-value store during shutdown. +// The orchestration is run using the Kyron async runtime, and the program chains are executed concurrently. + + +const CAMERA_IMG_READY: &str = "CameraImageReadyEvent"; +const CAMERA_IMG_PROCESSED: &str = "CameraImageProcessedEvent"; + +async fn on_camera_image_ready() -> InvokeResult { + info!("CAMERA LOGIC"); + // Simulate image processing + Ok(()) +} + +async fn detect_objects() -> InvokeResult { + info!("OBJECT DETECTION LOGIC!"); + // Simulate handling object detection + Ok(()) +} + +async fn on_shutdown() -> InvokeResult { + info!("Shutdown logic executed."); + + // Instance ID for KVS object instances. + let instance_id = InstanceId(0); + let builder = KvsBuilder::new(instance_id) + .dir("./") + .kvs_load(KvsLoad::Optional); + let kvs = builder.build().unwrap(); + + kvs.set_value("number", 123.0).unwrap(); + kvs.set_value("bool", true).unwrap(); + + kvs.flush().unwrap(); + + Ok(()) +} + +fn camera_processing_component_design() -> Result { + let mut design = Design::new("CameraProcessingDesign".into(), DesignConfig::default()); + + // Register events and invoke actions in design so it knows how to build task chains + design.register_event(CAMERA_IMG_READY.into())?; + design.register_event(CAMERA_IMG_PROCESSED.into())?; + let on_camera_image_ready_tag = design.register_invoke_async("on_camera_image_ready".into(), on_camera_image_ready)?; + + // Create a program describing task chain + design.add_program("CameraProcessingProgram", move |design_instance, builder| { + builder.with_run_action( + SequenceBuilder::new() + .with_step(SyncBuilder::from_design(CAMERA_IMG_READY, design_instance)) + .with_step(Invoke::from_tag(&on_camera_image_ready_tag, design_instance.config())) + .with_step(TriggerBuilder::from_design(CAMERA_IMG_PROCESSED, design_instance)) + .build(), + ); + Ok(()) + }); + + Ok(design) +} + +fn detect_object_component_design() -> Result { + let mut design = Design::new("DetectObjectDesign".into(), DesignConfig::default()); + + // Register events and invoke actions in design so it knows how to build task chains + design.register_event(CAMERA_IMG_PROCESSED.into())?; + let detect_objects_tag = design.register_invoke_async("detect_objects".into(), detect_objects)?; + let on_shutdown_tag = design.register_invoke_async("on_shutdown".into(), on_shutdown)?; + + // Create a program describing task chain + design.add_program("DetectObjectProgram", move |design_instance, builder| { + builder.with_run_action( + SequenceBuilder::new() + .with_step(SyncBuilder::from_design(CAMERA_IMG_PROCESSED, design_instance)) + .with_step(Invoke::from_tag(&detect_objects_tag, design_instance.config())) + .build(), + ).with_stop_action( + Invoke::from_tag(&on_shutdown_tag, design_instance.config()), Duration::from_secs(2) + ); + Ok(()) + }); + + Ok(design) +} + +fn main() { + // Setup any logging framework you want to use. + let mut logger = TracingLibraryBuilder::new().global_log_level(Level::INFO).enable_logging(true).build(); + + logger.init_log_trace(); + + // Create runtime + let (builder, _engine_id) = kyron::runtime::RuntimeBuilder::new().with_engine(ExecutionEngineBuilder::new().task_queue_size(256).workers(2)); + let mut runtime = builder.build().unwrap(); + + // Build Orchestration + let mut orch = Orchestration::new() + .add_design(camera_processing_component_design().expect("Failed to create camera_processing_component_design")) + .add_design(detect_object_component_design().expect("Failed to create detect_object_component_design")) + .design_done(); + + // Specify deployment information, ie. which event is local, which timer etc + orch.get_deployment_mut() + .bind_events_as_local(&[CAMERA_IMG_PROCESSED.into()]) + .expect("Failed to specify event"); + + orch.get_deployment_mut() + .bind_events_as_timer(&[CAMERA_IMG_READY.into()], Duration::from_millis(30)) + .expect("Failed to specify event"); + + // Create programs + let mut program_manager = orch.into_program_manager().unwrap(); + let mut programs = program_manager.get_programs(); + + // Put programs into runtime and run them + runtime.block_on(async move { + let mut program1 = programs.pop().unwrap(); + let mut program2 = programs.pop().unwrap(); + + let h1 = kyron::spawn(async move { + let _ = program1.run_n(3).await; + }); + + let h2 = kyron::spawn(async move { + let _ = program2.run_n(3).await; + }); + + let _ = h1.await; + let _ = h2.await; + + info!("Programs finished running"); + }); +} diff --git a/generate_rust_analyzer_support.sh b/generate_rust_analyzer_support.sh new file mode 100755 index 0000000000..23998bb4ce --- /dev/null +++ b/generate_rust_analyzer_support.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +# Manual targets are not take into account, must be set explicitly +bazel run @rules_rust//tools/rust_analyzer:gen_rust_project -- "@//feature_showcase/..." "@//feature_integration_tests/rust_test_scenarios:rust_test_scenarios" \ No newline at end of file diff --git a/integration_test.sh b/integration_test.sh index 75da1b4fc4..b8593a52ce 100755 --- a/integration_test.sh +++ b/integration_test.sh @@ -14,6 +14,7 @@ declare -A BUILD_TARGET_GROUPS=( [communication]="@communication//score/... @communication//third_party/..." [persistency]="@score_persistency//src/... @score_persistency//tests/cpp_test_scenarios/... @score_persistency//tests/rust_test_scenarios/..." [score-mw-log]="@score-mw-log//src/..." + [score-orchestrator]="@score_orchestrator//src/..." ) warn_count() { diff --git a/score_modules.MODULE.bazel b/score_modules.MODULE.bazel index 0fb42c4ccf..2e359ce901 100644 --- a/score_modules.MODULE.bazel +++ b/score_modules.MODULE.bazel @@ -24,16 +24,17 @@ git_override( bazel_dep(name = "score_persistency", version = "0.0.0") git_override( module_name = "score_persistency", - remote = "https://github.com/eclipse-score/persistency.git", - commit = "c3b737bb45ba9ddc9db0a3940ab2490c75fc3c02", + remote = "https://github.com/qorix-group/persistency.git", + #commit = "d911c73f58b1e02187e40902e9005c6d6f99cc1f", + commit = "02195a843d6b927c30dc0baa3f0a23af87be1c17", ) ## --- orchestration --- -bazel_dep(name = "inc_orchestrator", version = "0.0.0") +bazel_dep(name = "score_orchestrator", version = "0.0.2") git_override( - module_name = "inc_orchestrator", - remote = "https://github.com/eclipse-score/inc_orchestrator.git", - commit = "9f8ed482bdf786117eb3c4c1be14a1ee58a120ab", + module_name = "score_orchestrator", + remote = "https://github.com/eclipse-score/orchestrator.git", + commit = "ef7b2f2b339134daf281d3913d3a172cd252bf94", ) ## --- communication ---