diff --git a/crates/faas-containerd/src/impls/mod.rs b/crates/faas-containerd/src/impls/mod.rs index 4ca9b31..3c1a6ad 100644 --- a/crates/faas-containerd/src/impls/mod.rs +++ b/crates/faas-containerd/src/impls/mod.rs @@ -2,6 +2,7 @@ pub mod cni; pub mod container; pub mod error; pub mod function; +pub mod namespace; pub mod oci_image; pub mod snapshot; pub mod spec; diff --git a/crates/faas-containerd/src/impls/namespace.rs b/crates/faas-containerd/src/impls/namespace.rs new file mode 100644 index 0000000..4db303e --- /dev/null +++ b/crates/faas-containerd/src/impls/namespace.rs @@ -0,0 +1,144 @@ +use std::collections::HashMap; + +use containerd_client::services::v1::{ + CreateNamespaceRequest, DeleteNamespaceRequest, ListNamespacesRequest, Namespace, + UpdateNamespaceRequest, +}; +use derive_more::Display; + +use super::ContainerdService; + +#[derive(Debug, Display)] +pub enum NamespaceServiceError { + AlreadyExists, + NotFound, + Internal(String), +} + +impl From for NamespaceServiceError { + fn from(status: tonic::Status) -> Self { + use tonic::Code::*; + match status.code() { + NotFound => NamespaceServiceError::NotFound, + AlreadyExists => NamespaceServiceError::AlreadyExists, + _ => NamespaceServiceError::Internal(status.message().to_string()), + } + } +} + +impl ContainerdService { + // 创建命名空间 + pub async fn create_namespace( + &self, + namespace: &str, + labels: HashMap, + ) -> Result<(), NamespaceServiceError> { + let exist = self.namespace_exist(namespace).await?; + if exist.is_some() { + log::info!("Namespace {} already exists", namespace); + return Err(NamespaceServiceError::AlreadyExists); + } + + let mut c = self.client.namespaces(); + let ns_name = namespace; + + let namespace = Namespace { + name: namespace.to_string(), + labels, + }; + let req = CreateNamespaceRequest { + namespace: Some(namespace), + }; + c.create(req).await.map_err(|e| { + log::error!("Failed to create namespace: {}", e); + NamespaceServiceError::Internal(e.to_string()) + })?; + log::info!("Namespace {} created", ns_name); + Ok(()) + } + + // 删除命名空间 + pub async fn delete_namespace(&self, namespace: &str) -> Result<(), NamespaceServiceError> { + let exist = self.namespace_exist(namespace).await?; + if exist.is_none() { + log::info!("Namespace {} not found", namespace); + return Err(NamespaceServiceError::NotFound); + } + let mut c = self.client.namespaces(); + let ns_name = namespace; + let req = DeleteNamespaceRequest { + name: namespace.to_string(), + }; + c.delete(req).await.map_err(|e| { + log::error!("Failed to delete namespace: {}", e); + NamespaceServiceError::Internal(e.to_string()) + })?; + log::info!("Namespace {} deleted", ns_name); + Ok(()) + } + + // 判断命名空间是否存在 + pub async fn namespace_exist( + &self, + namespace: &str, + ) -> Result, NamespaceServiceError> { + let mut c = self.client.namespaces(); + let req = ListNamespacesRequest { + ..Default::default() + }; + let ns_list_resp = c.list(req).await.map_err(|e| { + log::error!("Failed to list namespaces: {}", e); + NamespaceServiceError::Internal(e.to_string()) + })?; + let ns_list = ns_list_resp.into_inner().namespaces; + for ns in ns_list { + if ns.name == namespace { + return Ok(Some(ns)); + } + } + Ok(None) + } + + // 获取命名空间列表 + pub async fn list_namespace(&self) -> Result, NamespaceServiceError> { + let mut c = self.client.namespaces(); + let req = ListNamespacesRequest { + ..Default::default() + }; + let ns_list_resp = c.list(req).await.map_err(|e| { + log::error!("Failed to list namespaces: {}", e); + NamespaceServiceError::Internal(e.to_string()) + })?; + let ns_list = ns_list_resp.into_inner().namespaces; + Ok(ns_list) + } + + // 更新命名空间信息 + pub async fn update_namespace( + &self, + namespace: &str, + labels: HashMap, + ) -> Result<(), NamespaceServiceError> { + let exist = self.namespace_exist(namespace).await?; + if exist.is_none() { + log::info!("Namespace {} not found", namespace); + return Err(NamespaceServiceError::NotFound); + } + let ns_name = namespace; + let namespace = Namespace { + name: namespace.to_string(), + labels, + }; + let mut c = self.client.namespaces(); + let req = UpdateNamespaceRequest { + namespace: Some(namespace), + ..Default::default() + }; + c.update(req).await.map_err(|e| { + log::error!("Failed to update namespace: {}", e); + NamespaceServiceError::Internal(e.to_string()) + })?; + log::info!("Namespace {} updated", ns_name); + Ok(()) + } +} diff --git a/crates/faas-containerd/src/provider/function/mod.rs b/crates/faas-containerd/src/provider/function/mod.rs index 88b78b9..78da40b 100644 --- a/crates/faas-containerd/src/provider/function/mod.rs +++ b/crates/faas-containerd/src/provider/function/mod.rs @@ -1,6 +1,7 @@ pub mod delete; pub mod deploy; pub mod list; +pub mod namespace; pub mod resolve; pub mod status; pub mod update; diff --git a/crates/faas-containerd/src/provider/function/namespace.rs b/crates/faas-containerd/src/provider/function/namespace.rs new file mode 100644 index 0000000..3633392 --- /dev/null +++ b/crates/faas-containerd/src/provider/function/namespace.rs @@ -0,0 +1,91 @@ +use std::collections::HashMap; + +use gateway::{handlers::namespace::NamespaceError, types::namespace::Namespace}; + +use crate::{ + impls::{backend, namespace::NamespaceServiceError}, + provider::ContainerdProvider, +}; + +impl ContainerdProvider { + pub(crate) async fn _create_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> Result<(), NamespaceError> { + backend() + .create_namespace(&namespace, labels) + .await + .map_err(|e| match e { + NamespaceServiceError::AlreadyExists => NamespaceError::AlreadyExists(format!( + "namespace {} has been existed", + namespace + )), + _ => NamespaceError::Internal(e.to_string()), + }) + } + + pub(crate) async fn _get_namespace( + &self, + namespace: String, + ) -> Result { + let exist = backend() + .namespace_exist(&namespace) + .await + .map_err(|e| NamespaceError::Internal(e.to_string()))?; + if exist.is_none() { + return Err(NamespaceError::NotFound(format!( + "namespace {} not found", + namespace + ))); + } + let ns = exist.unwrap(); + Ok(Namespace { + name: Some(ns.name), + labels: ns.labels, + }) + } + + pub(crate) async fn _namespace_list(&self) -> Result, NamespaceError> { + let ns_list = backend() + .list_namespace() + .await + .map_err(|e| NamespaceError::Internal(e.to_string()))?; + let mut ns_list_result = Vec::new(); + for ns in ns_list { + ns_list_result.push(Namespace { + name: Some(ns.name), + labels: ns.labels, + }); + } + Ok(ns_list_result) + } + + pub(crate) async fn _delete_namespace(&self, namespace: String) -> Result<(), NamespaceError> { + backend() + .delete_namespace(&namespace) + .await + .map_err(|e| match e { + NamespaceServiceError::NotFound => { + NamespaceError::NotFound(format!("namespace {} not found", namespace)) + } + _ => NamespaceError::Internal(e.to_string()), + }) + } + + pub(crate) async fn _update_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> Result<(), NamespaceError> { + backend() + .update_namespace(&namespace, labels) + .await + .map_err(|e| match e { + NamespaceServiceError::NotFound => { + NamespaceError::NotFound(format!("namespace {} not found", namespace)) + } + _ => NamespaceError::Internal(e.to_string()), + }) + } +} diff --git a/crates/faas-containerd/src/provider/mod.rs b/crates/faas-containerd/src/provider/mod.rs index b8769fd..5dd0e19 100644 --- a/crates/faas-containerd/src/provider/mod.rs +++ b/crates/faas-containerd/src/provider/mod.rs @@ -1,11 +1,16 @@ pub mod function; - -use std::{path::Path, sync::Arc}; +use std::{collections::HashMap, path::Path, sync::Arc}; use gateway::{ - handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, + handlers::{ + function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, + namespace::NamespaceError, + }, provider::Provider, - types::function::{Deployment, Query, Status}, + types::{ + function::{Deployment, Query, Status}, + namespace::Namespace, + }, }; pub struct ContainerdProvider { @@ -46,4 +51,32 @@ impl Provider for ContainerdProvider { async fn status(&self, function: Query) -> Result { self._status(function).await } + + async fn create_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> Result<(), NamespaceError> { + self._create_namespace(namespace, labels).await + } + + async fn update_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> Result<(), NamespaceError> { + self._update_namespace(namespace, labels).await + } + + async fn delete_namespace(&self, namespace: String) -> Result<(), NamespaceError> { + self._delete_namespace(namespace).await + } + + async fn get_namespace(&self, namespace: String) -> Result { + self._get_namespace(namespace).await + } + + async fn namespace_list(&self) -> Result, NamespaceError> { + self._namespace_list().await + } } diff --git a/crates/gateway/src/bootstrap/mod.rs b/crates/gateway/src/bootstrap/mod.rs index 83a0ea1..2f6e817 100644 --- a/crates/gateway/src/bootstrap/mod.rs +++ b/crates/gateway/src/bootstrap/mod.rs @@ -36,25 +36,34 @@ pub fn config_app(provider: Arc

) -> impl FnOnce(&mut ServiceConf .service( web::resource("/function/{functionName}") .route(web::get().to(handlers::function::status::

)), - ), // .service( - // web::resource("/scale-function/{name}") - // .route(web::post().to(handlers::scale_function)), - // ) - // .service(web::resource("/info").route(web::get().to(handlers::info))) - // .service( - // web::resource("/secrets") - // .route(web::get().to(handlers::secrets)) - // .route(web::post().to(handlers::secrets)) - // .route(web::put().to(handlers::secrets)) - // .route(web::delete().to(handlers::secrets)), - // ) - // .service(web::resource("/logs").route(web::get().to(handlers::logs))) - // .service( - // web::resource("/namespaces") - // .route(web::get().to(handlers::list_namespaces)) - // .route(web::post().to(handlers::mutate_namespace)), - // ), - // ) + ) + .service( + web::resource("/namespace/{namespace}") + .route(web::to(handlers::namespace::mut_namespace::

)), + ) + .service( + web::resource("/namespaces") + .route(web::get().to(handlers::namespace::namespace_list::

)), + ), + // .service( + // web::resource("/scale-function/{name}") + // .route(web::post().to(handlers::scale_function)), + // ) + // .service(web::resource("/info").route(web::get().to(handlers::info))) + // .service( + // web::resource("/secrets") + // .route(web::get().to(handlers::secrets)) + // .route(web::post().to(handlers::secrets)) + // .route(web::put().to(handlers::secrets)) + // .route(web::delete().to(handlers::secrets)), + // ) + // .service(web::resource("/logs").route(web::get().to(handlers::logs))) + // .service( + // web::resource("/namespaces") + // .route(web::get().to(handlers::list_namespaces)) + // .route(web::post().to(handlers::mutate_namespace)), + // ), + // ) ) .service(web::scope("/function").service( web::resource(PROXY_DISPATCH_PATH).route(web::to(handlers::proxy::proxy::

)), diff --git a/crates/gateway/src/handlers/mod.rs b/crates/gateway/src/handlers/mod.rs index e042611..770d0c1 100644 --- a/crates/gateway/src/handlers/mod.rs +++ b/crates/gateway/src/handlers/mod.rs @@ -1,4 +1,5 @@ pub mod function; +pub mod namespace; pub mod proxy; #[derive(Debug, thiserror::Error)] diff --git a/crates/gateway/src/handlers/namespace.rs b/crates/gateway/src/handlers/namespace.rs new file mode 100644 index 0000000..23fc759 --- /dev/null +++ b/crates/gateway/src/handlers/namespace.rs @@ -0,0 +1,106 @@ +use crate::{provider::Provider, types::namespace::Namespace}; +use actix_http::{Method, StatusCode}; +use actix_web::{HttpRequest, HttpResponse, ResponseError, web}; +use derive_more::Display; + +#[derive(Debug, Display)] +pub enum NamespaceError { + #[display("Invalid: {}", _0)] + Invalid(String), + #[display("AlreadyExists: {}", _0)] + AlreadyExists(String), + #[display("NotFound: {}", _0)] + NotFound(String), + #[display("Internal: {}", _0)] + Internal(String), + #[display("MethodNotAllowed: {}", _0)] + MethodNotAllowed(String), +} + +impl ResponseError for NamespaceError { + fn status_code(&self) -> StatusCode { + match self { + NamespaceError::Invalid(_) => StatusCode::BAD_REQUEST, + NamespaceError::AlreadyExists(_) => StatusCode::BAD_REQUEST, + NamespaceError::NotFound(_) => StatusCode::NOT_FOUND, + NamespaceError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + NamespaceError::MethodNotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED, + } + } +} + +pub async fn mut_namespace( + req: HttpRequest, + provider: web::Data

, + info: Option>, +) -> Result { + let namespace = req.match_info().get("namespace"); + if namespace.is_none() { + return Err(NamespaceError::Invalid("namespace is required".to_string())); + } + let namespace = namespace.unwrap(); + let labels; + match *req.method() { + Method::POST => { + match info { + Some(info) => { + labels = info.0.labels; + } + None => { + return Err(NamespaceError::Invalid( + "Request body is required".to_string(), + )); + } + } + (*provider) + .create_namespace(namespace.to_string(), labels) + .await + .map(|_| { + HttpResponse::Created() + .body(format!("namespace {} was created successfully", namespace)) + }) + } + Method::DELETE => (*provider) + .delete_namespace(namespace.to_string()) + .await + .map(|_| { + HttpResponse::Accepted() + .body(format!("namespace {} was deleted successfully", namespace)) + }), + Method::PUT => { + match info { + Some(info) => { + labels = info.0.labels; + } + None => { + return Err(NamespaceError::Invalid( + "Request body is required".to_string(), + )); + } + } + (*provider) + .update_namespace(namespace.to_string(), labels) + .await + .map(|_| { + HttpResponse::Accepted() + .body(format!("namespace {} was updated successfully", namespace)) + }) + } + Method::GET => (*provider) + .get_namespace(namespace.to_string()) + .await + .map(|ns| HttpResponse::Ok().json(ns)), + _ => Err(NamespaceError::MethodNotAllowed( + "Method not allowed".to_string(), + )), + } +} + +pub async fn namespace_list( + provider: web::Data

, +) -> Result { + (*provider) + .namespace_list() + .await + .map(|ns_list| HttpResponse::Ok().json(ns_list)) +} diff --git a/crates/gateway/src/provider/mod.rs b/crates/gateway/src/provider/mod.rs index 5260878..3a2f197 100644 --- a/crates/gateway/src/provider/mod.rs +++ b/crates/gateway/src/provider/mod.rs @@ -1,6 +1,14 @@ +use std::collections::HashMap; + use crate::{ - handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, - types::function::{Deployment, Query, Status}, + handlers::{ + function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, + namespace::NamespaceError, + }, + types::{ + function::{Deployment, Query, Status}, + namespace::Namespace, + }, }; pub trait Provider: Send + Sync + 'static { @@ -42,4 +50,30 @@ pub trait Provider: Send + Sync + 'static { &self, function: Query, ) -> impl std::future::Future> + Send; + + fn create_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> impl std::future::Future> + Send; + + fn update_namespace( + &self, + namespace: String, + labels: HashMap, + ) -> impl std::future::Future> + Send; + + fn delete_namespace( + &self, + namespace: String, + ) -> impl std::future::Future> + Send; + + fn get_namespace( + &self, + namespace: String, + ) -> impl std::future::Future> + Send; + + fn namespace_list( + &self, + ) -> impl std::future::Future, NamespaceError>> + Send; } diff --git a/crates/gateway/src/types/mod.rs b/crates/gateway/src/types/mod.rs index 30a6de0..1551222 100644 --- a/crates/gateway/src/types/mod.rs +++ b/crates/gateway/src/types/mod.rs @@ -1,2 +1,3 @@ pub mod config; pub mod function; +pub mod namespace; diff --git a/crates/gateway/src/types/namespace.rs b/crates/gateway/src/types/namespace.rs new file mode 100644 index 0000000..b4c9a09 --- /dev/null +++ b/crates/gateway/src/types/namespace.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Namespace { + pub name: Option, + pub labels: HashMap, +}