Skip to content

Commit

Permalink
fix(rust): node control api fixes and minor refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
davide-baldo committed Feb 20, 2025
1 parent 02a7b15 commit 9b91269
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::authenticator::direct::Members;
use crate::control_api::backend::common;
use crate::control_api::backend::common::create_authority_client;
use crate::control_api::backend::common::{create_authority_client, ResourceKind};
use crate::control_api::backend::entrypoint::HttpControlNodeApiBackend;
use crate::control_api::http::ControlApiHttpResponse;
use crate::control_api::protocol::authority_member::{
Expand All @@ -10,49 +10,44 @@ use crate::control_api::protocol::authority_member::{
use crate::control_api::protocol::common::{Attributes, ErrorResponse, NodeName};
use crate::control_api::ControlApiError;
use crate::nodes::NodeManager;
use http::StatusCode;
use http::{Method, StatusCode};
use ockam_node::Context;
use std::sync::Arc;

impl HttpControlNodeApiBackend {
pub(super) async fn handle_authority_member(
&self,
context: &Context,
method: &str,
method: Method,
resource_id: Option<&str>,
body: Option<Vec<u8>>,
) -> Result<ControlApiHttpResponse, ControlApiError> {
let resource_name = "authority-members";
let resource_name_identifier = "authority_member_identity";
match method {
"PUT" => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(
resource_name,
resource_name_identifier,
),
Method::PUT => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(ResourceKind::AuthorityMembers),
Some(id) => {
handle_authority_member_add_or_update(context, &self.node_manager, body, id)
.await
}
},
"GET" => match resource_id {
Method::GET => match resource_id {
None => handle_authority_member_list(context, &self.node_manager, body).await,
Some(id) => {
handle_authority_member_get(context, &self.node_manager, body, id).await
}
},
"DELETE" => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(
resource_name,
resource_name_identifier,
),
Method::DELETE => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(ResourceKind::AuthorityMembers),
Some(id) => {
handle_authority_member_remove(context, &self.node_manager, body, id).await
}
},
_ => {
warn!("Invalid method: {method}");
ControlApiHttpResponse::invalid_method(method, vec!["PUT", "GET", "DELETE"])
ControlApiHttpResponse::invalid_method(
method,
vec![Method::PUT, Method::GET, Method::DELETE],
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,91 @@
use crate::control_api::http::ControlApiHttpResponse;
use crate::control_api::protocol::common::Authority;
use crate::control_api::protocol::common::{Authority, ErrorResponse};
use crate::control_api::ControlApiError;
use crate::nodes::NodeManager;
use crate::orchestrator::project::Project;
use crate::orchestrator::AuthorityNodeClient;
use http::StatusCode;
use ockam::identity::Identifier;
use ockam_core::errcode::{Kind, Origin};
use ockam_multiaddr::MultiAddr;
use serde::de::DeserializeOwned;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::Arc;

pub(super) enum ResourceKind {
TcpInlets,
TcpOutlets,
Relays,
Tickets,
AuthorityMembers,
}

impl ResourceKind {
pub fn enumerate() -> Vec<Self> {
vec![
Self::TcpInlets,
Self::TcpOutlets,
Self::Relays,
Self::Tickets,
Self::AuthorityMembers,
]
}
pub fn from_str(resource: &str) -> Option<Self> {
match resource {
"tcp-inlets" => Some(Self::TcpInlets),
"tcp-outlets" => Some(Self::TcpOutlets),
"relays" => Some(Self::Relays),
"tickets" => Some(Self::Tickets),
"authority-members" => Some(Self::AuthorityMembers),
_ => None,
}
}

pub fn name(&self) -> &'static str {
match self {
Self::TcpInlets => "tcp-inlets",
Self::TcpOutlets => "tcp-outlets",
Self::Relays => "relays",
Self::Tickets => "tickets",
Self::AuthorityMembers => "authority-members",
}
}

pub fn parameter_name(&self) -> &'static str {
match self {
Self::TcpInlets => "inlet_name",
Self::TcpOutlets => "outlet_address",
Self::Relays => "relay_name",
Self::Tickets => "",
Self::AuthorityMembers => "authority_member",
}
}
}

impl Display for ResourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}

impl ControlApiHttpResponse {
pub(super) fn missing_resource_id<T>(
resource_kind: ResourceKind,
) -> Result<T, ControlApiError> {
let resource_name = resource_kind.name();
let resource_name_identifier = resource_kind.parameter_name();

Err(Self::with_body(
StatusCode::BAD_REQUEST,
ErrorResponse {
message: format!("Missing parameter {resource_name}. The HTTP path should be /{{node-name}}/{resource_name}/{{{resource_name_identifier}}}"),
},
)?
.into())
}
}

pub async fn create_authority_client(
node_manager: &Arc<NodeManager>,
authority: &Authority,
Expand Down Expand Up @@ -61,7 +136,7 @@ pub async fn create_authority_client(
}
Err(error) => {
warn!("No default project: {error:?}");
return ControlApiHttpResponse::not_found("Default default project not found");
return ControlApiHttpResponse::not_found("Default project not found");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::control_api::backend::common::ResourceKind;
use crate::control_api::http::{ControlApiHttpRequest, ControlApiHttpResponse};
use crate::control_api::protocol::common::ErrorResponse;
use crate::control_api::ControlApiError;
use crate::nodes::NodeManager;
use crate::DefaultAddress;
use http::{StatusCode, Uri};
use http::{Method, StatusCode, Uri};
use itertools::Itertools;
use ockam_abac::{IncomingAbac, OutgoingAbac, PolicyExpression};
use ockam_core::errcode::Kind;
use ockam_core::{
Expand Down Expand Up @@ -64,48 +66,56 @@ impl Worker for HttpControlNodeApiBackend {
// we can ignore the node identifier since it has been already addressed by
// the reverse proxy
let _node_identifier = path[1];
let raw_resource_kind = path[2].to_lowercase();

let resource_kind = path[2].to_lowercase();
let resource_kind = ResourceKind::from_str(&raw_resource_kind);
let resource_id = path.get(3).copied();
let method = match Method::try_from(request.method.as_str()) {
Ok(method) => method,
Err(_) => {
warn!("Invalid method: {}", request.method);
return context
.send(
message.return_route().clone(),
NeutralMessage::from(minicbor::to_vec(
&ControlApiHttpResponse::with_body(
StatusCode::BAD_REQUEST,
ErrorResponse {
message: "Invalid method".to_string(),
},
)?,
)?),
)
.await;
}
};

let result: Result<ControlApiHttpResponse, ControlApiError> = match resource_kind.as_str() {
"tcp-inlets" => {
self.handle_tcp_inlet(context, request.method.as_str(), resource_id, request.body)
let result: Result<ControlApiHttpResponse, ControlApiError> = match resource_kind {
Some(ResourceKind::TcpInlets) => {
self.handle_tcp_inlet(context, method, resource_id, request.body)
.await
}
"tcp-outlets" => {
self.handle_tcp_outlet(context, request.method.as_str(), resource_id, request.body)
Some(ResourceKind::TcpOutlets) => {
self.handle_tcp_outlet(context, method, resource_id, request.body)
.await
}
"relays" => {
self.handle_relay(context, request.method.as_str(), resource_id, request.body)
Some(ResourceKind::Relays) => {
self.handle_relay(context, method, resource_id, request.body)
.await
}
"tickets" => Ok(self
.handle_ticket(context, request.method.as_str(), resource_id, request.body)
Some(ResourceKind::Tickets) => Ok(self
.handle_ticket(context, method, resource_id, request.body)
.await
.unwrap()),
"authority-members" => {
self.handle_authority_member(
context,
request.method.as_str(),
resource_id,
request.body,
)
.await
Some(ResourceKind::AuthorityMembers) => {
self.handle_authority_member(context, method, resource_id, request.body)
.await
}
_ => {
warn!("Invalid resource kind: {resource_kind}");
let valid_resources = [
"tcp-inlets",
"tcp-outlets",
"relays",
"tickets",
"authority-members",
];
None => {
warn!("Invalid resource kind: {raw_resource_kind}");
let message = format!(
"Invalid resource kind: {resource_kind}. Possible: {}",
valid_resources.join(", ")
"Invalid resource kind: {raw_resource_kind}. Possible: {}",
ResourceKind::enumerate().iter().join(", ")
);
ControlApiHttpResponse::bad_request(&message)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::control_api::backend::common;
use crate::control_api::backend::common::ResourceKind;
use crate::control_api::backend::entrypoint::HttpControlNodeApiBackend;
use crate::control_api::http::ControlApiHttpResponse;
use crate::control_api::protocol::common::{ErrorResponse, NodeName};
use crate::control_api::protocol::inlet::{CreateInletRequest, InletKind, InletTls};
use crate::control_api::protocol::inlet::{InletStatus, UpdateInletRequest};
use crate::control_api::ControlApiError;
use crate::nodes::NodeManager;
use http::StatusCode;
use http::{Method, StatusCode};
use ockam_abac::{Action, Expr, PolicyExpression, ResourceName};
use ockam_core::compat::rand::random_string;
use ockam_core::Route;
Expand All @@ -18,37 +19,29 @@ impl HttpControlNodeApiBackend {
pub(super) async fn handle_tcp_inlet(
&self,
context: &Context,
method: &str,
method: Method,
resource_id: Option<&str>,
body: Option<Vec<u8>>,
) -> Result<ControlApiHttpResponse, ControlApiError> {
let resource_name = "tcp-inlets";
let resource_name_identifier = "tcp_inlet_name";
match method {
"POST" => handle_tcp_inlet_create(context, &self.node_manager, body).await,
"GET" => match resource_id {
Method::POST => handle_tcp_inlet_create(context, &self.node_manager, body).await,
Method::GET => match resource_id {
None => handle_tcp_inlet_list(&self.node_manager).await,
Some(id) => handle_tcp_inlet_get(&self.node_manager, id).await,
},
"PATCH" => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(
resource_name,
resource_name_identifier,
),
Method::PATCH => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(ResourceKind::TcpInlets),
Some(id) => handle_tcp_inlet_update(&self.node_manager, id, body).await,
},
"DELETE" => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(
resource_name,
resource_name_identifier,
),
Method::DELETE => match resource_id {
None => ControlApiHttpResponse::missing_resource_id(ResourceKind::TcpInlets),
Some(id) => handle_tcp_inlet_delete(&self.node_manager, id).await,
},
_ => {
warn!("Invalid method: {method}");
ControlApiHttpResponse::invalid_method(
method,
vec!["POST", "GET", "PATCH", "DELETE"],
vec![Method::POST, Method::GET, Method::PATCH, Method::DELETE],
)
}
}
Expand All @@ -61,8 +54,8 @@ impl HttpControlNodeApiBackend {
summary = "Create a new TCP Inlet",
description =
"Create a TCP Inlet, the main parameters are the destination `to`, and the bind address `from`.
You can also choose to the listen with a valid TLS certificate, restrict access to the Inlet with
`authorized` and `allow`, and select a specialized Portals with `kind`.
You can also choose to listen with a valid TLS certificate, restrict access to the Inlet with
`authorized` and `allow`, and select a specialized Portal with `kind`.
The creation will be asynchronous and the initial status will be `down`.",
path = "/{node}/tcp-inlets",
tags = ["Portals"],
Expand Down Expand Up @@ -202,7 +195,7 @@ async fn handle_tcp_inlet_create(
summary = "Update a TCP Inlet",
description =
"Update the specified TCP Inlet by name.
Currently only `allow` policy expression can be updated, for more advanced updates it's necessary
Currently the only `allow` policy expression can be updated, for more advanced updates it's necessary
to delete the TCP Inlet and create a new one.",
path = "/{node}/tcp-inlets/{tcp_inlet_name}",
tags = ["Portals"],
Expand Down Expand Up @@ -393,7 +386,6 @@ mod test {
assert_eq!(inlet_status.status, ConnectionStatus::Down);
assert_eq!(inlet_status.current_route, None);
assert_eq!(inlet_status.to, "/service/outlet");
assert!(!inlet_status.privileged);
assert_eq!(inlet_status.bind_address.hostname, "127.0.0.1");
assert!(inlet_status.bind_address.port > 0);

Expand All @@ -418,7 +410,6 @@ mod test {
assert_eq!(inlet_status.status, ConnectionStatus::Up);
assert_eq!(inlet_status.current_route, Some("0#outlet".to_string()));
assert_eq!(inlet_status.to, "/service/outlet");
assert!(!inlet_status.privileged);

let request = ControlApiHttpRequest {
method: "GET".to_string(),
Expand All @@ -440,7 +431,6 @@ mod test {
assert_eq!(inlets[0].status, ConnectionStatus::Up);
assert_eq!(inlets[0].current_route, Some("0#outlet".to_string()));
assert_eq!(inlets[0].to, "/service/outlet");
assert!(!inlets[0].privileged);

let request = ControlApiHttpRequest {
method: "DELETE".to_string(),
Expand Down
Loading

0 comments on commit 9b91269

Please sign in to comment.