use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use serde::{Serialize, Deserialize};
use tracing::{info, warn, error, debug};
use std::sync::RwLock;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmberCluster {
pub api_version: String,
pub kind: String,
pub metadata: ObjectMeta,
pub spec: AmberClusterSpec,
pub status: Option<AmberClusterStatus>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ObjectMeta {
pub name: String,
pub namespace: String,
pub labels: Option<std::collections::HashMap<String, String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmberClusterSpec {
pub replicas: u32,
pub version: String,
pub image: Option<String>,
pub resources: ResourceRequirements,
pub networking: NetworkingConfig,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmberClusterStatus {
pub phase: ClusterPhase,
pub replicas: u32,
pub ready_replicas: u32,
pub conditions: Vec<Condition>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum ClusterPhase {
Pending,
Creating,
Running,
Failed,
Terminating,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Condition {
pub type_: String,
pub status: String,
pub last_transition_time: String,
pub reason: String,
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceRequirements {
pub cpu: Option<String>,
pub memory: Option<String>,
pub storage: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkingConfig {
pub service_type: ServiceType,
pub port: u32,
pub ingress: Option<IngressConfig>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
ClusterIP,
NodePort,
LoadBalancer,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IngressConfig {
pub enabled: bool,
pub host: Option<String>,
pub tls_enabled: bool,
}
pub struct Operator {
config: OperatorConfig,
clusters: Arc<RwLock<std::collections::HashMap<String, AmberCluster>>>,
event_sender: Arc<tokio::sync::mpsc::UnboundedSender<OperatorEvent>>,
}
#[derive(Debug, Clone)]
pub struct OperatorConfig {
pub namespace: String,
pub reconcile_interval: std::time::Duration,
pub max_retries: u32,
}
#[derive(Debug, Clone)]
pub enum OperatorEvent {
ClusterCreated { name: String, namespace: String },
ClusterUpdated { name: String, namespace: String },
ClusterDeleted { name: String, namespace: String },
ClusterFailed { name: String, namespace: String, reason: String },
}
impl Operator {
pub fn new(config: OperatorConfig) -> (Self, tokio::sync::mpsc::UnboundedReceiver<OperatorEvent>) {
let (event_sender, event_receiver) = tokio::sync::mpsc::unbounded_channel();
let operator: _ = Operator {
config,
clusters: Arc::new(Mutex::new(std::collections::HashMap::new()))
event_sender: Arc::new(Mutex::new(event_sender)))
};
(operator, event_receiver)
}
pub async fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
info!("Starting Amber Kubernetes Operator");
let clusters: _ = self.clusters.clone();
let event_sender: _ = self.event_sender.clone();
let interval: _ = self.config.reconcile_interval;
tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(interval);
loop {
interval_timer.tick().await;
debug!("Reconciliation tick");
let clusters_read: _ = clusters.read().await;
for (name, cluster) in clusters_read.iter() {
debug!("Reconciling cluster: {}", name);
}
}
});
Ok(())
}
pub async fn create_cluster(
&self,
cluster: AmberCluster,
) -> Result<(), Box<dyn std::error::Error>> {
let name: _ = cluster.metadata.name.clone();
let namespace: _ = cluster.metadata.namespace.clone();
info!("Creating AmberCluster: {} in {}", name, namespace);
let mut clusters = self.clusters.write().await;
clusters.insert(name.clone(), cluster);
let event: _ = OperatorEvent::ClusterCreated {
name: name.clone(),
namespace: namespace.clone(),
};
self.event_sender.send(event).map_err(|e| {
error!("Failed to send cluster created event: {}", e);
e
})?;
info!("Successfully created AmberCluster: {} in {}", name, namespace);
Ok(())
}
pub async fn update_cluster(
&self,
name: String,
namespace: String,
spec: AmberClusterSpec,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Updating AmberCluster: {} in {}", name, namespace);
let mut clusters = self.clusters.write().await;
if let Some(cluster) = clusters.get_mut(&name) {
cluster.spec = spec;
let event: _ = OperatorEvent::ClusterUpdated {
name: name.clone(),
namespace: namespace.clone(),
};
self.event_sender.send(event).map_err(|e| {
error!("Failed to send cluster updated event: {}", e);
e
})?;
info!("Successfully updated AmberCluster: {} in {}", name, namespace);
Ok(())
} else {
Err(format!("Cluster {} not found in namespace {}", name, namespace).into())
}
}
pub async fn delete_cluster(
&self,
name: String,
namespace: String,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Deleting AmberCluster: {} in {}", name, namespace);
let mut clusters = self.clusters.write().await;
clusters.remove(&name);
let event: _ = OperatorEvent::ClusterDeleted {
name: name.clone(),
namespace: namespace.clone(),
};
self.event_sender.send(event).map_err(|e| {
error!("Failed to send cluster deleted event: {}", e);
e
})?;
info!("Successfully deleted AmberCluster: {} in {}", name, namespace);
Ok(())
}
pub async fn get_cluster_status(
&self,
name: String,
) -> Result<Option<AmberClusterStatus>, Box<dyn std::error::Error>> {
let clusters: _ = self.clusters.read().await;
Ok(clusters.get(&name).and_then(|c| c.status.clone())
}
pub async fn list_clusters(&self) -> Result<Vec<AmberCluster>, Box<dyn std::error::Error>> {
let clusters: _ = self.clusters.read().await;
Ok(clusters.values().cloned().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_operator_creation() {
let config: _ = OperatorConfig {
namespace: "default".to_string(),
reconcile_interval: std::time::Duration::from_secs(30),
max_retries: 3,
};
let (operator, _receiver) = Operator::new(config);
assert_eq!(operator.config.namespace, "default");
}
#[tokio::test]
async fn test_create_cluster() {
let config: _ = OperatorConfig {
namespace: "default".to_string(),
reconcile_interval: std::time::Duration::from_secs(30),
max_retries: 3,
};
let (operator, _receiver) = Operator::new(config);
let cluster: _ = AmberCluster {
api_version: "v1".to_string(),
kind: "AmberCluster".to_string(),
metadata: ObjectMeta {
name: "test-cluster".to_string(),
namespace: "default".to_string(),
labels: None,
},
spec: AmberClusterSpec {
replicas: 3,
version: "v0.1.0".to_string(),
image: None,
resources: ResourceRequirements {
cpu: Some("100m".to_string()),
memory: Some("128Mi".to_string()),
storage: None,
},
networking: NetworkingConfig {
service_type: ServiceType::ClusterIP,
port: 8080,
ingress: None,
},
},
status: None,
};
let result: _ = operator.create_cluster(cluster).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_list_clusters() {
let config: _ = OperatorConfig {
namespace: "default".to_string(),
reconcile_interval: std::time::Duration::from_secs(30),
max_retries: 3,
};
let (operator, _receiver) = Operator::new(config);
let clusters: _ = operator.list_clusters().await.unwrap();
assert_eq!(clusters.len(), 0);
let cluster: _ = AmberCluster {
api_version: "v1".to_string(),
kind: "AmberCluster".to_string(),
metadata: ObjectMeta {
name: "test-cluster".to_string(),
namespace: "default".to_string(),
labels: None,
},
spec: AmberClusterSpec {
replicas: 1,
version: "v0.1.0".to_string(),
image: None,
resources: ResourceRequirements {
cpu: Some("100m".to_string()),
memory: Some("128Mi".to_string()),
storage: None,
},
networking: NetworkingConfig {
service_type: ServiceType::ClusterIP,
port: 8080,
ingress: None,
},
},
status: None,
};
operator.create_cluster(cluster).await.unwrap();
let clusters: _ = operator.list_clusters().await.unwrap();
assert_eq!(clusters.len(), 1);
assert_eq!(clusters[0].metadata.name, "test-cluster");
}
}