feat: 添加namespace相关api (#108)

This commit is contained in:
DoL 2025-05-28 20:34:57 +08:00 committed by GitHub
parent 308e9bcc5d
commit ce81aa84c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 454 additions and 25 deletions

View File

@ -2,6 +2,7 @@ pub mod cni;
pub mod container; pub mod container;
pub mod error; pub mod error;
pub mod function; pub mod function;
pub mod namespace;
pub mod oci_image; pub mod oci_image;
pub mod snapshot; pub mod snapshot;
pub mod spec; pub mod spec;

View File

@ -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<tonic::Status> 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<String, String>,
) -> 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<Option<Namespace>, 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<Vec<Namespace>, 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<String, String>,
) -> 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(())
}
}

View File

@ -1,6 +1,7 @@
pub mod delete; pub mod delete;
pub mod deploy; pub mod deploy;
pub mod list; pub mod list;
pub mod namespace;
pub mod resolve; pub mod resolve;
pub mod status; pub mod status;
pub mod update; pub mod update;

View File

@ -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<String, String>,
) -> 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<Namespace, NamespaceError> {
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<Vec<Namespace>, 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<String, String>,
) -> 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()),
})
}
}

View File

@ -1,11 +1,16 @@
pub mod function; pub mod function;
use std::{collections::HashMap, path::Path, sync::Arc};
use std::{path::Path, sync::Arc};
use gateway::{ use gateway::{
handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, handlers::{
function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
namespace::NamespaceError,
},
provider::Provider, provider::Provider,
types::function::{Deployment, Query, Status}, types::{
function::{Deployment, Query, Status},
namespace::Namespace,
},
}; };
pub struct ContainerdProvider { pub struct ContainerdProvider {
@ -46,4 +51,32 @@ impl Provider for ContainerdProvider {
async fn status(&self, function: Query) -> Result<Status, ResolveError> { async fn status(&self, function: Query) -> Result<Status, ResolveError> {
self._status(function).await self._status(function).await
} }
async fn create_namespace(
&self,
namespace: String,
labels: HashMap<String, String>,
) -> Result<(), NamespaceError> {
self._create_namespace(namespace, labels).await
}
async fn update_namespace(
&self,
namespace: String,
labels: HashMap<String, String>,
) -> 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<Namespace, NamespaceError> {
self._get_namespace(namespace).await
}
async fn namespace_list(&self) -> Result<Vec<Namespace>, NamespaceError> {
self._namespace_list().await
}
} }

View File

@ -36,7 +36,16 @@ pub fn config_app<P: Provider>(provider: Arc<P>) -> impl FnOnce(&mut ServiceConf
.service( .service(
web::resource("/function/{functionName}") web::resource("/function/{functionName}")
.route(web::get().to(handlers::function::status::<P>)), .route(web::get().to(handlers::function::status::<P>)),
), // .service( )
.service(
web::resource("/namespace/{namespace}")
.route(web::to(handlers::namespace::mut_namespace::<P>)),
)
.service(
web::resource("/namespaces")
.route(web::get().to(handlers::namespace::namespace_list::<P>)),
),
// .service(
// web::resource("/scale-function/{name}") // web::resource("/scale-function/{name}")
// .route(web::post().to(handlers::scale_function)), // .route(web::post().to(handlers::scale_function)),
// ) // )

View File

@ -1,4 +1,5 @@
pub mod function; pub mod function;
pub mod namespace;
pub mod proxy; pub mod proxy;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View File

@ -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<P: Provider>(
req: HttpRequest,
provider: web::Data<P>,
info: Option<web::Json<Namespace>>,
) -> Result<HttpResponse, NamespaceError> {
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<P: Provider>(
provider: web::Data<P>,
) -> Result<HttpResponse, NamespaceError> {
(*provider)
.namespace_list()
.await
.map(|ns_list| HttpResponse::Ok().json(ns_list))
}

View File

@ -1,6 +1,14 @@
use std::collections::HashMap;
use crate::{ use crate::{
handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError}, handlers::{
types::function::{Deployment, Query, Status}, function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
namespace::NamespaceError,
},
types::{
function::{Deployment, Query, Status},
namespace::Namespace,
},
}; };
pub trait Provider: Send + Sync + 'static { pub trait Provider: Send + Sync + 'static {
@ -42,4 +50,30 @@ pub trait Provider: Send + Sync + 'static {
&self, &self,
function: Query, function: Query,
) -> impl std::future::Future<Output = Result<Status, ResolveError>> + Send; ) -> impl std::future::Future<Output = Result<Status, ResolveError>> + Send;
fn create_namespace(
&self,
namespace: String,
labels: HashMap<String, String>,
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
fn update_namespace(
&self,
namespace: String,
labels: HashMap<String, String>,
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
fn delete_namespace(
&self,
namespace: String,
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
fn get_namespace(
&self,
namespace: String,
) -> impl std::future::Future<Output = Result<Namespace, NamespaceError>> + Send;
fn namespace_list(
&self,
) -> impl std::future::Future<Output = Result<Vec<Namespace>, NamespaceError>> + Send;
} }

View File

@ -1,2 +1,3 @@
pub mod config; pub mod config;
pub mod function; pub mod function;
pub mod namespace;

View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
pub struct Namespace {
pub name: Option<String>,
pub labels: HashMap<String, String>,
}