Implement k8shost deployment REST API
This commit is contained in:
parent
2b7c3166d2
commit
23ec8b5edb
4 changed files with 693 additions and 96 deletions
|
|
@ -45,6 +45,10 @@ struct Args {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
addr: Option<String>,
|
addr: Option<String>,
|
||||||
|
|
||||||
|
/// Listen address for HTTP REST server (e.g., "127.0.0.1:8085")
|
||||||
|
#[arg(long)]
|
||||||
|
http_addr: Option<String>,
|
||||||
|
|
||||||
/// Log level (e.g., "info", "debug", "trace")
|
/// Log level (e.g., "info", "debug", "trace")
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
log_level: Option<String>,
|
log_level: Option<String>,
|
||||||
|
|
@ -112,7 +116,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.addr
|
.addr
|
||||||
.map(|s| s.parse().unwrap_or(loaded_config.server.addr))
|
.map(|s| s.parse().unwrap_or(loaded_config.server.addr))
|
||||||
.unwrap_or(loaded_config.server.addr),
|
.unwrap_or(loaded_config.server.addr),
|
||||||
http_addr: loaded_config.server.http_addr,
|
http_addr: args
|
||||||
|
.http_addr
|
||||||
|
.map(|s| s.parse().unwrap_or(loaded_config.server.http_addr))
|
||||||
|
.unwrap_or(loaded_config.server.http_addr),
|
||||||
log_level: args.log_level.unwrap_or(loaded_config.server.log_level),
|
log_level: args.log_level.unwrap_or(loaded_config.server.log_level),
|
||||||
},
|
},
|
||||||
flaredb: config::FlareDbConfig {
|
flaredb: config::FlareDbConfig {
|
||||||
|
|
@ -277,7 +284,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
auth_service.clone(),
|
auth_service.clone(),
|
||||||
));
|
));
|
||||||
let node_service = Arc::new(NodeServiceImpl::new(storage.clone(), auth_service.clone()));
|
let node_service = Arc::new(NodeServiceImpl::new(storage.clone(), auth_service.clone()));
|
||||||
let deployment_service = DeploymentServiceImpl::new(storage.clone(), auth_service.clone());
|
let deployment_service = Arc::new(DeploymentServiceImpl::new(
|
||||||
|
storage.clone(),
|
||||||
|
auth_service.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
// Start scheduler in background with CreditService integration
|
// Start scheduler in background with CreditService integration
|
||||||
let scheduler = Arc::new(scheduler::Scheduler::new_with_credit_service(storage.clone()).await);
|
let scheduler = Arc::new(scheduler::Scheduler::new_with_credit_service(storage.clone()).await);
|
||||||
|
|
@ -335,7 +345,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
make_interceptor(auth_service.clone()),
|
make_interceptor(auth_service.clone()),
|
||||||
))
|
))
|
||||||
.add_service(tonic::codegen::InterceptedService::new(
|
.add_service(tonic::codegen::InterceptedService::new(
|
||||||
DeploymentServiceServer::new(deployment_service),
|
DeploymentServiceServer::new(deployment_service.as_ref().clone()),
|
||||||
make_interceptor(auth_service.clone()),
|
make_interceptor(auth_service.clone()),
|
||||||
))
|
))
|
||||||
.serve(config.server.addr);
|
.serve(config.server.addr);
|
||||||
|
|
@ -343,6 +353,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// HTTP REST API server
|
// HTTP REST API server
|
||||||
let http_addr = config.server.http_addr;
|
let http_addr = config.server.http_addr;
|
||||||
let rest_state = rest::RestApiState {
|
let rest_state = rest::RestApiState {
|
||||||
|
deployment_service: deployment_service.clone(),
|
||||||
pod_service: pod_service.clone(),
|
pod_service: pod_service.clone(),
|
||||||
service_service: service_service.clone(),
|
service_service: service_service.clone(),
|
||||||
node_service: node_service.clone(),
|
node_service: node_service.clone(),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@
|
||||||
//! - DELETE /api/v1/pods/{namespace}/{name} - Delete pod
|
//! - DELETE /api/v1/pods/{namespace}/{name} - Delete pod
|
||||||
//! - GET /api/v1/services - List services
|
//! - GET /api/v1/services - List services
|
||||||
//! - POST /api/v1/services - Create service
|
//! - POST /api/v1/services - Create service
|
||||||
|
//! - GET /api/v1/deployments - List deployments
|
||||||
|
//! - POST /api/v1/deployments - Create deployment
|
||||||
|
//! - GET /api/v1/deployments/{namespace}/{name} - Get deployment
|
||||||
|
//! - PUT /api/v1/deployments/{namespace}/{name} - Update deployment
|
||||||
|
//! - DELETE /api/v1/deployments/{namespace}/{name} - Delete deployment
|
||||||
//! - GET /api/v1/nodes - List nodes
|
//! - GET /api/v1/nodes - List nodes
|
||||||
//! - GET /health - Health check
|
//! - GET /health - Health check
|
||||||
|
|
||||||
|
|
@ -18,21 +23,29 @@ use axum::{
|
||||||
};
|
};
|
||||||
use iam_service_auth::{resolve_tenant_ids_from_context, AuthService, TenantContext};
|
use iam_service_auth::{resolve_tenant_ids_from_context, AuthService, TenantContext};
|
||||||
use k8shost_proto::{
|
use k8shost_proto::{
|
||||||
node_service_server::NodeService, pod_service_server::PodService,
|
deployment_service_server::DeploymentService, node_service_server::NodeService,
|
||||||
service_service_server::ServiceService, Container, CreatePodRequest, CreateServiceRequest,
|
pod_service_server::PodService, service_service_server::ServiceService, Container,
|
||||||
DeletePodRequest, DeleteServiceRequest, ListNodesRequest, ListPodsRequest, ListServicesRequest,
|
ContainerPort, CreateDeploymentRequest, CreatePodRequest, CreateServiceRequest,
|
||||||
Node as ProtoNode, ObjectMeta, Pod as ProtoPod, PodSpec, Service as ProtoService, ServicePort,
|
DeleteDeploymentRequest, DeletePodRequest, DeleteServiceRequest, Deployment as ProtoDeployment,
|
||||||
ServiceSpec,
|
DeploymentSpec, EnvVar, GetDeploymentRequest, LabelSelector, ListDeploymentsRequest,
|
||||||
|
ListNodesRequest, ListPodsRequest, ListServicesRequest, Node as ProtoNode, ObjectMeta,
|
||||||
|
Pod as ProtoPod, PodSpec, PodTemplateSpec, Service as ProtoService, ServicePort, ServiceSpec,
|
||||||
|
UpdateDeploymentRequest,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tonic::{Code, Request};
|
use tonic::{Code, Request};
|
||||||
|
|
||||||
use crate::services::{node::NodeServiceImpl, pod::PodServiceImpl, service::ServiceServiceImpl};
|
use crate::services::{
|
||||||
|
deployment::DeploymentServiceImpl, node::NodeServiceImpl, pod::PodServiceImpl,
|
||||||
|
service::ServiceServiceImpl,
|
||||||
|
};
|
||||||
|
|
||||||
/// REST API state
|
/// REST API state
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RestApiState {
|
pub struct RestApiState {
|
||||||
|
pub deployment_service: Arc<DeploymentServiceImpl>,
|
||||||
pub pod_service: Arc<PodServiceImpl>,
|
pub pod_service: Arc<PodServiceImpl>,
|
||||||
pub service_service: Arc<ServiceServiceImpl>,
|
pub service_service: Arc<ServiceServiceImpl>,
|
||||||
pub node_service: Arc<NodeServiceImpl>,
|
pub node_service: Arc<NodeServiceImpl>,
|
||||||
|
|
@ -103,7 +116,50 @@ pub struct CreateServiceRequestRest {
|
||||||
pub service_type: Option<String>,
|
pub service_type: Option<String>,
|
||||||
pub port: i32,
|
pub port: i32,
|
||||||
pub target_port: Option<i32>,
|
pub target_port: Option<i32>,
|
||||||
pub selector: Option<std::collections::HashMap<String, String>>,
|
pub selector: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deployment creation request
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct CreateDeploymentRequestRest {
|
||||||
|
pub name: String,
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
pub replicas: Option<i32>,
|
||||||
|
pub selector: HashMap<String, String>,
|
||||||
|
pub template_labels: Option<HashMap<String, String>>,
|
||||||
|
pub containers: Vec<DeploymentContainerRequestRest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deployment update request
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct UpdateDeploymentRequestRest {
|
||||||
|
pub replicas: Option<i32>,
|
||||||
|
pub selector: Option<HashMap<String, String>>,
|
||||||
|
pub template_labels: Option<HashMap<String, String>>,
|
||||||
|
pub containers: Option<Vec<DeploymentContainerRequestRest>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct DeploymentContainerRequestRest {
|
||||||
|
pub name: String,
|
||||||
|
pub image: String,
|
||||||
|
pub command: Option<Vec<String>>,
|
||||||
|
pub args: Option<Vec<String>>,
|
||||||
|
pub ports: Option<Vec<DeploymentContainerPortRequestRest>>,
|
||||||
|
pub env: Option<Vec<DeploymentEnvVarRequestRest>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct DeploymentContainerPortRequestRest {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub container_port: i32,
|
||||||
|
pub protocol: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct DeploymentEnvVarRequestRest {
|
||||||
|
pub name: String,
|
||||||
|
pub value: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query params for list operations
|
/// Query params for list operations
|
||||||
|
|
@ -273,12 +329,169 @@ pub struct NodesResponse {
|
||||||
pub nodes: Vec<NodeResponse>,
|
pub nodes: Vec<NodeResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deployment response
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DeploymentResponse {
|
||||||
|
pub name: String,
|
||||||
|
pub namespace: String,
|
||||||
|
pub replicas: i32,
|
||||||
|
pub ready_replicas: i32,
|
||||||
|
pub available_replicas: i32,
|
||||||
|
pub selector: HashMap<String, String>,
|
||||||
|
pub template_labels: HashMap<String, String>,
|
||||||
|
pub containers: Vec<DeploymentContainerResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DeploymentContainerResponse {
|
||||||
|
pub name: String,
|
||||||
|
pub image: String,
|
||||||
|
pub command: Vec<String>,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub ports: Vec<DeploymentContainerPortResponse>,
|
||||||
|
pub env: Vec<DeploymentEnvVarResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DeploymentContainerPortResponse {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub container_port: i32,
|
||||||
|
pub protocol: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DeploymentEnvVarResponse {
|
||||||
|
pub name: String,
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProtoDeployment> for DeploymentResponse {
|
||||||
|
fn from(deployment: ProtoDeployment) -> Self {
|
||||||
|
let metadata = deployment.metadata.unwrap_or(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: None,
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
});
|
||||||
|
let DeploymentSpec {
|
||||||
|
replicas,
|
||||||
|
selector,
|
||||||
|
template,
|
||||||
|
} = deployment.spec.unwrap_or(DeploymentSpec {
|
||||||
|
replicas: None,
|
||||||
|
selector: None,
|
||||||
|
template: None,
|
||||||
|
});
|
||||||
|
let template = template.unwrap_or(PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: None,
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
containers: Vec::new(),
|
||||||
|
restart_policy: None,
|
||||||
|
node_name: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let template_metadata = template.metadata.unwrap_or(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: None,
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
});
|
||||||
|
let template_spec = template.spec.unwrap_or(PodSpec {
|
||||||
|
containers: Vec::new(),
|
||||||
|
restart_policy: None,
|
||||||
|
node_name: None,
|
||||||
|
});
|
||||||
|
let status = deployment.status;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name: metadata.name,
|
||||||
|
namespace: metadata.namespace.unwrap_or_else(|| "default".to_string()),
|
||||||
|
replicas: replicas.unwrap_or(1),
|
||||||
|
ready_replicas: status
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|status| status.ready_replicas)
|
||||||
|
.unwrap_or(0),
|
||||||
|
available_replicas: status
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|status| status.available_replicas)
|
||||||
|
.unwrap_or(0),
|
||||||
|
selector: selector
|
||||||
|
.map(|selector| selector.match_labels)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
template_labels: template_metadata.labels,
|
||||||
|
containers: template_spec
|
||||||
|
.containers
|
||||||
|
.into_iter()
|
||||||
|
.map(|container| DeploymentContainerResponse {
|
||||||
|
name: container.name,
|
||||||
|
image: container.image,
|
||||||
|
command: container.command,
|
||||||
|
args: container.args,
|
||||||
|
ports: container
|
||||||
|
.ports
|
||||||
|
.into_iter()
|
||||||
|
.map(|port| DeploymentContainerPortResponse {
|
||||||
|
name: port.name,
|
||||||
|
container_port: port.container_port,
|
||||||
|
protocol: port.protocol,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
env: container
|
||||||
|
.env
|
||||||
|
.into_iter()
|
||||||
|
.map(|env| DeploymentEnvVarResponse {
|
||||||
|
name: env.name,
|
||||||
|
value: env.value,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deployments list response
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct DeploymentsResponse {
|
||||||
|
pub deployments: Vec<DeploymentResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the REST API router
|
/// Build the REST API router
|
||||||
pub fn build_router(state: RestApiState) -> Router {
|
pub fn build_router(state: RestApiState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/api/v1/pods", get(list_pods).post(create_pod))
|
.route("/api/v1/pods", get(list_pods).post(create_pod))
|
||||||
.route("/api/v1/pods/{namespace}/{name}", delete(delete_pod))
|
.route("/api/v1/pods/{namespace}/{name}", delete(delete_pod))
|
||||||
.route("/api/v1/services", get(list_services).post(create_service))
|
.route("/api/v1/services", get(list_services).post(create_service))
|
||||||
|
.route(
|
||||||
|
"/api/v1/deployments",
|
||||||
|
get(list_deployments).post(create_deployment),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/deployments/{namespace}/{name}",
|
||||||
|
get(get_deployment)
|
||||||
|
.put(update_deployment)
|
||||||
|
.delete(delete_deployment),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/v1/services/{namespace}/{name}",
|
"/api/v1/services/{namespace}/{name}",
|
||||||
delete(delete_service),
|
delete(delete_service),
|
||||||
|
|
@ -311,13 +524,11 @@ async fn list_pods(
|
||||||
});
|
});
|
||||||
req.extensions_mut().insert(tenant);
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
let response = state.pod_service.list_pods(req).await.map_err(|e| {
|
let response = state
|
||||||
error_response(
|
.pod_service
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.list_pods(req)
|
||||||
"LIST_FAILED",
|
.await
|
||||||
&e.message(),
|
.map_err(map_tonic_status)?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let pods: Vec<PodResponse> = response
|
let pods: Vec<PodResponse> = response
|
||||||
.into_inner()
|
.into_inner()
|
||||||
|
|
@ -368,13 +579,11 @@ async fn create_pod(
|
||||||
});
|
});
|
||||||
grpc_req.extensions_mut().insert(tenant);
|
grpc_req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
let response = state.pod_service.create_pod(grpc_req).await.map_err(|e| {
|
let response = state
|
||||||
error_response(
|
.pod_service
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.create_pod(grpc_req)
|
||||||
"CREATE_FAILED",
|
.await
|
||||||
&e.message(),
|
.map_err(map_tonic_status)?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let pod = response.into_inner().pod.ok_or_else(|| {
|
let pod = response.into_inner().pod.ok_or_else(|| {
|
||||||
error_response(
|
error_response(
|
||||||
|
|
@ -404,13 +613,11 @@ async fn delete_pod(
|
||||||
});
|
});
|
||||||
req.extensions_mut().insert(tenant);
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
state.pod_service.delete_pod(req).await.map_err(|e| {
|
state
|
||||||
error_response(
|
.pod_service
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.delete_pod(req)
|
||||||
"DELETE_FAILED",
|
.await
|
||||||
&e.message(),
|
.map_err(map_tonic_status)?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
|
|
@ -436,13 +643,7 @@ async fn list_services(
|
||||||
.service_service
|
.service_service
|
||||||
.list_services(req)
|
.list_services(req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(map_tonic_status)?;
|
||||||
error_response(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"LIST_FAILED",
|
|
||||||
&e.message(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let services: Vec<ServiceResponse> = response
|
let services: Vec<ServiceResponse> = response
|
||||||
.into_inner()
|
.into_inner()
|
||||||
|
|
@ -498,13 +699,7 @@ async fn create_service(
|
||||||
.service_service
|
.service_service
|
||||||
.create_service(grpc_req)
|
.create_service(grpc_req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(map_tonic_status)?;
|
||||||
error_response(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"CREATE_FAILED",
|
|
||||||
&e.message(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let service = response.into_inner().service.ok_or_else(|| {
|
let service = response.into_inner().service.ok_or_else(|| {
|
||||||
error_response(
|
error_response(
|
||||||
|
|
@ -538,13 +733,7 @@ async fn delete_service(
|
||||||
.service_service
|
.service_service
|
||||||
.delete_service(req)
|
.delete_service(req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(map_tonic_status)?;
|
||||||
error_response(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"DELETE_FAILED",
|
|
||||||
&e.message(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
|
|
@ -563,13 +752,11 @@ async fn list_nodes(
|
||||||
let mut req = Request::new(ListNodesRequest {});
|
let mut req = Request::new(ListNodesRequest {});
|
||||||
req.extensions_mut().insert(tenant);
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
let response = state.node_service.list_nodes(req).await.map_err(|e| {
|
let response = state
|
||||||
error_response(
|
.node_service
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.list_nodes(req)
|
||||||
"LIST_FAILED",
|
.await
|
||||||
&e.message(),
|
.map_err(map_tonic_status)?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let nodes: Vec<NodeResponse> = response
|
let nodes: Vec<NodeResponse> = response
|
||||||
.into_inner()
|
.into_inner()
|
||||||
|
|
@ -581,6 +768,393 @@ async fn list_nodes(
|
||||||
Ok(Json(SuccessResponse::new(NodesResponse { nodes })))
|
Ok(Json(SuccessResponse::new(NodesResponse { nodes })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /api/v1/deployments - List deployments
|
||||||
|
async fn list_deployments(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
Query(params): Query<ListParams>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<Json<SuccessResponse<DeploymentsResponse>>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
let tenant = resolve_rest_tenant(&state, &headers).await?;
|
||||||
|
let mut req = Request::new(ListDeploymentsRequest {
|
||||||
|
namespace: params.namespace,
|
||||||
|
});
|
||||||
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
|
let response = state
|
||||||
|
.deployment_service
|
||||||
|
.list_deployments(req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?;
|
||||||
|
|
||||||
|
let deployments = response
|
||||||
|
.into_inner()
|
||||||
|
.items
|
||||||
|
.into_iter()
|
||||||
|
.map(DeploymentResponse::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::new(DeploymentsResponse {
|
||||||
|
deployments,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /api/v1/deployments/{namespace}/{name} - Get deployment
|
||||||
|
async fn get_deployment(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
Path((namespace, name)): Path<(String, String)>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<Json<SuccessResponse<DeploymentResponse>>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
let tenant = resolve_rest_tenant(&state, &headers).await?;
|
||||||
|
let mut req = Request::new(GetDeploymentRequest { namespace, name });
|
||||||
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
|
let response = state
|
||||||
|
.deployment_service
|
||||||
|
.get_deployment(req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?;
|
||||||
|
let deployment = response.into_inner().deployment.ok_or_else(|| {
|
||||||
|
error_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"INTERNAL",
|
||||||
|
"No deployment returned",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::new(DeploymentResponse::from(
|
||||||
|
deployment,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /api/v1/deployments - Create deployment
|
||||||
|
async fn create_deployment(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(req): Json<CreateDeploymentRequestRest>,
|
||||||
|
) -> Result<
|
||||||
|
(StatusCode, Json<SuccessResponse<DeploymentResponse>>),
|
||||||
|
(StatusCode, Json<ErrorResponse>),
|
||||||
|
> {
|
||||||
|
let tenant = resolve_rest_tenant(&state, &headers).await?;
|
||||||
|
let mut grpc_req = Request::new(CreateDeploymentRequest {
|
||||||
|
deployment: Some(build_proto_deployment(
|
||||||
|
req.name,
|
||||||
|
req.namespace,
|
||||||
|
req.replicas,
|
||||||
|
req.selector,
|
||||||
|
req.template_labels,
|
||||||
|
req.containers,
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
grpc_req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
|
let response = state
|
||||||
|
.deployment_service
|
||||||
|
.create_deployment(grpc_req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?;
|
||||||
|
let deployment = response.into_inner().deployment.ok_or_else(|| {
|
||||||
|
error_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"INTERNAL",
|
||||||
|
"No deployment returned",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::CREATED,
|
||||||
|
Json(SuccessResponse::new(DeploymentResponse::from(deployment))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PUT /api/v1/deployments/{namespace}/{name} - Update deployment
|
||||||
|
async fn update_deployment(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
Path((namespace, name)): Path<(String, String)>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(req): Json<UpdateDeploymentRequestRest>,
|
||||||
|
) -> Result<Json<SuccessResponse<DeploymentResponse>>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
let tenant = resolve_rest_tenant(&state, &headers).await?;
|
||||||
|
|
||||||
|
let mut get_req = Request::new(GetDeploymentRequest {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
});
|
||||||
|
get_req.extensions_mut().insert(tenant.clone());
|
||||||
|
let existing = state
|
||||||
|
.deployment_service
|
||||||
|
.get_deployment(get_req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?
|
||||||
|
.into_inner()
|
||||||
|
.deployment
|
||||||
|
.ok_or_else(|| {
|
||||||
|
error_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"INTERNAL",
|
||||||
|
"No deployment returned",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut deployment = existing;
|
||||||
|
let spec = deployment.spec.get_or_insert(DeploymentSpec {
|
||||||
|
replicas: None,
|
||||||
|
selector: None,
|
||||||
|
template: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(replicas) = req.replicas {
|
||||||
|
spec.replicas = Some(replicas);
|
||||||
|
}
|
||||||
|
if let Some(selector) = req.selector {
|
||||||
|
spec.selector = Some(LabelSelector {
|
||||||
|
match_labels: selector,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = spec.template.get_or_insert(PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: Some(namespace.clone()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
containers: Vec::new(),
|
||||||
|
restart_policy: None,
|
||||||
|
node_name: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(template_labels) = req.template_labels {
|
||||||
|
template
|
||||||
|
.metadata
|
||||||
|
.get_or_insert_with(|| ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: Some(namespace.clone()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
})
|
||||||
|
.labels = template_labels;
|
||||||
|
}
|
||||||
|
if let Some(containers) = req.containers {
|
||||||
|
template
|
||||||
|
.spec
|
||||||
|
.get_or_insert(PodSpec {
|
||||||
|
containers: Vec::new(),
|
||||||
|
restart_policy: None,
|
||||||
|
node_name: None,
|
||||||
|
})
|
||||||
|
.containers = containers
|
||||||
|
.into_iter()
|
||||||
|
.map(proto_container_from_rest)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_selector_labels_on_template(spec);
|
||||||
|
|
||||||
|
let metadata = deployment.metadata.get_or_insert(ObjectMeta {
|
||||||
|
name: name.clone(),
|
||||||
|
namespace: Some(namespace.clone()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
});
|
||||||
|
metadata.name = name;
|
||||||
|
metadata.namespace = Some(namespace);
|
||||||
|
|
||||||
|
let mut update_req = Request::new(UpdateDeploymentRequest {
|
||||||
|
deployment: Some(deployment),
|
||||||
|
});
|
||||||
|
update_req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
|
let response = state
|
||||||
|
.deployment_service
|
||||||
|
.update_deployment(update_req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?;
|
||||||
|
let deployment = response.into_inner().deployment.ok_or_else(|| {
|
||||||
|
error_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"INTERNAL",
|
||||||
|
"No deployment returned",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::new(DeploymentResponse::from(
|
||||||
|
deployment,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DELETE /api/v1/deployments/{namespace}/{name} - Delete deployment
|
||||||
|
async fn delete_deployment(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
Path((namespace, name)): Path<(String, String)>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
|
||||||
|
{
|
||||||
|
let tenant = resolve_rest_tenant(&state, &headers).await?;
|
||||||
|
let mut req = Request::new(DeleteDeploymentRequest {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
});
|
||||||
|
req.extensions_mut().insert(tenant);
|
||||||
|
|
||||||
|
state
|
||||||
|
.deployment_service
|
||||||
|
.delete_deployment(req)
|
||||||
|
.await
|
||||||
|
.map_err(map_tonic_status)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(SuccessResponse::new(serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"namespace": namespace,
|
||||||
|
"deleted": true
|
||||||
|
}))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_proto_deployment(
|
||||||
|
name: String,
|
||||||
|
namespace: Option<String>,
|
||||||
|
replicas: Option<i32>,
|
||||||
|
selector: HashMap<String, String>,
|
||||||
|
template_labels: Option<HashMap<String, String>>,
|
||||||
|
containers: Vec<DeploymentContainerRequestRest>,
|
||||||
|
) -> ProtoDeployment {
|
||||||
|
let namespace = namespace.unwrap_or_else(|| "default".to_string());
|
||||||
|
let mut labels = template_labels.unwrap_or_else(|| selector.clone());
|
||||||
|
for (key, value) in &selector {
|
||||||
|
labels.entry(key.clone()).or_insert_with(|| value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtoDeployment {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
name,
|
||||||
|
namespace: Some(namespace.clone()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
}),
|
||||||
|
spec: Some(DeploymentSpec {
|
||||||
|
replicas,
|
||||||
|
selector: Some(LabelSelector {
|
||||||
|
match_labels: selector,
|
||||||
|
}),
|
||||||
|
template: Some(PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: Some(namespace),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels,
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
containers: containers
|
||||||
|
.into_iter()
|
||||||
|
.map(proto_container_from_rest)
|
||||||
|
.collect(),
|
||||||
|
restart_policy: Some("Always".to_string()),
|
||||||
|
node_name: None,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
status: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proto_container_from_rest(container: DeploymentContainerRequestRest) -> Container {
|
||||||
|
Container {
|
||||||
|
name: container.name,
|
||||||
|
image: container.image,
|
||||||
|
command: container.command.unwrap_or_default(),
|
||||||
|
args: container.args.unwrap_or_default(),
|
||||||
|
ports: container
|
||||||
|
.ports
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|port| ContainerPort {
|
||||||
|
name: port.name,
|
||||||
|
container_port: port.container_port,
|
||||||
|
protocol: port.protocol,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
env: container
|
||||||
|
.env
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|env| EnvVar {
|
||||||
|
name: env.name,
|
||||||
|
value: env.value,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_selector_labels_on_template(spec: &mut DeploymentSpec) {
|
||||||
|
let selector = spec
|
||||||
|
.selector
|
||||||
|
.as_ref()
|
||||||
|
.map(|selector| selector.match_labels.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let template = spec.template.get_or_insert(PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: Some("default".to_string()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
containers: Vec::new(),
|
||||||
|
restart_policy: None,
|
||||||
|
node_name: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let metadata = template.metadata.get_or_insert_with(|| ObjectMeta {
|
||||||
|
name: String::new(),
|
||||||
|
namespace: Some("default".to_string()),
|
||||||
|
uid: None,
|
||||||
|
resource_version: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
labels: HashMap::new(),
|
||||||
|
annotations: HashMap::new(),
|
||||||
|
org_id: None,
|
||||||
|
project_id: None,
|
||||||
|
});
|
||||||
|
for (key, value) in selector {
|
||||||
|
metadata.labels.entry(key).or_insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to create error response
|
/// Helper to create error response
|
||||||
fn error_response(
|
fn error_response(
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
|
|
@ -608,17 +1182,22 @@ async fn resolve_rest_tenant(
|
||||||
.auth_service
|
.auth_service
|
||||||
.authenticate_headers(headers)
|
.authenticate_headers(headers)
|
||||||
.await
|
.await
|
||||||
.map_err(map_auth_status)?;
|
.map_err(map_tonic_status)?;
|
||||||
resolve_tenant_ids_from_context(&tenant, "", "").map_err(map_auth_status)?;
|
resolve_tenant_ids_from_context(&tenant, "", "").map_err(map_tonic_status)?;
|
||||||
Ok(tenant)
|
Ok(tenant)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_auth_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
|
fn map_tonic_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
|
||||||
let status_code = match status.code() {
|
let status_code = match status.code() {
|
||||||
Code::Unauthenticated => StatusCode::UNAUTHORIZED,
|
Code::Unauthenticated => StatusCode::UNAUTHORIZED,
|
||||||
Code::PermissionDenied => StatusCode::FORBIDDEN,
|
Code::PermissionDenied => StatusCode::FORBIDDEN,
|
||||||
Code::InvalidArgument => StatusCode::BAD_REQUEST,
|
Code::InvalidArgument => StatusCode::BAD_REQUEST,
|
||||||
Code::NotFound => StatusCode::NOT_FOUND,
|
Code::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
Code::AlreadyExists => StatusCode::CONFLICT,
|
||||||
|
Code::FailedPrecondition => StatusCode::PRECONDITION_FAILED,
|
||||||
|
Code::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
|
||||||
|
Code::DeadlineExceeded => StatusCode::GATEWAY_TIMEOUT,
|
||||||
|
Code::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
};
|
};
|
||||||
let code = match status.code() {
|
let code = match status.code() {
|
||||||
|
|
@ -626,6 +1205,11 @@ fn map_auth_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
|
||||||
Code::PermissionDenied => "FORBIDDEN",
|
Code::PermissionDenied => "FORBIDDEN",
|
||||||
Code::InvalidArgument => "INVALID_ARGUMENT",
|
Code::InvalidArgument => "INVALID_ARGUMENT",
|
||||||
Code::NotFound => "NOT_FOUND",
|
Code::NotFound => "NOT_FOUND",
|
||||||
|
Code::AlreadyExists => "ALREADY_EXISTS",
|
||||||
|
Code::FailedPrecondition => "FAILED_PRECONDITION",
|
||||||
|
Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
|
||||||
|
Code::DeadlineExceeded => "DEADLINE_EXCEEDED",
|
||||||
|
Code::Unavailable => "UNAVAILABLE",
|
||||||
_ => "INTERNAL",
|
_ => "INTERNAL",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ in
|
||||||
description = "Port for k8shost gRPC API server";
|
description = "Port for k8shost gRPC API server";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
httpPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 8085;
|
||||||
|
description = "Port for k8shost HTTP REST API server";
|
||||||
|
};
|
||||||
|
|
||||||
iamAddr = lib.mkOption {
|
iamAddr = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = null;
|
default = null;
|
||||||
|
|
@ -126,6 +132,7 @@ in
|
||||||
ExecStart = lib.concatStringsSep " " ([
|
ExecStart = lib.concatStringsSep " " ([
|
||||||
"${cfg.package}/bin/k8shost-server"
|
"${cfg.package}/bin/k8shost-server"
|
||||||
"--addr 0.0.0.0:${toString cfg.port}"
|
"--addr 0.0.0.0:${toString cfg.port}"
|
||||||
|
"--http-addr 127.0.0.1:${toString cfg.httpPort}"
|
||||||
] ++ lib.optional (cfg.iamAddr != null) "--iam-server-addr ${cfg.iamAddr}"
|
] ++ lib.optional (cfg.iamAddr != null) "--iam-server-addr ${cfg.iamAddr}"
|
||||||
++ lib.optional (cfg.chainfireAddr != null) "--chainfire-endpoint ${cfg.chainfireAddr}"
|
++ lib.optional (cfg.chainfireAddr != null) "--chainfire-endpoint ${cfg.chainfireAddr}"
|
||||||
++ lib.optional (cfg.prismnetAddr != null) "--prismnet-server-addr ${cfg.prismnetAddr}"
|
++ lib.optional (cfg.prismnetAddr != null) "--prismnet-server-addr ${cfg.prismnetAddr}"
|
||||||
|
|
|
||||||
|
|
@ -3463,13 +3463,14 @@ validate_fiberlb_flow() {
|
||||||
validate_k8shost_flow() {
|
validate_k8shost_flow() {
|
||||||
log "Validating K8sHost node, pod, service, and controller integrations"
|
log "Validating K8sHost node, pod, service, and controller integrations"
|
||||||
|
|
||||||
local iam_tunnel="" prism_tunnel="" dns_tunnel="" lb_tunnel="" k8s_tunnel=""
|
local iam_tunnel="" prism_tunnel="" dns_tunnel="" lb_tunnel="" k8s_tunnel="" k8s_http_tunnel=""
|
||||||
iam_tunnel="$(start_ssh_tunnel node01 15080 50080)"
|
iam_tunnel="$(start_ssh_tunnel node01 15080 50080)"
|
||||||
prism_tunnel="$(start_ssh_tunnel node01 15081 50081)"
|
prism_tunnel="$(start_ssh_tunnel node01 15081 50081)"
|
||||||
dns_tunnel="$(start_ssh_tunnel node01 15084 50084)"
|
dns_tunnel="$(start_ssh_tunnel node01 15084 50084)"
|
||||||
lb_tunnel="$(start_ssh_tunnel node01 15085 50085)"
|
lb_tunnel="$(start_ssh_tunnel node01 15085 50085)"
|
||||||
k8s_tunnel="$(start_ssh_tunnel node01 15087 50087)"
|
k8s_tunnel="$(start_ssh_tunnel node01 15087 50087)"
|
||||||
trap 'stop_ssh_tunnel node01 "${k8s_tunnel}"; stop_ssh_tunnel node01 "${lb_tunnel}"; stop_ssh_tunnel node01 "${dns_tunnel}"; stop_ssh_tunnel node01 "${prism_tunnel}"; stop_ssh_tunnel node01 "${iam_tunnel}"' RETURN
|
k8s_http_tunnel="$(start_ssh_tunnel node01 18087 8085)"
|
||||||
|
trap 'stop_ssh_tunnel node01 "${k8s_http_tunnel}"; stop_ssh_tunnel node01 "${k8s_tunnel}"; stop_ssh_tunnel node01 "${lb_tunnel}"; stop_ssh_tunnel node01 "${dns_tunnel}"; stop_ssh_tunnel node01 "${prism_tunnel}"; stop_ssh_tunnel node01 "${iam_tunnel}"' RETURN
|
||||||
|
|
||||||
local org_id="default-org"
|
local org_id="default-org"
|
||||||
local project_id="default-project"
|
local project_id="default-project"
|
||||||
|
|
@ -3503,19 +3504,16 @@ validate_k8shost_flow() {
|
||||||
127.0.0.1:15087 k8shost.NodeService/ListNodes \
|
127.0.0.1:15087 k8shost.NodeService/ListNodes \
|
||||||
| jq -e --arg name "${node_name}" '.items | any(.metadata.name == $name)' >/dev/null
|
| jq -e --arg name "${node_name}" '.items | any(.metadata.name == $name)' >/dev/null
|
||||||
|
|
||||||
grpcurl -plaintext \
|
curl -fsS \
|
||||||
-H "authorization: Bearer ${token}" \
|
-H "Authorization: Bearer ${token}" \
|
||||||
-import-path "${K8SHOST_PROTO_DIR}" \
|
-H "Content-Type: application/json" \
|
||||||
-proto "${K8SHOST_PROTO}" \
|
-d "$(jq -cn --arg name "${deployment_name}" '{name:$name, namespace:"default", replicas:2, selector:{app:"k8shost-deployment-smoke", deployment:$name}, containers:[{name:"backend", image:"smoke", ports:[{container_port:8082, protocol:"TCP"}]}]}')" \
|
||||||
-d "$(jq -cn --arg name "${deployment_name}" --arg org "${org_id}" --arg project "${project_id}" '{deployment:{metadata:{name:$name, namespace:"default", orgId:$org, projectId:$project}, spec:{replicas:2, selector:{matchLabels:{app:"k8shost-deployment-smoke", deployment:$name}}, template:{metadata:{name:"", namespace:"default", labels:{app:"k8shost-deployment-smoke", deployment:$name}}, spec:{containers:[{name:"backend", image:"smoke", ports:[{containerPort:8082, protocol:"TCP"}]}]}}}}}')" \
|
http://127.0.0.1:18087/api/v1/deployments \
|
||||||
127.0.0.1:15087 k8shost.DeploymentService/CreateDeployment >/dev/null
|
| jq -e --arg name "${deployment_name}" '.data.name == $name and .data.replicas == 2' >/dev/null
|
||||||
grpcurl -plaintext \
|
curl -fsS \
|
||||||
-H "authorization: Bearer ${token}" \
|
-H "Authorization: Bearer ${token}" \
|
||||||
-import-path "${K8SHOST_PROTO_DIR}" \
|
http://127.0.0.1:18087/api/v1/deployments?namespace=default \
|
||||||
-proto "${K8SHOST_PROTO}" \
|
| jq -e --arg name "${deployment_name}" '.data.deployments | any(.name == $name)' >/dev/null
|
||||||
-d "$(jq -cn '{namespace:"default"}')" \
|
|
||||||
127.0.0.1:15087 k8shost.DeploymentService/ListDeployments \
|
|
||||||
| jq -e --arg name "${deployment_name}" '.items | any(.metadata.name == $name)' >/dev/null
|
|
||||||
|
|
||||||
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
||||||
while true; do
|
while true; do
|
||||||
|
|
@ -3537,19 +3535,17 @@ validate_k8shost_flow() {
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
local deployment_json
|
curl -fsS \
|
||||||
deployment_json="$(grpcurl -plaintext \
|
-H "Authorization: Bearer ${token}" \
|
||||||
-H "authorization: Bearer ${token}" \
|
http://127.0.0.1:18087/api/v1/deployments/default/${deployment_name} \
|
||||||
-import-path "${K8SHOST_PROTO_DIR}" \
|
| jq -e --arg name "${deployment_name}" '.data.name == $name and .data.ready_replicas >= 0' >/dev/null
|
||||||
-proto "${K8SHOST_PROTO}" \
|
curl -fsS \
|
||||||
-d "$(jq -cn --arg ns "default" --arg name "${deployment_name}" '{namespace:$ns, name:$name}')" \
|
-X PUT \
|
||||||
127.0.0.1:15087 k8shost.DeploymentService/GetDeployment)"
|
-H "Authorization: Bearer ${token}" \
|
||||||
grpcurl -plaintext \
|
-H "Content-Type: application/json" \
|
||||||
-H "authorization: Bearer ${token}" \
|
-d '{"replicas":1}' \
|
||||||
-import-path "${K8SHOST_PROTO_DIR}" \
|
http://127.0.0.1:18087/api/v1/deployments/default/${deployment_name} \
|
||||||
-proto "${K8SHOST_PROTO}" \
|
| jq -e '.data.replicas == 1' >/dev/null
|
||||||
-d "$(printf '%s' "${deployment_json}" | jq '.deployment.spec.replicas = 1 | {deployment:.deployment}')" \
|
|
||||||
127.0.0.1:15087 k8shost.DeploymentService/UpdateDeployment >/dev/null
|
|
||||||
|
|
||||||
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
||||||
while true; do
|
while true; do
|
||||||
|
|
@ -3569,12 +3565,11 @@ validate_k8shost_flow() {
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
grpcurl -plaintext \
|
curl -fsS \
|
||||||
-H "authorization: Bearer ${token}" \
|
-X DELETE \
|
||||||
-import-path "${K8SHOST_PROTO_DIR}" \
|
-H "Authorization: Bearer ${token}" \
|
||||||
-proto "${K8SHOST_PROTO}" \
|
http://127.0.0.1:18087/api/v1/deployments/default/${deployment_name} \
|
||||||
-d "$(jq -cn --arg ns "default" --arg name "${deployment_name}" '{namespace:$ns, name:$name}')" \
|
| jq -e '.data.deleted == true' >/dev/null
|
||||||
127.0.0.1:15087 k8shost.DeploymentService/DeleteDeployment >/dev/null
|
|
||||||
|
|
||||||
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
deadline=$((SECONDS + HTTP_WAIT_TIMEOUT))
|
||||||
while true; do
|
while true; do
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue