zenoh_flow/runtime/mod.rs
1//
2// Copyright (c) 2021 - 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12// ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15#![allow(clippy::manual_async_fn)]
16use std::collections::HashMap;
17use std::convert::TryFrom;
18
19use crate::model::descriptor::{
20 FlattenDataFlowDescriptor, OperatorDescriptor, SinkDescriptor, SourceDescriptor,
21};
22use crate::model::record::DataFlowRecord;
23use serde::{Deserialize, Serialize};
24use std::sync::Arc;
25use uuid::Uuid;
26use zenoh::prelude::ZenohId;
27
28use self::dataflow::loader::LoaderConfig;
29use crate::runtime::dataflow::loader::Loader;
30use crate::types::{ControlMessage, FlowId, RuntimeId};
31use crate::zferror;
32use crate::zfresult::ErrorKind;
33use crate::{DaemonResult, Result as ZFResult};
34use uhlc::{Timestamp, HLC};
35use zenoh::Session;
36use zrpc::zrpcresult::{ZRPCError, ZRPCResult};
37use zrpc_macros::zservice;
38
39pub mod dataflow;
40pub mod resources;
41pub mod worker_pool;
42
43/// The context of a Zenoh Flow runtime.
44/// This is shared across all the instances in a runtime.
45/// It allows sharing the `zenoh::Session`, the `Loader`,
46/// the `HLC` and other relevant singletons.
47#[derive(Clone)]
48pub struct RuntimeContext {
49 pub session: Arc<Session>,
50 pub loader: Arc<Loader>,
51 pub hlc: Arc<HLC>,
52 pub runtime_name: RuntimeId,
53 pub runtime_uuid: ZenohId,
54 pub shared_memory_element_size: usize,
55 pub shared_memory_elements: usize,
56 pub shared_memory_backoff: u64,
57 pub use_shm: bool,
58}
59
60/// The context of a Zenoh Flow graph instance.
61#[derive(Clone)]
62pub struct InstanceContext {
63 pub flow_id: FlowId,
64 pub instance_id: Uuid,
65 pub runtime: RuntimeContext,
66}
67
68/// This function maps a [`FlattenDataFlowDescriptor`](`FlattenDataFlowDescriptor`) into
69/// the infrastructure.
70/// The initial implementation simply maps all missing mapping
71/// to the provided runtime.
72///
73/// # Errors
74/// An error variant is returned in case of:
75/// - unable to map node to infrastructure
76pub async fn map_to_infrastructure(
77 mut descriptor: FlattenDataFlowDescriptor,
78 runtime: &str,
79) -> ZFResult<FlattenDataFlowDescriptor> {
80 log::debug!("[Dataflow mapping] Begin mapping for: {}", descriptor.flow);
81
82 let runtime_id: Arc<str> = runtime.into();
83
84 // Initial "stupid" mapping, if an operator is not mapped, we map to the local runtime.
85 // function is async because it could involve other nodes.
86 let mut mapping = descriptor.mapping.clone().map_or(HashMap::new(), |m| m);
87
88 for o in &descriptor.operators {
89 mapping
90 .entry(o.id.clone())
91 .or_insert_with(|| runtime_id.clone());
92 }
93
94 for o in &descriptor.sources {
95 mapping
96 .entry(o.id.clone())
97 .or_insert_with(|| runtime_id.clone());
98 }
99
100 for o in &descriptor.sinks {
101 mapping
102 .entry(o.id.clone())
103 .or_insert_with(|| runtime_id.clone());
104 }
105 log::trace!(
106 "[Dataflow mapping] Mapping for: {} is {:?}",
107 descriptor.flow,
108 mapping
109 );
110 descriptor.mapping = Some(mapping);
111 Ok(descriptor)
112}
113
114// Runtime related types, maybe can be moved.
115
116/// Runtime Status, either Ready or Not Ready.
117/// When Ready it is able to accept commands and instantiate graphs.
118#[derive(Serialize, Deserialize, Debug, Clone)]
119#[serde(rename_all = "lowercase")]
120pub enum RuntimeStatusKind {
121 Ready,
122 NotReady,
123}
124
125/// The Runtime information.
126#[derive(Serialize, Deserialize, Debug, Clone)]
127pub struct RuntimeInfo {
128 pub id: ZenohId,
129 pub name: Arc<str>,
130 pub tags: Vec<String>,
131 pub status: RuntimeStatusKind,
132 // Do we need/want also RAM usage?
133}
134
135/// The detailed runtime status.
136#[derive(Serialize, Deserialize, Debug, Clone)]
137pub struct RuntimeStatus {
138 pub id: ZenohId,
139 pub running_flows: usize,
140 pub running_operators: usize,
141 pub running_sources: usize,
142 pub running_sinks: usize,
143 pub running_connectors: usize,
144}
145
146/// Wrapper for Zenoh kind.
147#[derive(Serialize, Deserialize, Debug, Clone)]
148#[serde(rename_all = "lowercase")]
149pub enum ZenohConfigKind {
150 Peer,
151 Client,
152}
153
154impl std::fmt::Display for ZenohConfigKind {
155 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
156 match self {
157 ZenohConfigKind::Peer => write!(f, "peer"),
158 ZenohConfigKind::Client => write!(f, "client"),
159 }
160 }
161}
162
163impl TryFrom<zenoh::config::whatami::WhatAmI> for ZenohConfigKind {
164 type Error = crate::zfresult::Error;
165 fn try_from(value: zenoh::config::whatami::WhatAmI) -> Result<Self, Self::Error> {
166 match value {
167 zenoh::config::whatami::WhatAmI::Client => Ok(Self::Client),
168 zenoh::config::whatami::WhatAmI::Peer => Ok(Self::Peer),
169 _ => Err(zferror!(ErrorKind::MissingConfiguration).into()),
170 }
171 }
172}
173
174#[allow(clippy::from_over_into)]
175impl Into<zenoh::config::whatami::WhatAmI> for ZenohConfigKind {
176 fn into(self) -> zenoh::config::whatami::WhatAmI {
177 match self {
178 Self::Peer => zenoh::config::whatami::WhatAmI::Peer,
179 Self::Client => zenoh::config::whatami::WhatAmI::Client,
180 }
181 }
182}
183
184/// The runtime configuration.
185#[derive(Serialize, Deserialize, Debug, Clone)]
186pub struct RuntimeConfig {
187 pub pid_file: String, //Where the PID file resides
188 pub path: String, //Where the libraries are downloaded/located
189 pub name: String,
190 pub uuid: ZenohId,
191 pub loader: LoaderConfig,
192}
193
194/// The type of [`Job`](`Job`) to be executed by the workers
195///
196/// [^note]: This enum is not exhaustive yet, it will evolve in the future
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub enum JobKind {
199 CreateInstance(FlattenDataFlowDescriptor, Uuid),
200 DeleteInstance(Uuid),
201 Instantiate(FlattenDataFlowDescriptor, Uuid),
202 Teardown(Uuid),
203 StartInstance(Uuid),
204 StopInstance(Uuid),
205 StartNode(Uuid, String),
206 StopNode(Uuid, String),
207}
208
209/// The status of a [`Job`](`Job`), associated with a timestamp from when the status change happenend
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub enum JobStatus {
212 Submitted(Timestamp),
213 Started(Timestamp),
214 Done(Timestamp),
215 Failed(Timestamp, String),
216}
217
218/// All the needed information to run a Job
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct Job {
221 id: Uuid,
222 job: JobKind,
223 status: JobStatus,
224 assignee: Option<usize>,
225}
226
227impl Job {
228 fn new_instantiate(
229 dfd: FlattenDataFlowDescriptor,
230 instance_id: Uuid,
231 id: Uuid,
232 ts: Timestamp,
233 ) -> Self {
234 Self {
235 id,
236 job: JobKind::Instantiate(dfd, instance_id),
237 status: JobStatus::Submitted(ts),
238 assignee: None,
239 }
240 }
241
242 fn new_create(
243 dfd: FlattenDataFlowDescriptor,
244 instance_id: Uuid,
245 id: Uuid,
246 ts: Timestamp,
247 ) -> Self {
248 Self {
249 id,
250 job: JobKind::CreateInstance(dfd, instance_id),
251 status: JobStatus::Submitted(ts),
252 assignee: None,
253 }
254 }
255
256 fn new_teardown(fid: Uuid, id: Uuid, ts: Timestamp) -> Self {
257 Self {
258 id,
259 job: JobKind::Teardown(fid),
260 status: JobStatus::Submitted(ts),
261 assignee: None,
262 }
263 }
264
265 fn new_delete(fid: Uuid, id: Uuid, ts: Timestamp) -> Self {
266 Self {
267 id,
268 job: JobKind::DeleteInstance(fid),
269 status: JobStatus::Submitted(ts),
270 assignee: None,
271 }
272 }
273
274 fn new_start(fid: Uuid, id: Uuid, ts: Timestamp) -> Self {
275 Self {
276 id,
277 job: JobKind::StartInstance(fid),
278 status: JobStatus::Submitted(ts),
279 assignee: None,
280 }
281 }
282
283 fn new_stop(fid: Uuid, id: Uuid, ts: Timestamp) -> Self {
284 Self {
285 id,
286 job: JobKind::StopInstance(fid),
287 status: JobStatus::Submitted(ts),
288 assignee: None,
289 }
290 }
291
292 fn new_start_node(fid: Uuid, node_id: String, id: Uuid, ts: Timestamp) -> Self {
293 Self {
294 id,
295 job: JobKind::StartNode(fid, node_id),
296 status: JobStatus::Submitted(ts),
297 assignee: None,
298 }
299 }
300
301 fn new_stop_node(fid: Uuid, node_id: String, id: Uuid, ts: Timestamp) -> Self {
302 Self {
303 id,
304 job: JobKind::StopNode(fid, node_id),
305 status: JobStatus::Submitted(ts),
306 assignee: None,
307 }
308 }
309
310 pub fn get_id(&self) -> &Uuid {
311 &self.id
312 }
313
314 pub fn get_kind(&self) -> &JobKind {
315 &self.job
316 }
317
318 pub fn get_status(&self) -> &JobStatus {
319 &self.status
320 }
321
322 pub fn get_assigne(&self) -> &Option<usize> {
323 &self.assignee
324 }
325
326 pub fn set_status(&mut self, status: JobStatus) {
327 self.status = status;
328 }
329
330 pub fn assign(&mut self, assignee: usize) {
331 self.assignee.replace(assignee);
332 }
333
334 pub fn started(&mut self, assignee: usize, ts: Timestamp) {
335 self.assignee.replace(assignee);
336 self.status = JobStatus::Started(ts);
337 }
338
339 pub fn done(&mut self, ts: Timestamp) {
340 self.status = JobStatus::Done(ts);
341 }
342
343 pub fn failed(&mut self, ts: Timestamp, error_description: String) {
344 self.status = JobStatus::Failed(ts, error_description)
345 }
346}
347
348/// The interface the Daemon expose to a client
349/// (eg. the cli, or, the mgmt API)[^note]
350/// The service is exposed using zenoh-rpc, the server and client
351/// are generated automatically.
352///
353/// [^note]: We may split this interface in the future.
354#[zservice(
355 timeout_s = 60,
356 prefix = "zf/daemon",
357 service_uuid = "11111111111111111111111111111111"
358)]
359pub trait DaemonInterface {
360 /// Creates an instance of the given [`FlattenDataFlowDescriptor`][^note].
361 ///
362 /// This function:
363 /// 1) Generates the instance `Uuid`
364 /// 2) Maps the flow into the infrastructure
365 /// 3) Creates the associated record
366 /// 4) Stores the record in Zenoh
367 /// 5) Prepares all the involved runtimes to host the data flow instance
368 ///
369 /// Returns the [`Uuid`] associated with the instance.
370 ///
371 /// [^note]: When the registry will be in place it will take the Flow identifier as parameter
372 ///
373 /// # Errors
374 ///
375 /// An error variant is returned in case of:
376 /// - error on zenoh-rpc
377 /// - unable to map
378 /// - unable to prepare nodes
379 async fn create_instance(&self, flow: FlattenDataFlowDescriptor) -> DaemonResult<Uuid>;
380 //TODO: workaround - it should just take the ID of the flow (when
381 // the registry will be in place)
382
383 /// Deletes the given instance.
384 ///
385 /// This function:
386 /// 1) Cleans the instance nodes from all the involved runtimes.
387 /// 2) Deletes the record from zenoh
388 ///
389 /// # Errors
390 ///
391 /// An error variant is returned in case of:
392 /// - error on zenoh-rpc
393 /// - instance not stopped
394 /// - unable to clean
395 /// - zenoh error
396 async fn delete_instance(&self, instance_id: Uuid) -> DaemonResult<DataFlowRecord>;
397
398 /// Instantiates the given [`FlattenDataFlowDescriptor`][^note].
399 ///
400 /// The instance contains an [`Uuid`] that identifies it uniquely.
401 /// The actual instantiation process runs asynchronously in the runtime.
402 ///
403 /// Returns the [`Uuid`] associated with the instance.
404 ///
405 /// It is equivalent to calling `create_instance` and then `start_instance`.
406 ///
407 /// [^note]: When the registry will be in place it will take the Flow identifier as parameter.
408 ///
409 /// # Errors
410 ///
411 /// An error variant is returned in case of:
412 /// - error on zenoh-rpc
413 /// - unable to instantiate
414 async fn instantiate(&self, flow: FlattenDataFlowDescriptor) -> DaemonResult<Uuid>;
415 //TODO: workaround - it should just take the ID of the flow (when
416 // the registry will be in place)
417
418 /// Sends a teardown request for the given instance identified by the [`Uuid`].
419 ///
420 /// Note that the request is asynchronous, the runtime that receives the request will return
421 /// immediately, but the teardown process will run asynchronously in the runtime.
422 ///
423 /// It is equivalent to calling `stop_instance` and then `delete_instance`.
424 ///
425 /// # Errors
426 /// An error variant is returned in case of:
427 /// - error on zenoh-rpc
428 /// - unable to teardown
429 /// - instance not found
430 async fn teardown(&self, instance_id: Uuid) -> DaemonResult<DataFlowRecord>;
431
432 /// Starts the instance on all involved nodes.
433 ///
434 /// It first starts all the nodes and then the sources.
435 ///
436 /// # Errors
437 ///
438 /// An error variant is returned in case of:
439 /// - error on zenoh-rpc
440 /// - instance not found
441 /// - instance already started
442 async fn start_instance(&self, instance_id: Uuid) -> DaemonResult<()>;
443
444 /// Stops the instance on all involved nodes.
445 ///
446 /// It first stops the sources then the other nodes.
447 ///
448 /// # Errors
449 /// An error variant is returned in case of:
450 /// - error on zenoh-rpc
451 /// - unable to clean
452 async fn stop_instance(&self, instance_id: Uuid) -> DaemonResult<DataFlowRecord>;
453
454 /// Starts the given graph node for the given instance.
455 /// A graph node can be a source, a sink, a connector, or an operator.
456 ///
457 /// # Errors
458 /// An error variant is returned in case of:
459 /// - error on zenoh-rpc
460 /// - record not found
461 /// - node already started
462 /// - node not found
463 async fn start_node(&self, instance_id: Uuid, node: String) -> DaemonResult<()>;
464
465 /// Stops the given graph node from the given instance.
466 /// A graph node can be a source, a sink, a connector, or an operator.
467 ///
468 /// # Errors
469 /// An error variant is returned in case of:
470 /// - error on zenoh-rpc
471 /// - instance not found
472 /// - node not found
473 /// - node already stopped
474 async fn stop_node(&self, instance_id: Uuid, node: String) -> DaemonResult<()>;
475
476 // FIXME A source now has several outputs.
477
478 // /// Start a recording for the given source.
479 // ///
480 // /// # Errors
481 // /// An error variant is returned in case of:
482 // /// - error on zenoh-rpc
483 // /// - record not found
484 // /// - record already started
485 // /// - source not found
486 // /// - node is not a source
487 // async fn start_record(&self, instance_id: Uuid, source_id: NodeId) -> DaemonResult<String>;
488
489 // /// Stops the recording for the given source.
490 // ///
491 // /// # Errors
492 // /// An error variant is returned in case of:
493 // /// - error on zenoh-rpc
494 // /// - record not found
495 // /// - record already stopped
496 // /// - source not found
497 // /// - node is not a source
498 // async fn stop_record(&self, instance_id: Uuid, source_id: NodeId) -> DaemonResult<String>;
499
500 // /// Starts the replay for the given source.
501 // /// The replay creates a new node that has the same port and links as the
502 // /// source is replaying.
503 // ///
504 // /// # Errors
505 // /// An error variant is returned in case of:
506 // /// - error on zenoh-rpc
507 // /// - record not found
508 // /// - replay already started
509 // /// - source not found
510 // /// - node is not a source
511 // async fn start_replay(
512 // &self,
513 // instance_id: Uuid,
514 // source_id: NodeId,
515 // key_expr: String,
516 // ) -> DaemonResult<NodeId>;
517
518 // /// Stops the replay for the given source.
519 // /// This stops and removes the replay node from the graph.
520 // ///
521 // /// # Errors
522 // /// An error variant is returned in case of:
523 // /// - error on zenoh-rpc
524 // /// - record not found
525 // /// - replay already stopped
526 // /// - source not found
527 // /// - node is not a source
528 // async fn stop_replay(
529 // &self,
530 // instance_id: Uuid,
531 // source_id: NodeId,
532 // replay_id: NodeId,
533 // ) -> DaemonResult<NodeId>;
534
535 // /// Gets the state of the given graph node for the given instance.
536 // /// A graph node can be a source, a sink, a connector, or an operator.
537 // /// The node state represents the current state of the node:
538 // /// `enum NodeState { Running, Stopped, Error(err) }`
539 // async fn get_node_state(&self, instance_id: Uuid, node: String) -> DaemonResult<NodeState>;
540}
541
542/// The interface the Daemon expose to other daemons.
543/// The service is exposed using zenoh-rpc, the server and client
544/// are generated automatically.
545///
546#[zservice(
547 timeout_s = 600,
548 prefix = "zf/daemon",
549 service_uuid = "22222222222222222222222222222222"
550)]
551pub trait DaemonInterfaceInternal {
552 /// Prepares the runtime host the instance identified by the [`Uuid`].
553 ///
554 /// Preparing a runtime means, fetch the operators/source/sinks libraries,
555 /// create the needed structures in memory, the links.
556 /// Once everything is prepared the runtime should return the [`DataFlowRecord`]
557 ///
558 /// # Errors
559 /// An error variant is returned in case of:
560 /// - error on zenoh-rpc
561 /// - unable to prepare
562 async fn prepare(&self, instance_id: Uuid) -> DaemonResult<DataFlowRecord>;
563
564 /// Cleans the "remains" of the given instance: unload the libraries, drop data structures and
565 /// destroy links.
566 ///
567 /// # Errors
568 /// An error variant is returned in case of:
569 /// - error on zenoh-rpc
570 /// - unable to clean
571 async fn clean(&self, instance_id: Uuid) -> DaemonResult<DataFlowRecord>;
572
573 /// Starts the sinks, connectors, and operators for the given instance.
574 ///
575 /// # Errors
576 /// An error variant is returned in case of:
577 /// - error on zenoh-rpc
578 /// - instance not found
579 /// - instance already started
580 async fn start(&self, instance_id: Uuid) -> DaemonResult<()>;
581
582 /// Starts the sources for the given instance.
583 /// Note that this should be called only after the `start(instance)` has returned
584 /// successfully otherwise data may be lost.
585 ///
586 /// # Errors
587 /// An error variant is returned in case of:
588 /// - error on zenoh-rpc
589 /// - instance not found
590 /// - sources already started
591 async fn start_sources(&self, instance_id: Uuid) -> DaemonResult<()>;
592
593 /// Stops the sinks, connectors, and operators for the given instance.
594 ///
595 /// Note that this should be called after the `stop_sources(instance)` has returned
596 /// successfully otherwise data may be lost.
597 ///
598 /// # Errors
599 /// An error variant is returned in case of:
600 /// - error on zenoh-rpc
601 /// - instance not found
602 /// - instance already stopped
603 async fn stop(&self, instance_id: Uuid) -> DaemonResult<()>;
604
605 /// Stops the sources for the given instance.
606 ///
607 /// # Errors
608 /// An error variant is returned in case of:
609 /// - error on zenoh-rpc
610 /// - instance not found
611 /// - sources already stopped
612 async fn stop_sources(&self, instance_id: Uuid) -> DaemonResult<()>;
613
614 /// Sends the `message` to `node` for the given instance.
615 ///
616 /// This is useful for sending out-of-band notification to a node (eg. in the case of deadline
617 /// miss notification).
618 ///
619 /// # Errors
620 /// An error variant is returned in case of:
621 /// - error on zenoh-rpc
622 /// - instance not found
623 async fn notify_runtime(
624 &self,
625 instance_id: Uuid,
626 runtime: String,
627 message: ControlMessage,
628 ) -> DaemonResult<()>;
629
630 /// Checks the compatibility for the given `operator`.
631 ///
632 /// Compatibility is based on tags and some machine characteristics (eg. CPU architecture, OS).
633 ///
634 /// # Errors
635 /// An error variant is returned in case of:
636 /// - error on zenoh-rpc
637 async fn check_operator_compatibility(
638 &self,
639 operator: OperatorDescriptor,
640 ) -> DaemonResult<bool>;
641
642 /// Checks the compatibility for the given `source`.
643 ///
644 /// Compatibility is based on tags and some machine characteristics (eg. CPU architecture, OS)
645 ///
646 /// # Errors
647 /// An error variant is returned in case of:
648 /// - error on zenoh-rpc
649 async fn check_source_compatibility(&self, source: SourceDescriptor) -> DaemonResult<bool>;
650
651 /// Checks the compatibility for the given `sink`.
652 ///
653 /// Compatibility is based on tags and some machine characteristics (eg. CPU architecture, OS)
654 ///
655 /// # Errors
656 /// An error variant is returned in case of:
657 /// - error on zenoh-rpc
658 async fn check_sink_compatibility(&self, sink: SinkDescriptor) -> DaemonResult<bool>;
659}