exocore_core/cell/
config.rs

1use std::io::prelude::*;
2
3use exocore_protos::{
4    core::{cell_application_config, cell_node_config, CellApplicationConfig, NodeConfig},
5    generated::{
6        exocore_apps::Manifest,
7        exocore_core::{
8            node_cell_config, CellConfig, CellNodeConfig, LocalNodeConfig, NodeCellConfig,
9        },
10    },
11};
12
13use super::Error;
14
15/// Extension for `LocalNodeConfig` proto.
16pub trait LocalNodeConfigExt {
17    fn config(&self) -> &LocalNodeConfig;
18
19    fn read_yaml<R: Read>(reader: R) -> Result<LocalNodeConfig, Error>;
20
21    fn to_yaml_string(&self) -> Result<String, Error>;
22
23    fn write_yaml<W: Write>(&self, write: W) -> Result<(), Error>;
24
25    fn create_cell_node_config(&self, roles: Vec<cell_node_config::Role>) -> CellNodeConfig;
26
27    fn add_cell(&mut self, cell: NodeCellConfig);
28}
29
30impl LocalNodeConfigExt for LocalNodeConfig {
31    fn config(&self) -> &LocalNodeConfig {
32        self
33    }
34
35    fn read_yaml<R: Read>(reader: R) -> Result<LocalNodeConfig, Error> {
36        let config = serde_yaml::from_reader(reader)
37            .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML node config: {}", err)))?;
38
39        Ok(config)
40    }
41
42    fn to_yaml_string(&self) -> Result<String, Error> {
43        serde_yaml::to_string(self.config())
44            .map_err(|err| Error::Config(anyhow!("Couldn't encode node config to YAML: {}", err)))
45    }
46
47    fn write_yaml<W: Write>(&self, write: W) -> Result<(), Error> {
48        serde_yaml::to_writer(write, self.config())
49            .map_err(|err| Error::Config(anyhow!("Couldn't encode node config to YAML: {}", err)))
50    }
51
52    fn create_cell_node_config(&self, roles: Vec<cell_node_config::Role>) -> CellNodeConfig {
53        let node_config = self.config();
54        CellNodeConfig {
55            node: Some(NodeConfig {
56                public_key: node_config.public_key.clone(),
57                id: node_config.id.clone(),
58                name: node_config.name.clone(),
59                addresses: node_config.addresses.clone(),
60            }),
61            roles: roles.into_iter().map(|r| r.into()).collect(),
62        }
63    }
64
65    fn add_cell(&mut self, cell: NodeCellConfig) {
66        self.cells.retain(|other| other.id != cell.id);
67        self.cells.push(cell);
68    }
69}
70
71/// Extension for `CellNodeConfig` proto.
72pub trait CellNodeConfigExt {
73    fn config(&self) -> &CellNodeConfig;
74
75    fn to_yaml_string(&self) -> Result<String, Error>;
76
77    fn read_yaml<R: Read>(reader: R) -> Result<CellNodeConfig, Error>;
78}
79
80impl CellNodeConfigExt for CellNodeConfig {
81    fn config(&self) -> &CellNodeConfig {
82        self
83    }
84
85    fn to_yaml_string(&self) -> Result<String, Error> {
86        serde_yaml::to_string(self.config()).map_err(|err| {
87            Error::Config(anyhow!("Couldn't encode cell node config to YAML: {}", err))
88        })
89    }
90
91    fn read_yaml<R: Read>(reader: R) -> Result<CellNodeConfig, Error> {
92        let config = serde_yaml::from_reader(reader).map_err(|err| {
93            Error::Config(anyhow!("Couldn't decode YAML cell node config: {}", err))
94        })?;
95
96        Ok(config)
97    }
98}
99
100pub trait CellConfigExt {
101    fn config(&self) -> &CellConfig;
102
103    fn read_yaml<R: Read>(reader: R) -> Result<CellConfig, Error>;
104
105    fn to_yaml_string(&self) -> Result<String, Error>;
106
107    fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error>;
108
109    fn from_node_cell(config: &NodeCellConfig) -> Result<CellConfig, Error>;
110
111    fn find_node(&mut self, node_pk: &str) -> Option<&mut CellNodeConfig>;
112
113    fn add_node(&mut self, node: CellNodeConfig);
114
115    fn add_application(&mut self, cell_app: CellApplicationConfig);
116}
117
118impl CellConfigExt for CellConfig {
119    fn config(&self) -> &CellConfig {
120        self
121    }
122
123    fn read_yaml<R: Read>(reader: R) -> Result<CellConfig, Error> {
124        let config: CellConfig = serde_yaml::from_reader(reader)
125            .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML cell config: {}", err)))?;
126
127        Ok(config)
128    }
129
130    fn to_yaml_string(&self) -> Result<String, Error> {
131        serde_yaml::to_string(self.config())
132            .map_err(|err| Error::Config(anyhow!("Couldn't encode cell config to YAML: {}", err)))
133    }
134
135    fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error> {
136        serde_yaml::to_writer(writer, self.config())
137            .map_err(|err| Error::Config(anyhow!("Couldn't encode cell config to YAML: {}", err)))
138    }
139
140    fn from_node_cell(config: &NodeCellConfig) -> Result<CellConfig, Error> {
141        match &config.location {
142            Some(node_cell_config::Location::Inline(cell_config)) => Ok(cell_config.clone()),
143            other => Err(Error::Config(anyhow!(
144                "Invalid cell instance config: {:?}",
145                other
146            ))),
147        }
148    }
149
150    fn find_node(&mut self, node_pk: &str) -> Option<&mut CellNodeConfig> {
151        self.nodes.iter_mut().find(|cell_node| {
152            cell_node
153                .node
154                .as_ref()
155                .map_or(false, |n| n.public_key == node_pk)
156        })
157    }
158
159    fn add_node(&mut self, node: CellNodeConfig) {
160        let Some(node_pk) = node.node.as_ref().map(|n| n.public_key.as_str()) else {
161            return;
162        };
163
164        // check if node exists first
165        if let Some(cell_node) = self.find_node(node_pk) {
166            *cell_node = node;
167            return;
168        }
169
170        // otherwise it doesn't exist, we just add it
171        self.nodes.push(node);
172    }
173
174    /// Adds application to the cell. Only support deduping on inline apps.
175    fn add_application(&mut self, cell_app: CellApplicationConfig) {
176        // check if app exists, and replace with newer is so
177        for existing_cell_app in &mut self.apps {
178            if cell_app.public_key == existing_cell_app.public_key {
179                *existing_cell_app = cell_app;
180                return;
181            }
182        }
183
184        self.apps.push(cell_app);
185    }
186}
187
188/// Extension for `NodeConfig` proto.
189pub trait NodeConfigExt {
190    fn read_yaml<R: Read>(reader: R) -> Result<NodeConfig, Error>;
191
192    fn to_yaml_string(&self) -> Result<String, Error>;
193}
194
195impl NodeConfigExt for NodeConfig {
196    fn read_yaml<R: Read>(reader: R) -> Result<NodeConfig, Error> {
197        let config: NodeConfig = serde_yaml::from_reader(reader)
198            .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML node config: {}", err)))?;
199
200        Ok(config)
201    }
202
203    fn to_yaml_string(&self) -> Result<String, Error> {
204        serde_yaml::to_string(self).map_err(|err| {
205            Error::Config(anyhow!("Couldn't encode cell node config to YAML: {}", err))
206        })
207    }
208}
209
210/// Extension for `CellApplicationConfig` proto.
211pub trait CellApplicationConfigExt {
212    fn from_manifest(manifest: Manifest) -> CellApplicationConfig;
213}
214
215impl CellApplicationConfigExt for CellApplicationConfig {
216    fn from_manifest(manifest: Manifest) -> CellApplicationConfig {
217        CellApplicationConfig {
218            name: manifest.name.clone(),
219            version: manifest.version.clone(),
220            public_key: manifest.public_key.clone(),
221            package_url: String::new(),
222            location: Some(cell_application_config::Location::Inline(manifest)),
223        }
224    }
225}
226
227/// Extension for `Manifest` proto.
228pub trait ManifestExt {
229    fn manifest(&self) -> &Manifest;
230
231    fn read_yaml<R: Read>(reader: R) -> Result<Manifest, Error>;
232
233    fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error>;
234}
235
236impl ManifestExt for Manifest {
237    fn manifest(&self) -> &Manifest {
238        self
239    }
240
241    fn read_yaml<R: Read>(reader: R) -> Result<Manifest, Error> {
242        let config: Manifest = serde_yaml::from_reader(reader)
243            .map_err(|err| Error::Config(anyhow!("Couldn't decode YAML manifest: {}", err)))?;
244
245        Ok(config)
246    }
247
248    fn write_yaml<W: Write>(&self, writer: W) -> Result<(), Error> {
249        serde_yaml::to_writer(writer, self.manifest()).map_err(|err| {
250            Error::Config(anyhow!(
251                "Couldn't encode application manifest to YAML: {}",
252                err
253            ))
254        })
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use exocore_protos::{
261        core::{
262            cell_application_config, CellApplicationConfig, ChainConfig, EntityIndexConfig,
263            MutationIndexConfig, NodeAddresses,
264        },
265        generated::exocore_core::{
266            cell_node_config, node_cell_config, CellConfig, CellNodeConfig, LocalNodeConfig,
267            NodeCellConfig, NodeConfig,
268        },
269    };
270
271    use super::{
272        super::{Cell, CellNodes},
273        *,
274    };
275    use crate::{dir::os::OsDirectory, tests_utils::find_test_fixture};
276
277    #[test]
278    fn parse_node_config_yaml_ser_deser() -> anyhow::Result<()> {
279        use exocore_protos::generated::exocore_core::NodeStoreConfig;
280
281        let conf_ser = LocalNodeConfig {
282            keypair: "keypair".to_string(),
283            public_key: "pk".to_string(),
284            name: "node_name".to_string(),
285            id: String::new(),
286            cells: vec![
287                NodeCellConfig {
288                    id: "cell1".into(),
289                    location: Some(node_cell_config::Location::Inline(CellConfig {
290                        public_key: "pk".to_string(),
291                        keypair: "kp".to_string(),
292                        name: "cell_name".to_string(),
293                        id: String::new(),
294                        nodes: vec![CellNodeConfig {
295                            node: Some(NodeConfig {
296                                public_key: "pk".to_string(),
297                                name: "node_name".to_string(),
298                                id: String::new(),
299                                addresses: Some(NodeAddresses {
300                                    p2p: vec!["maddr".to_string()],
301                                    http: vec!["http_addr".to_string()],
302                                }),
303                            }),
304                            roles: vec![cell_node_config::Role::ChainRole.into()],
305                        }],
306                        apps: vec![
307                            CellApplicationConfig {
308                                name: "app1".to_string(),
309                                version: "0.0.1".to_string(),
310                                public_key: "pk1".to_string(),
311                                package_url: "https://somewhere/package.zip".to_string(),
312                                location: Some(cell_application_config::Location::Inline(
313                                    Manifest {
314                                        name: "app1".to_string(),
315                                        ..Default::default()
316                                    },
317                                )),
318                            },
319                            CellApplicationConfig {
320                                name: "app2".to_string(),
321                                version: "0.0.1".to_string(),
322                                public_key: "pk2".to_string(),
323                                package_url: "https://somewhere/package.zip".to_string(),
324                                location: None,
325                            },
326                        ],
327                    })),
328                },
329                NodeCellConfig {
330                    id: "cell2".into(),
331                    location: None,
332                },
333            ],
334            addresses: Some(NodeAddresses {
335                p2p: vec!["maddr".to_string()],
336                http: vec!["http_addr".to_string()],
337            }),
338            listen_addresses: Some(NodeAddresses {
339                p2p: vec!["listen_maddr".to_string()],
340                http: vec!["listen_http_addr".to_string()],
341            }),
342            store: Some(NodeStoreConfig {
343                index: Some(EntityIndexConfig {
344                    chain_index_min_depth: Some(3),
345                    chain_index_depth_leeway: Some(10),
346                    pending_index: Some(MutationIndexConfig {
347                        indexer_num_threads: Some(2),
348                        indexer_heap_size_bytes: Some(30_000_000),
349                        entity_mutations_cache_size: Some(2000),
350                    }),
351                    chain_index: Some(MutationIndexConfig {
352                        indexer_num_threads: Some(2),
353                        indexer_heap_size_bytes: Some(30_000_000),
354                        entity_mutations_cache_size: Some(2000),
355                    }),
356                    ..Default::default()
357                }),
358                query_parallelism: Some(5),
359            }),
360            chain: Some(ChainConfig {
361                segment_max_size: Some(1_000),
362                segment_max_open_mmap: Some(2),
363            }),
364        };
365
366        let conf_yaml = conf_ser.to_yaml_string()?;
367        let conf_deser = LocalNodeConfig::read_yaml(conf_yaml.as_bytes())?;
368        assert_eq!(conf_ser, conf_deser);
369
370        Ok(())
371    }
372
373    #[test]
374    fn parse_node_config_example_yaml_file() -> anyhow::Result<()> {
375        let node_path = find_test_fixture("examples/node");
376        // let config = LocalNodeConfig::from_yaml_file(config_path)?;
377
378        let dir = OsDirectory::new(node_path);
379        let (cells, node) = Cell::from_local_node_directory(dir)?;
380        assert_eq!(1, cells.len());
381        assert_eq!(2, node.p2p_addresses().len());
382
383        {
384            // cell from directory
385            let cell = cells[0].clone().unwrap_full();
386
387            {
388                let nodes = cell.cell().nodes();
389                assert_eq!(2, nodes.count());
390            }
391
392            {
393                let schemas = cell
394                    .cell()
395                    .schemas()
396                    .get_message_descriptor("exocore.example_app.Task");
397                assert!(schemas.is_ok());
398            }
399        }
400
401        Ok(())
402    }
403
404    #[test]
405    fn write_node_config_to_yaml_read_write() -> anyhow::Result<()> {
406        let config_init = LocalNodeConfig {
407            name: "node_name".to_string(),
408            ..Default::default()
409        };
410
411        let mut bytes = Vec::new();
412
413        config_init.write_yaml(&mut bytes)?;
414
415        let config_read = LocalNodeConfig::read_yaml(bytes.as_slice())?;
416
417        assert_eq!(config_init, config_read);
418
419        Ok(())
420    }
421
422    #[test]
423    fn node_config_add_cell() {
424        let mut config = LocalNodeConfig::default();
425
426        config.add_cell(NodeCellConfig {
427            id: "id1".into(),
428            ..Default::default()
429        });
430        assert_eq!(1, config.cells.len());
431
432        config.add_cell(NodeCellConfig {
433            id: "id1".into(),
434            ..Default::default()
435        });
436        assert_eq!(1, config.cells.len());
437
438        config.add_cell(NodeCellConfig {
439            id: "id2".into(),
440            ..Default::default()
441        });
442        assert_eq!(2, config.cells.len());
443    }
444
445    #[test]
446    fn cell_config_yaml_read_write() -> anyhow::Result<()> {
447        let config_init = CellConfig {
448            ..Default::default()
449        };
450
451        let mut bytes = Vec::new();
452        config_init.write_yaml(&mut bytes)?;
453
454        let config_read = CellConfig::read_yaml(bytes.as_slice())?;
455
456        assert_eq!(config_init, config_read);
457
458        Ok(())
459    }
460
461    #[test]
462    fn cell_config_add_node() {
463        let mut config = CellConfig {
464            ..Default::default()
465        };
466
467        let node1 = CellNodeConfig {
468            node: Some(NodeConfig {
469                public_key: "pk1".to_string(),
470                ..Default::default()
471            }),
472            ..Default::default()
473        };
474
475        config.add_node(node1);
476        assert_eq!(config.nodes.len(), 1);
477
478        let node1_changed = CellNodeConfig {
479            node: Some(NodeConfig {
480                public_key: "pk1".to_string(),
481                name: "new name".to_string(),
482                ..Default::default()
483            }),
484            ..Default::default()
485        };
486        config.add_node(node1_changed);
487        assert_eq!(config.nodes.len(), 1);
488        assert_eq!("new name", config.nodes[0].node.as_ref().unwrap().name);
489
490        let node2 = CellNodeConfig {
491            node: Some(NodeConfig {
492                public_key: "pk2".to_string(),
493                ..Default::default()
494            }),
495            ..Default::default()
496        };
497
498        config.add_node(node2);
499        assert_eq!(config.nodes.len(), 2);
500    }
501
502    #[test]
503    fn cell_config_add_app() {
504        let mut config = CellConfig {
505            ..Default::default()
506        };
507
508        config.add_application(CellApplicationConfig {
509            name: "name".to_string(),
510            version: "0.0.1".to_string(),
511            public_key: "pk1".to_string(),
512            package_url: "https://some/location/package.zip".to_string(),
513            location: Some(cell_application_config::Location::Inline(Manifest {
514                ..Default::default()
515            })),
516        });
517        assert_eq!(config.apps.len(), 1);
518
519        config.add_application(CellApplicationConfig {
520            name: "name".to_string(),
521            version: "0.0.1".to_string(),
522            public_key: "pk1".to_string(),
523            package_url: "https://some/location/package.zip".to_string(),
524            location: Some(cell_application_config::Location::Inline(Manifest {
525                ..Default::default()
526            })),
527        });
528        assert_eq!(config.apps.len(), 1);
529
530        config.add_application(CellApplicationConfig {
531            name: "name".to_string(),
532            version: "0.0.1".to_string(),
533            public_key: "pk2".to_string(),
534            package_url: "https://some/location/package.zip".to_string(),
535            location: Some(cell_application_config::Location::Inline(Manifest {
536                ..Default::default()
537            })),
538        });
539        assert_eq!(config.apps.len(), 2);
540    }
541}