Skip to content

Commit

Permalink
Add node shutdown
Browse files Browse the repository at this point in the history
  • Loading branch information
dayeon5470 committed Feb 23, 2024
1 parent 57b0e98 commit 9e83c8a
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 5 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- Add `apply_target_id` field to `Node` struct for reverting node status.
- Add `apply_in_progress` field to `Node` struct for reverting node status.
- Add `node_shutdown` Graphql API.

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ipnet = { version = "2", features = ["serde"] }
jsonwebtoken = "9"
lazy_static = "1"
num-traits = "0.2"
oinq = { git = "https://github.com/petabi/oinq.git", tag = "0.9.1" }
oinq = { git = "https://github.com/petabi/oinq.git", tag = "0.9.3" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls-native-roots",
] }
Expand Down
6 changes: 5 additions & 1 deletion src/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,14 +575,18 @@ struct TestSchema {
#[cfg(test)]
impl TestSchema {
async fn new() -> Self {
let agent_manager: BoxedAgentManager = Box::new(MockAgentManager {});
Self::new_with(agent_manager).await
}

async fn new_with(agent_manager: BoxedAgentManager) -> Self {
use self::account::set_initial_admin_password;

let db_dir = tempfile::tempdir().unwrap();
let backup_dir = tempfile::tempdir().unwrap();
let store = Store::new(db_dir.path(), backup_dir.path()).unwrap();
let _ = set_initial_admin_password(&store);
let store = Arc::new(RwLock::new(store));
let agent_manager: BoxedAgentManager = Box::new(MockAgentManager {});
let schema = Schema::build(
Query::default(),
Mutation::default(),
Expand Down
139 changes: 138 additions & 1 deletion src/graphql/node/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl NodeControlMutation {
let agents = ctx.data::<BoxedAgentManager>()?;
let review_hostname = roxy::hostname();
if !review_hostname.is_empty() && review_hostname == hostname {
roxy::reboot().map_or_else(|e| Err(e.to_string().into()), |_| Ok(hostname))
Err("reboot the system".into())
} else {
let apps = agents.online_apps_by_host_id().await?;
let Some(apps) = apps.get(&hostname) else {
Expand All @@ -40,4 +40,141 @@ impl NodeControlMutation {
)
}
}

#[graphql(guard = "RoleGuard::new(Role::SystemAdministrator)
.or(RoleGuard::new(Role::SecurityAdministrator))")]
async fn node_shutdown(&self, ctx: &Context<'_>, hostname: String) -> Result<String> {
let agents = ctx.data::<BoxedAgentManager>()?;
let review_hostname = roxy::hostname();

if !review_hostname.is_empty() && review_hostname == hostname {
Err("shutdown the system".into())
} else {
let apps = agents.online_apps_by_host_id().await?;
let Some(apps) = apps.get(&hostname) else {
return Err("unable to gather info of online agents".into());
};
let Some((key, _)) = apps.first() else {
return Err("unable to access first of online agents".into());
};

let code: u32 = RequestCode::Shutdown.into();
let msg = bincode::serialize(&code)?;
let response = agents.send_and_recv(key, &msg).await?;
let Ok(response) =
bincode::DefaultOptions::new().deserialize::<Result<(), &str>>(&response)
else {
return Ok(hostname);
};
response.map_or_else(
|e| Err(format!("unable to shutdown the system: {e}").into()),
|()| Ok(hostname),
)
}
}
}

#[cfg(test)]
mod tests {
use crate::graphql::{AgentManager, BoxedAgentManager, TestSchema};
use ipnet::IpNet;
use std::collections::HashMap;
use tokio::sync::mpsc::{self, Sender};

struct MockAgentManager {
pub online_apps_by_host_id: HashMap<String, Vec<(String, String)>>,
pub send_result_checker: Sender<String>,
}

impl MockAgentManager {
pub async fn insert_result(&self, result_key: &str) {
self.send_result_checker
.send(result_key.to_string())
.await
.expect("send result failed");
}
}

#[async_trait::async_trait]
impl AgentManager for MockAgentManager {
async fn broadcast_to_crusher(&self, _msg: &[u8]) -> Result<(), anyhow::Error> {
unimplemented!()
}
async fn broadcast_trusted_domains(&self) -> Result<(), anyhow::Error> {
unimplemented!()
}
async fn broadcast_trusted_user_agent_list(
&self,
_list: &[u8],
) -> Result<(), anyhow::Error> {
unimplemented!()
}
async fn broadcast_internal_networks(
&self,
_networks: &[u8],
) -> Result<Vec<String>, anyhow::Error> {
unimplemented!()
}
async fn broadcast_allow_networks(
&self,
_networks: &[u8],
) -> Result<Vec<String>, anyhow::Error> {
unimplemented!()
}
async fn broadcast_block_networks(
&self,
_networks: &[u8],
) -> Result<Vec<String>, anyhow::Error> {
unimplemented!()
}
async fn online_apps_by_host_id(
&self,
) -> Result<HashMap<String, Vec<(String, String)>>, anyhow::Error> {
Ok(self.online_apps_by_host_id.clone())
}
async fn send_and_recv(&self, key: &str, _msg: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
self.insert_result(key).await;
Ok(vec![])
}
async fn update_traffic_filter_rules(
&self,
_key: &str,
_rules: &[(IpNet, Option<Vec<u16>>, Option<Vec<u16>>)],
) -> Result<(), anyhow::Error> {
unimplemented!()
}
}

fn insert_apps(host: &str, apps: &[&str], map: &mut HashMap<String, Vec<(String, String)>>) {
let entries = apps
.iter()
.map(|&app| (format!("{}@{}", app, host), app.to_string()))
.collect();
map.insert(host.to_string(), entries);
}

#[tokio::test]
async fn test_node_shutdown() {
let mut online_apps_by_host_id = HashMap::new();
insert_apps("localhost", &["hog"], &mut online_apps_by_host_id);

let (send_result_checker, _) = mpsc::channel(10);

let agent_manager: BoxedAgentManager = Box::new(MockAgentManager {
online_apps_by_host_id,
send_result_checker,
});

let schema = TestSchema::new_with(agent_manager).await;

// node_shutdown
let res = schema
.execute(
r#"mutation {
nodeShutdown(hostname:"localhost")
}"#,
)
.await;
assert_eq!(res.data.to_string(), r#"{nodeShutdown: "localhost"}"#);
}
}

0 comments on commit 9e83c8a

Please sign in to comment.