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}