Skip to main content

oct_orchestrator/
lib.rs

1use petgraph::Graph;
2
3use oct_cloud::aws::types::InstanceType;
4use oct_cloud::infra;
5
6pub mod backend;
7pub mod user_state;
8
9pub struct OrchestratorWithGraph;
10
11impl OrchestratorWithGraph {
12    /// Initial step of the `oct`-managed system deployment
13    pub async fn genesis(&self) -> Result<(), Box<dyn std::error::Error>> {
14        let config = oct_config::Config::new(None)?;
15
16        let infra_state_backend =
17            backend::get_state_backend::<infra::state::State>(&config.project.state_backend);
18
19        // In the current version there is only one Leader node which serves
20        // all user services, so it's okay to get instance type from the user services graph
21        let user_services_graph = config.to_graph()?;
22        let instance_type = get_instance_type(&user_services_graph)?;
23
24        let genesis_spec_graph = infra::graph::GraphManager::get_genesis_graph(instance_type);
25
26        let infra_graph_manager = infra::graph::GraphManager::new().await;
27        let (resource_graph, _vm) = infra_graph_manager
28            .deploy_genesis_graph(&genesis_spec_graph)
29            .await?;
30
31        let state = infra::state::State::from_graph(&resource_graph);
32        let () = infra_state_backend.save(&state).await?;
33
34        Ok(())
35    }
36
37    pub async fn apply(&self) -> Result<(), Box<dyn std::error::Error>> {
38        let config = oct_config::Config::new(None)?;
39
40        let infra_state_backend =
41            backend::get_state_backend::<infra::state::State>(&config.project.state_backend);
42        let (infra_state, _loaded) = infra_state_backend.load().await?;
43
44        let vms = infra_state.get_vms();
45        let leader_vm = vms.first().ok_or("No VMs available")?;
46
47        let oct_ctl_client = oct_ctl_sdk::Client::new(leader_vm.public_ip.clone());
48        let () = oct_ctl_client.apply(config).await?;
49
50        Ok(())
51    }
52
53    pub async fn destroy(&self) -> Result<(), Box<dyn std::error::Error>> {
54        let config = oct_config::Config::new(None)?;
55
56        let infra_state_backend =
57            backend::get_state_backend::<infra::state::State>(&config.project.state_backend);
58        let (infra_state, _loaded) = infra_state_backend.load().await?;
59
60        let vms = infra_state.get_vms();
61        let leader_vm = vms.first().ok_or("No VMs available")?;
62
63        let oct_ctl_client = oct_ctl_sdk::Client::new(leader_vm.public_ip.clone());
64        let () = oct_ctl_client.destroy().await?;
65
66        let mut resource_graph = infra_state.to_graph();
67
68        let graph_manager = infra::graph::GraphManager::new().await;
69        let destroy_result = graph_manager.destroy(&mut resource_graph).await;
70
71        match destroy_result {
72            Ok(()) => {
73                infra_state_backend.remove().await?;
74
75                Ok(())
76            }
77            Err(e) => {
78                log::error!("Failed to destroy: {e}");
79
80                let current_infra_state = infra::state::State::from_graph(&resource_graph);
81
82                if let Err(save_err) = infra_state_backend.save(&current_infra_state).await {
83                    return Err(format!(
84                        "Destruction failed: {e}. Additionally, failed to save state: {save_err}"
85                    )
86                    .into());
87                }
88
89                Err(format!("Partial destruction: {e}. Remaining resources saved to state.").into())
90            }
91        }
92    }
93}
94
95/// Tries to find an instance type which can fit all user-requested services
96fn get_instance_type(
97    services_graph: &Graph<oct_config::Node, String>,
98) -> Result<InstanceType, Box<dyn std::error::Error>> {
99    let sorted_graph = infra::graph::kahn_traverse(services_graph)?;
100
101    let (total_services_cpus, total_services_memory) = sorted_graph
102        .iter()
103        .filter_map(|node_index| {
104            if let oct_config::Node::Resource(service) = &services_graph[*node_index] {
105                return Some(service);
106            }
107
108            None
109        })
110        .fold((0u32, 0u64), |(cpus, mem), service| {
111            (cpus + service.cpus, mem + service.memory)
112        });
113
114    let instance_type = InstanceType::from_resources(total_services_cpus, total_services_memory);
115
116    match instance_type {
117        Some(instance_type) => Ok(instance_type),
118        None => Err("Failed to get instance type to fit all services".into()),
119    }
120}
121
122#[cfg(test)]
123mod tests {}