diff --git a/.gitignore b/.gitignore index cdd859bf2df40f538f0d89435c411a596676910f..a2c285434996504f9e3236a9d122a65b1ec41004 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,13 @@ debug/ target/ .idea/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# Remove Cargo.toml from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock +.cargo/ # These are backup files generated by rustfmt **/*.rs.bk database.sqlite -test-database.sqlite \ No newline at end of file +test-database.sqlite +storage-metadata.db \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8fa347d0276731f52813630b55c7eff98b80b80c..929cac6ad0f32ad1450ecef2d0cd789fe0780668 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,15 +10,14 @@ keywords = ["apiserver", "kubernetes", "k8s", "fleet"] repository = "https://gitee.com/iscas-system/apiserver" readme = "README.md" +[[bin]] +name = "example" +path = "examples/example/src/main.rs" + #[[bin]] #name = "event-client-example" #path = "examples/event_client_example/src/main.rs" -#[[bin]] -#name = "reflector-example" -#path = "examples/reflector_example/src/main.rs" -# - #[[bin]] #name = "custom-route-example" #path = "examples/custom_route_example/src/main.rs" @@ -29,36 +28,33 @@ readme = "README.md" [dependencies] #feventbus = "0.3.0" -feventbus = { git = "https://gitee.com/iscas-system/eventbus.git"} +#feventbus = { path = "../eventbus" } +feventbus = { git = "https://gitee.com/iscas-system/eventbus.git" } fleetmod = { git = "https://gitee.com/iscas-system/fleetmod.git" } -client-rust = { git = "https://gitee.com/iscas-system/client-rust.git"} +#fleetmod = { path = "../fleetmod" } r2d2 = "0.8.10" -dotenv = "0.15.0" diesel = { version = "2.2.0", features = ["sqlite", "postgres", "r2d2"] } diesel_migrations = "2.2.0" actix-http = "3.9.0" -actix-service = "2.0.2" actix-web = "4.9.0" async-trait = "0.1.74" env_logger = "0.11.5" serde = { version = "1.0.209", features = ["derive"] } serde_json = "1.0.127" tokio = { version = "1.0", features = ["full", "test-util", "macros"] } -k8s-openapi = { version = "0.23.0", features = ["latest"] } -jsonschema = "0.23.0" -schemars = "0.8.21" chrono = "0.4.38" -once_cell = "1.20.2" -lazy_static = "1.5.0" -async-stream = "0.3.6" serial_test = "0.10.0" reqwest = "0.12.9" log = "0.4.22" -itertools = "0.13.0" anyhow = "1.0.94" json-patch = "3.0.1" strum = { version = "0.26.3", features = ["derive"] } -serde_yaml = "0.9.34+deprecated" futures = "0.3.31" -uuid = { version = "1.11.1", features = ["v4"] } +uuid = { version = "1.13.1", features = ["v4"] } dirs = "6.0.0" +paperclip = { version = "0.9.4", features = ["actix4", "swagger-ui"] } +tokio-stream = "0.1.17" +enum-as-inner = "0.6.1" +bon = "3.3.2" +derive_more = "0.99.18" +serde_yaml = "0.9.34" diff --git a/examples/custom_route_example/src/main.rs b/examples/custom_route_example/src/main.rs deleted file mode 100644 index 7fcb8c550a6e5bb882dd040427a28b43aeac390b..0000000000000000000000000000000000000000 --- a/examples/custom_route_example/src/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::sync::Arc; -use env_logger::{Builder, Target}; -use fleet_apiserver::cores::plugin::PluginManager; -use fleet_apiserver::custom_route::custom_example; -use fleet_apiserver::cluster_info_route::cluster_info; -const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; -const ADDRESS: &str = "0.0.0.0:8080"; - -fn setup_logger() { - let mut builder = Builder::from_default_env(); - builder.target(Target::Stdout); - builder.init(); -} - -#[tokio::main] -async fn main() { - setup_logger(); - - let plugin_manager = Arc::new(PluginManager::new()); - - custom_example::init_plugin(plugin_manager.clone()); - cluster_info::init_plugin(plugin_manager.clone()); - - if let Err(e) = fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await { - eprintln!("Failed to start server: {}", e); - } -} \ No newline at end of file diff --git a/examples/event_client_example/src/main.rs b/examples/event_client_example/src/main.rs index b92a2235776a96c5a3ef987e26d0f6d2e25bdb53..45824ed2bf4bd78b84ae54162546776e48dcb630 100644 --- a/examples/event_client_example/src/main.rs +++ b/examples/event_client_example/src/main.rs @@ -1,329 +1,329 @@ -use std::sync::Arc; -use std::sync::atomic::AtomicUsize; -use std::time; -use client_rust::event_client::{EventClient, EventTopic, PubSubEventTopic, ResourceCollectionIdentifier, ResourceDefinitionBuilder, WatchEventMessageResource}; -use client_rust::result::ServerResult; -use env_logger::{Builder, Target}; -use feventbus::impls::messaging::messaging::Messaging; -use feventbus::traits::controller::EventBus; -use fleetmod::pod::Pod; -use fleetmod::utils::APIVersion; -use serde_json::{Value}; -use tokio::time::{sleep, timeout}; -use fleet_apiserver::cores::plugin::PluginManager; -use fleet_apiserver::custom_route::custom_example; - -const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; -const ADDRESS: &str = "localhost:8000"; - -fn setup_logger() { - let mut builder = Builder::from_default_env(); - builder.target(Target::Stdout); - builder.init(); -} - +// use std::sync::Arc; +// use std::sync::atomic::AtomicUsize; +// use std::time; +// use client_rust::event_client::{EventClient, EventTopic, PubSubEventTopic, ResourceCollectionIdentifier, ResourceDefinitionBuilder, WatchEventMessageResource}; +// use client_rust::result::ServerResult; +// use env_logger::{Builder, Target}; +// use feventbus::impls::messaging::messaging::Messaging; +// use feventbus::traits::controller::EventBus; +// use fleetmod::pod::Pod; +// use fleetmod::utils::APIVersion; +// use serde_json::{Value}; +// use tokio::time::{sleep, timeout}; +// use fleet_apiserver::cores::plugin::PluginManager; +// use fleet_apiserver::custom_route::custom_example; +// +// const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; +// const ADDRESS: &str = "localhost:8000"; +// +// fn setup_logger() { +// let mut builder = Builder::from_default_env(); +// builder.target(Target::Stdout); +// builder.init(); +// } +// #[tokio::main] async fn main() { - setup_logger(); - - let plugin_manager = Arc::new(PluginManager::new()); - - custom_example::init_plugin(plugin_manager.clone()); - - - // 启动apiserver - tokio::spawn(async move { - fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await.unwrap(); - }); - - // 等待服务器启动 todo! 未来添加阻塞等待服务启动完毕的方法 - tokio::time::sleep(time::Duration::from_secs(5)).await; - - // test_p2p().await; - test_watch().await; -} - -#[allow(dead_code)] -async fn test_p2p() { - // 新建NatsClient - let nats_client = Messaging::new().await.unwrap(); - // 新建EventClient. 30s表示客户端设置的超时时间 - let event_cli = EventClient::new(Arc::new(nats_client), Some(time::Duration::from_secs(30))); - - let pod_name = "test-create-pod".to_string(); - let namespace = "ns1".to_string(); - let version = "v1".to_string(); - let plural = "pods".to_string(); - let kind = "Pod".to_string(); - // 创建测试用pod - let pod = mock_pod(pod_name.clone(), Some(namespace.clone())); - - // with_name_params: 相当于url: /api/v1/namespaces/ns1/pods/test-create-pod - let with_name_params = ResourceDefinitionBuilder::new() - .namespace(namespace.clone()) - .version(version.clone()) - .kind(kind.clone()) // .plural(plural.clone()) 可以传入plural或kind,选一个即可 - .cluster_id("1234".to_string()) - .name(pod_name.clone()).build().unwrap(); - - // without_name_params: 相当于url: /api/v1/pods - let without_name_params = ResourceDefinitionBuilder::new() - .version(version.clone()) - .plural(plural.clone()) // .kind(kind.clone()) 可以传入plural或kind,选一个即可 - .cluster_id("1234".to_string()) - .build().unwrap(); - - // APIServerServiceParamsBuilder 必须传入的参数有version,plural或kind,其他参数name,namespace,group可选 - - // 在创建前,先删除可能存在的pod - let res: ServerResult = event_cli.delete(with_name_params.clone()).await; - log::info!("---delete before create result: {:?}---", res); - - // 创建pod - let res = event_cli - .create_by_resource(pod.clone()) - .await; - // 也可以用下面的方式创建,但是需要传入without_name_params - // let res = event_cli - // .create(without_name_params, pod.clone()) - // .await; - log::info!("---create by resource result: {:?}---", res); - assert_eq!(pod, res.ok().unwrap()); - - // 测试获取单个pod:因为需要返回单个列表,所以需要指定具体pod名称,因此传入with_name_params - let res = event_cli - .get(with_name_params.clone(), Value::Null) // 第二个参数为Value类型的query,后续可支持分页等查询条件,目前传空即可 - .await; - log::info!("---get one pod result: {:?}---", res); - assert!(res.is_ok()); - assert_eq!(pod, res.ok().unwrap()); - - // 测试获取pod列表:因为需要返回列表,所以不要指定具体的pod名称,因此传入without_name_params - let res = event_cli - .list(without_name_params.clone(), Value::Null) // 第二个参数为Value类型的query,后续可支持分页等查询条件,目前传空即可 - .await; - log::info!("---list pods result {:?}---", res); - assert!(res.is_ok()); - let pods_from_list: Vec = res.ok().unwrap(); - let found = pods_from_list.iter().any(|p| p.metadata.name == pod_name); - assert!(found); - let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == pod_name).unwrap(); - assert_eq!(pod, *pod_from_list); - - // 测试patch功能:因为需要指定具体的pod名称,所以传with_name_params - let mut pod_after_pad_should_be = pod.clone(); - pod_after_pad_should_be.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); - pod_after_pad_should_be.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); - let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(pod_after_pad_should_be.clone()).unwrap()); - let patch = serde_json::to_value(&diff).unwrap(); - let res = event_cli - .patch(with_name_params.clone(), patch).await; - log::info!("---patch result: {:?}---", res); - assert!(res.is_ok()); - assert_eq!(pod_after_pad_should_be, res.ok().unwrap()); - - // 测试删除功能:因为需要指定具体的pod名称,所以传with_name_params - let res = event_cli - .delete(with_name_params.clone()) - .await; - log::info!("---delete result: {:?}---", res); - assert_eq!(pod_after_pad_should_be, res.ok().unwrap()); - - // 测试是否删除成功 - let res: ServerResult = event_cli - .get(with_name_params.clone(), Value::Null) - .await; - log::info!("---get result after deletion: {:?}---", res); - assert!(res.is_err()); - // assert_eq!(res.err().unwrap().status_code, APIServerStatusCode::NotFound); -} -#[allow(dead_code)] -async fn test_watch() { - let name = "test-create-pod".to_string(); - let namespace = "ns1".to_string(); - let version = "v1".to_string(); - let plural = "pods".to_string(); - let kind = "Pod".to_string(); - let pod: Pod = mock_pod(name.clone(), Some(namespace.clone())); - - let with_name_params = ResourceDefinitionBuilder::new() - .name(name.clone()) - .version(version.clone()) - .namespace(namespace.clone()) - .cluster_id("1234".to_string()) - .plural(plural.clone()).build().unwrap(); - - let ri = ResourceCollectionIdentifier { - api_version: APIVersion { group: None, version: version.to_string() }, - kind: kind.to_string(), - cluster_id: "1234".to_string(), - namespace: Some(namespace.clone()), - }; - - let ri_topic: EventTopic = EventTopic::PubSub(PubSubEventTopic::Watch(ri.clone())); - let topic_str = ri_topic.to_string(); - let got_watch_msg_cnt = Arc::new(AtomicUsize::new(0)); - let got_watch_msg_cnt_cloned = got_watch_msg_cnt.clone(); - - let nats_cli = Arc::new(Messaging::new().await.unwrap()); - let event_cli = EventClient::new(nats_cli.clone(), None); - - // 在进行watch之前先删除,保证资源不存在 - let res: ServerResult = event_cli.delete(with_name_params.clone()).await; - log::info!("--event cli delete res {:?}---", res.is_ok()); - - log::info!("watch event subscribing {}", topic_str); - let mut watch_value_receiver = event_cli.watch::(ri.clone()).await.unwrap(); - - // 等待subscribe订阅成功 - sleep(time::Duration::from_secs(1)).await; - log::info!("watch event subscribed"); - tokio::spawn(async move { - loop { - while let Some(resource) = watch_value_receiver.recv().await { - match resource { - WatchEventMessageResource::Created(pod) => { - log::info!("watched created pod {}-{}!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); - } - WatchEventMessageResource::Updated(pod) => { - log::info!("watched updated pod {}-{}!!!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); - } - WatchEventMessageResource::Deleted(pod) => { - log::info!("watched deleted pod {}-{}!!!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); - } - } - got_watch_msg_cnt_cloned.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - if got_watch_msg_cnt_cloned.load(std::sync::atomic::Ordering::SeqCst) >= 3 { - break; - } - } - } - }); - - // 等待watcher启动 - sleep(time::Duration::from_secs(1)).await; - - // do create - let res = event_cli.create_by_resource(pod.clone()).await; - log::info!("event cli create res {:?}", res.is_ok()); - assert_eq!(pod, res.ok().unwrap()); - - // do patch - let mut target_pod = pod.clone(); - target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); - target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); - let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(target_pod.clone()).unwrap()); - let patch = serde_json::to_value(&diff).unwrap(); - let res = event_cli.patch(with_name_params.clone(), patch).await; - log::info!("event cli patch res {:?}", res.is_ok()); - assert!(res.is_ok()); - assert_eq!(target_pod, res.ok().unwrap()); - - // do delete - let res = event_cli.delete(with_name_params.clone()).await; - log::info!("event cli delete res {:?}", res.is_ok()); - assert_eq!(target_pod, res.ok().unwrap()); - - // 等待直到收到3个watch消息才算成功 - let max_wait_secs = 20; - timeout(time::Duration::from_secs(max_wait_secs), async { - loop { - let got_watch_msg_cnt = got_watch_msg_cnt.load(std::sync::atomic::Ordering::SeqCst); - if got_watch_msg_cnt >= 3 { - break; - } - sleep(time::Duration::from_secs(1)).await; - } - }).await.unwrap(); - log::info!("watch test done"); +// setup_logger(); +// +// let plugin_manager = Arc::new(PluginManager::new()); +// +// custom_example::init_plugin(plugin_manager.clone()); +// +// +// // 启动apiserver +// tokio::spawn(async move { +// fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await.unwrap(); +// }); +// +// // 等待服务器启动 todo! 未来添加阻塞等待服务启动完毕的方法 +// tokio::time::sleep(time::Duration::from_secs(5)).await; +// +// // test_p2p().await; +// test_watch().await; } - -fn mock_pod(name: String, namespace: Option) -> Pod { - let pod_yaml = r#" -apiVersion: v1 -kind: Pod -metadata: - name: example-pod - namespace: default - labels: - app: my-app - creationTimestamp: 2024-12-10T08:00:00+08:00 -spec: - nodeName: my-node - hostname: my-hostname - hostAliases: - - ip: "127.0.0.1" - hostnames: - - "my-local-host" - - "another-host" - containers: - - name: example-container - image: example-image:latest - imagePullPolicy: IfNotPresent - command: ["nginx"] - args: ["-g", "daemon off;"] - workingDir: /usr/share/nginx/html - ports: - - name: http - containerPort: 80 - protocol: TCP - - name: https - containerPort: 443 - protocol: TCP - env: - - name: ENV_MODE - value: production - - name: ENV_VERSION - valueFrom: - fieldRef: - fieldPath: metadata.name - resources: - requests: - memory: "128Mi" - cpu: "250m" - limits: - memory: "512Mi" - cpu: "1" - volumeMounts: - - name: config-volume - mountPath: /etc/nginx/conf.d - readOnly: true - - name: data-volume - mountPath: /usr/share/nginx/html - - name: data-host-volume - mountPath: /usr/share/nginx/a.txt - volumeDevices: - - name: device-volume - devicePath: /dev/sdb - securityContext: - runAsUser: 1000 - runAsGroup: 1000 - readOnlyRootFilesystem: true - allowPrivilegeEscalation: true - privileged: true - volumes: - - name: example-volume - configMap: - name: nginx-config - - name: data-volume - emptyDir: {} - - name: device-volume - hostPath: - path: /dev/sdb - type: Directory - - name: device-volume - hostPath: - path: /dev/sdb - type: Directory -status: - phase: Pending - message: begin handle - podIP: 10.42.0.9 - podIPs: - - ip: 10.42.0.9 -"#; - let mut pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML"); - pod.metadata.name = name.to_string(); - pod.metadata.namespace = namespace.map(|s| s.to_string()); - pod -} \ No newline at end of file +// +// #[allow(dead_code)] +// async fn test_p2p() { +// // 新建NatsClient +// let nats_client = Messaging::new().await.unwrap(); +// // 新建EventClient. 30s表示客户端设置的超时时间 +// let event_cli = EventClient::new(Arc::new(nats_client), Some(time::Duration::from_secs(30))); +// +// let pod_name = "test-create-pod".to_string(); +// let namespace = "ns1".to_string(); +// let version = "v1".to_string(); +// let plural = "pods".to_string(); +// let kind = "Pod".to_string(); +// // 创建测试用pod +// let pod = mock_pod(pod_name.clone(), Some(namespace.clone())); +// +// // with_name_params: 相当于url: /api/v1/namespaces/ns1/pods/test-create-pod +// let with_name_params = ResourceDefinitionBuilder::new() +// .namespace(namespace.clone()) +// .version(version.clone()) +// .kind(kind.clone()) // .plural(plural.clone()) 可以传入plural或kind,选一个即可 +// .cluster_id("1234".to_string()) +// .name(pod_name.clone()).build().unwrap(); +// +// // without_name_params: 相当于url: /api/v1/pods +// let without_name_params = ResourceDefinitionBuilder::new() +// .version(version.clone()) +// .plural(plural.clone()) // .kind(kind.clone()) 可以传入plural或kind,选一个即可 +// .cluster_id("1234".to_string()) +// .build().unwrap(); +// +// // APIServerServiceParamsBuilder 必须传入的参数有version,plural或kind,其他参数name,namespace,group可选 +// +// // 在创建前,先删除可能存在的pod +// let res: ServerResult = event_cli.delete(with_name_params.clone()).await; +// log::info!("---delete before create result: {:?}---", res); +// +// // 创建pod +// let res = event_cli +// .create_by_resource(pod.clone()) +// .await; +// // 也可以用下面的方式创建,但是需要传入without_name_params +// // let res = event_cli +// // .create(without_name_params, pod.clone()) +// // .await; +// log::info!("---create by resource result: {:?}---", res); +// assert_eq!(pod, res.ok().unwrap()); +// +// // 测试获取单个pod:因为需要返回单个列表,所以需要指定具体pod名称,因此传入with_name_params +// let res = event_cli +// .get(with_name_params.clone(), Value::Null) // 第二个参数为Value类型的query,后续可支持分页等查询条件,目前传空即可 +// .await; +// log::info!("---get one pod result: {:?}---", res); +// assert!(res.is_ok()); +// assert_eq!(pod, res.ok().unwrap()); +// +// // 测试获取pod列表:因为需要返回列表,所以不要指定具体的pod名称,因此传入without_name_params +// let res = event_cli +// .list(without_name_params.clone(), Value::Null) // 第二个参数为Value类型的query,后续可支持分页等查询条件,目前传空即可 +// .await; +// log::info!("---list pods result {:?}---", res); +// assert!(res.is_ok()); +// let pods_from_list: Vec = res.ok().unwrap(); +// let found = pods_from_list.iter().any(|p| p.metadata.name == pod_name); +// assert!(found); +// let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == pod_name).unwrap(); +// assert_eq!(pod, *pod_from_list); +// +// // 测试patch功能:因为需要指定具体的pod名称,所以传with_name_params +// let mut pod_after_pad_should_be = pod.clone(); +// pod_after_pad_should_be.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); +// pod_after_pad_should_be.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); +// let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(pod_after_pad_should_be.clone()).unwrap()); +// let patch = serde_json::to_value(&diff).unwrap(); +// let res = event_cli +// .patch(with_name_params.clone(), patch).await; +// log::info!("---patch result: {:?}---", res); +// assert!(res.is_ok()); +// assert_eq!(pod_after_pad_should_be, res.ok().unwrap()); +// +// // 测试删除功能:因为需要指定具体的pod名称,所以传with_name_params +// let res = event_cli +// .delete(with_name_params.clone()) +// .await; +// log::info!("---delete result: {:?}---", res); +// assert_eq!(pod_after_pad_should_be, res.ok().unwrap()); +// +// // 测试是否删除成功 +// let res: ServerResult = event_cli +// .get(with_name_params.clone(), Value::Null) +// .await; +// log::info!("---get result after deletion: {:?}---", res); +// assert!(res.is_err()); +// // assert_eq!(res.err().unwrap().status_code, APIServerStatusCode::NotFound); +// } +// #[allow(dead_code)] +// async fn test_watch() { +// let name = "test-create-pod".to_string(); +// let namespace = "ns1".to_string(); +// let version = "v1".to_string(); +// let plural = "pods".to_string(); +// let kind = "Pod".to_string(); +// let pod: Pod = mock_pod(name.clone(), Some(namespace.clone())); +// +// let with_name_params = ResourceDefinitionBuilder::new() +// .name(name.clone()) +// .version(version.clone()) +// .namespace(namespace.clone()) +// .cluster_id("1234".to_string()) +// .plural(plural.clone()).build().unwrap(); +// +// let ri = ResourceCollectionIdentifier { +// api_version: APIVersion { group: None, version: version.to_string() }, +// kind: kind.to_string(), +// cluster_id: "1234".to_string(), +// namespace: Some(namespace.clone()), +// }; +// +// let ri_topic: EventTopic = EventTopic::PubSub(PubSubEventTopic::Watch(ri.clone())); +// let topic_str = ri_topic.to_string(); +// let got_watch_msg_cnt = Arc::new(AtomicUsize::new(0)); +// let got_watch_msg_cnt_cloned = got_watch_msg_cnt.clone(); +// +// let nats_cli = Arc::new(Messaging::new().await.unwrap()); +// let event_cli = EventClient::new(nats_cli.clone(), None); +// +// // 在进行watch之前先删除,保证资源不存在 +// let res: ServerResult = event_cli.delete(with_name_params.clone()).await; +// log::info!("--event cli delete res {:?}---", res.is_ok()); +// +// log::info!("watch event subscribing {}", topic_str); +// let mut watch_value_receiver = event_cli.watch::(ri.clone()).await.unwrap(); +// +// // 等待subscribe订阅成功 +// sleep(time::Duration::from_secs(1)).await; +// log::info!("watch event subscribed"); +// tokio::spawn(async move { +// loop { +// while let Some(resource) = watch_value_receiver.recv().await { +// match resource { +// WatchEventMessageResource::Created(pod) => { +// log::info!("watched created pod {}-{}!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); +// } +// WatchEventMessageResource::Updated(pod) => { +// log::info!("watched updated pod {}-{}!!!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); +// } +// WatchEventMessageResource::Deleted(pod) => { +// log::info!("watched deleted pod {}-{}!!!!!!!!!!!",pod.metadata.name.clone(),pod.metadata.namespace.clone().unwrap()); +// } +// } +// got_watch_msg_cnt_cloned.fetch_add(1, std::sync::atomic::Ordering::SeqCst); +// if got_watch_msg_cnt_cloned.load(std::sync::atomic::Ordering::SeqCst) >= 3 { +// break; +// } +// } +// } +// }); +// +// // 等待watcher启动 +// sleep(time::Duration::from_secs(1)).await; +// +// // do create +// let res = event_cli.create_by_resource(pod.clone()).await; +// log::info!("event cli create res {:?}", res.is_ok()); +// assert_eq!(pod, res.ok().unwrap()); +// +// // do patch +// let mut target_pod = pod.clone(); +// target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); +// target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); +// let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(target_pod.clone()).unwrap()); +// let patch = serde_json::to_value(&diff).unwrap(); +// let res = event_cli.patch(with_name_params.clone(), patch).await; +// log::info!("event cli patch res {:?}", res.is_ok()); +// assert!(res.is_ok()); +// assert_eq!(target_pod, res.ok().unwrap()); +// +// // do delete +// let res = event_cli.delete(with_name_params.clone()).await; +// log::info!("event cli delete res {:?}", res.is_ok()); +// assert_eq!(target_pod, res.ok().unwrap()); +// +// // 等待直到收到3个watch消息才算成功 +// let max_wait_secs = 20; +// timeout(time::Duration::from_secs(max_wait_secs), async { +// loop { +// let got_watch_msg_cnt = got_watch_msg_cnt.load(std::sync::atomic::Ordering::SeqCst); +// if got_watch_msg_cnt >= 3 { +// break; +// } +// sleep(time::Duration::from_secs(1)).await; +// } +// }).await.unwrap(); +// log::info!("watch test done"); +// } +// +// fn mock_pod(name: String, namespace: Option) -> Pod { +// let pod_yaml = r#" +// apiVersion: v1 +// kind: Pod +// metadata: +// name: example-pod +// namespace: default +// labels: +// app: my-app +// creationTimestamp: 2024-12-10T08:00:00+08:00 +// spec: +// nodeName: my-node +// hostname: my-hostname +// hostAliases: +// - ip: "127.0.0.1" +// hostnames: +// - "my-local-host" +// - "another-host" +// containers: +// - name: example-container +// image: example-image:latest +// imagePullPolicy: IfNotPresent +// command: ["nginx"] +// args: ["-g", "daemon off;"] +// workingDir: /usr/share/nginx/html +// ports: +// - name: http +// containerPort: 80 +// protocol: TCP +// - name: https +// containerPort: 443 +// protocol: TCP +// env: +// - name: ENV_MODE +// value: production +// - name: ENV_VERSION +// valueFrom: +// fieldRef: +// fieldPath: metadata.name +// resources: +// requests: +// memory: "128Mi" +// cpu: "250m" +// limits: +// memory: "512Mi" +// cpu: "1" +// volumeMounts: +// - name: config-volume +// mountPath: /etc/nginx/conf.d +// readOnly: true +// - name: data-volume +// mountPath: /usr/share/nginx/html +// - name: data-host-volume +// mountPath: /usr/share/nginx/a.txt +// volumeDevices: +// - name: device-volume +// devicePath: /dev/sdb +// securityContext: +// runAsUser: 1000 +// runAsGroup: 1000 +// readOnlyRootFilesystem: true +// allowPrivilegeEscalation: true +// privileged: true +// volumes: +// - name: example-volume +// configMap: +// name: nginx-config +// - name: data-volume +// emptyDir: {} +// - name: device-volume +// hostPath: +// path: /dev/sdb +// type: Directory +// - name: device-volume +// hostPath: +// path: /dev/sdb +// type: Directory +// status: +// phase: Pending +// message: begin handle +// podIP: 10.42.0.9 +// podIPs: +// - ip: 10.42.0.9 +// "#; +// let mut pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML"); +// pod.metadata.name = name.to_string(); +// pod.metadata.namespace = namespace.map(|s| s.to_string()); +// pod +// } \ No newline at end of file diff --git a/examples/custom_route_example/Cargo.toml b/examples/example/Cargo.toml similarity index 75% rename from examples/custom_route_example/Cargo.toml rename to examples/example/Cargo.toml index 20909d471559e9eb7178401ccd353b30aad09739..7deb3e58c6456c17c96d02358c16d6db4c2a2897 100644 --- a/examples/custom_route_example/Cargo.toml +++ b/examples/example/Cargo.toml @@ -1,7 +1,7 @@ -[package] -name = "custom_route_example" -version = "0.1.0" -edition = "2021" - -[dependencies] -fleet_apiserver = { path = "../../../apiserver" } +[package] +name = "example" +version = "0.1.0" +edition = "2021" + +[dependencies] +fleet_apiserver = { path = "../../../apiserver" } diff --git a/examples/example/src/main.rs b/examples/example/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..75d9e41062d69a7e610e27de8549d31149c23f9c --- /dev/null +++ b/examples/example/src/main.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; +use env_logger::{Builder, Target}; +use fleet_apiserver::prepare_app_state; + +const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; +const ADDRESS: &str = "0.0.0.0:38080"; + +#[tokio::main] +async fn main() { + let mut builder = Builder::from_default_env(); + builder.target(Target::Stdout); + let _ = builder.try_init(); + + let msg_cli = Arc::new(fleet_apiserver::utils::test::setup_message_cli().await); + let msg_cli_moved = msg_cli.clone(); + let app_state = prepare_app_state(DATABASE_URL, msg_cli_moved).await.unwrap(); + let app_state_moved = app_state.clone(); + tokio::join!(async move { + fleet_apiserver::utils::test::start_test_api_server(app_state_moved, ADDRESS).await; + }); + // waiting for ctrl-c signal + tokio::signal::ctrl_c().await.unwrap(); + fleet_apiserver::utils::test::tear_down_message_cli(msg_cli) +} \ No newline at end of file diff --git a/src/cluster_info_route/cluster_info.rs b/src/cluster_info_route/cluster_info.rs deleted file mode 100644 index 8848974577d1bbbac8d15b4765c7950b035d36ef..0000000000000000000000000000000000000000 --- a/src/cluster_info_route/cluster_info.rs +++ /dev/null @@ -1,60 +0,0 @@ -use serde_json::{json}; -use std::sync::Arc; -use crate::cores::apiserver::{CustomRouteProvider, APIServerRoute, AppState}; -use crate::cores::plugin::PluginManager; -use actix_web::{web, HttpResponse, Route}; -use actix_web::http::Method; - - -pub struct ClusterInfoRoute { - pub method: Method, - pub path: String, - pub handler: fn(app_state: Arc) -> HttpResponse, -} - -impl APIServerRoute for ClusterInfoRoute { - fn get_method(&self) -> Method { - self.method.clone() - } - - fn get_path(&self) -> String { - self.path.clone() - } - - fn get_web_route(&self) -> Route { - let handler = self.handler; - web::route() - .method(self.method.clone()) - .to(move |state: web::Data| async move { - handler(state.into_inner()) - }) - } -} - - -pub struct ClusterInfoRouteProvider; - -impl CustomRouteProvider for ClusterInfoRouteProvider { - fn get_routes(&self) -> Vec> { - vec![ - Box::new(ClusterInfoRoute { - method: Method::GET, - path: "/cluster/info".to_string(), - handler: get_info_handler, - }), - ] - } -} - - -fn get_info_handler(app_state: Arc) -> HttpResponse { - HttpResponse::Ok().json(json!({ - "status_code": "OK", - "message": "GET cluster info", - "data": app_state.cluster_id - })) -} - -pub fn init_plugin(plugin_manager: Arc) { - plugin_manager.register(Arc::new(ClusterInfoRouteProvider {})); -} diff --git a/src/cluster_info_route/mod.rs b/src/cluster_info_route/mod.rs deleted file mode 100644 index da9486a444f563227e12c0b00097c398aefe60ed..0000000000000000000000000000000000000000 --- a/src/cluster_info_route/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod cluster_info; \ No newline at end of file diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs deleted file mode 100644 index 7ba17e3cd635952ef94d9eb2f14b5d680ac6d43c..0000000000000000000000000000000000000000 --- a/src/cores/apiserver.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::cores::handlers::{APIServerResponse, Handler}; -use crate::cores::services::{APIServerError, APIServerResult}; -use crate::db::db::DbPool; -use actix_web::dev::Server; -use actix_web::web::route; -use actix_web::{http, web, App, Error, HttpResponse, HttpServer}; -use feventbus::impls::messaging::messaging::Messaging; -use futures::StreamExt; -use http::Method; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::sync::Arc; -use client_rust::event_client::WatchEventPublisher; - -pub struct APIServer {} - -#[derive(Clone)] -pub struct AppState { - pub db_pool: Arc, - pub nats_cli: Arc, - pub handler: Arc, - pub watch_event_publisher: Arc, - pub cluster_id: String -} - -impl APIServer { - pub fn new() -> Self { - APIServer {} - } - - pub fn start(&self, addr: &str, app_state: AppState, custom_route_providers: Arc>>) -> Server { - let server = HttpServer::new(move || { - let app = App::new() - .app_data(web::Data::new(app_state.clone())); - let routes = K8sStyleRoute::get_routes(); - let app = routes.iter().fold(app, |app, route| { - log::info!("register route: {} {}", route.get_method(), route.get_path()); - app.route(route.get_path().as_str(), route.get_web_route()) - }); - - // 动态注册自定义路由 - let app = custom_route_providers.iter().fold(app, |app, provider| { - provider.get_routes().into_iter().fold(app, |app, route| { - log::info!("register custom route: {} {}", route.get_method(), route.get_path()); - app.route(route.get_path().as_str(), route.get_web_route()) - }) - }); - - let app = app.default_service(web::to(move || async { - Ok::( - HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) - })); - app - }); - // 考虑到资源约束,目前只启动一个worker - // TODO 未来用户可以自定义该配置worker数 - server.workers(1).bind(addr).expect("reason").run() - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct K8sStylePathParams { - pub group: Option, - pub version: String, - pub namespace: Option, - pub plural: String, - pub name: Option, -} - -pub trait APIServerRoute { - fn get_method(&self) -> Method; - fn get_path(&self) -> String; - fn get_web_route(&self) -> actix_web::Route; -} - -pub struct K8sStyleRoute { - pub method: Method, - pub with_group: bool, - pub with_namespace: bool, - pub with_name: bool, -} - -impl APIServerRoute for K8sStyleRoute { - fn get_method(&self) -> Method { - self.method.clone() - } - - // apis/{group}/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] - // api/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] - fn get_path(&self) -> String { - let prefix = if self.with_group { "apis/{group}/" } else { "api/" }; - let version = "{version}/"; - let namespace = if self.with_namespace { "namespaces/{namespace}/" } else { "" }; - let plural_and_name = if self.with_name { "{plural}/{name}" } else { "{plural}" }; - format!("{}{}{}{}", prefix, version, namespace, plural_and_name) - } - - fn get_web_route(&self) -> actix_web::Route { - route().method(self.method.clone()).to(Self::handler) - } -} - -impl K8sStyleRoute { - pub fn get_routes() -> Vec> { - itertools::iproduct!(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::PATCH], vec![true, false], vec![true, false], vec![true, false]) - .map(|(method, with_group, with_namespace, with_name)| { - Box::new(Self { method, with_group, with_namespace, with_name }) as Box - }).collect() - } - - async fn parse_body(mut payload: web::Payload) -> APIServerResult { - // payload is a stream of Bytes objects - let mut body = web::BytesMut::new(); - let parse_chunk_error = APIServerError::bad_request("parse payload chunk error"); - while let Some(chunk) = payload.next().await { - let chunk = chunk; - if chunk.is_err() { - return Err(parse_chunk_error); - } - let chunk = chunk.unwrap(); - // limit max size of in-memory payload - if (body.len() + chunk.len()) > 262_144 { - return Err(parse_chunk_error); - } - body.extend_from_slice(&chunk); - } - - let body = serde_json::from_slice::(&body); - if body.is_err() { - return Err(APIServerError::bad_request("payload is not a valid json")); - } - Ok(body.unwrap()) - } - - async fn handler(method: Method, query: web::Query, path_params: web::Path, state: web::Data, payload: web::Payload) - -> HttpResponse { - let body = Self::parse_body(payload).await; - log::info!("ApiServerHandler: method: {:?}, path_params: {:?}", method, path_params); - let handler = state.handler.clone(); - match method { - Method::POST | Method::PUT | Method::PATCH => { - if body.is_err() { - return HttpResponse::from(APIServerResponse::from(body)); - } - } - _ => {} - } - match method { - Method::GET => { - handler.get_resource(path_params.into_inner(), query.into_inner(), state.into_inner()).await - } - Method::POST => { - handler.create_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await - } - Method::PUT => { - handler.update_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await - } - Method::DELETE => { - handler.delete_resource(path_params.into_inner(), state.into_inner()).await - } - Method::PATCH => { - handler.patch_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await - } - _ => { - HttpResponse::from(APIServerResponse::from(Err::(APIServerError::bad_request("unsupported method")))) - } - } - } -} - -pub trait CustomRouteProvider: Send + Sync { - fn get_routes(&self) -> Vec>; -} \ No newline at end of file diff --git a/src/cores/checker.rs b/src/cores/checker.rs deleted file mode 100644 index 7dff4b32a2ca0413b7eb042e07bc536def37e525..0000000000000000000000000000000000000000 --- a/src/cores/checker.rs +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use jsonschema::Validator; - - -use actix_web::Result; -use serde_json::Value; -use std::error::Error; -use std::fs::File; -use std::io::Read; - -// TODO,未来能自动从openapi中获取Schema -pub fn validate_pod_json(json_str: &str) -> Result> { - let mut file = File::open("schemas/pod-schema.json")?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - // 解析 JSON 数据 - let pod_schema: Value = serde_json::from_str(&contents).expect("fail to parse JSON."); - - // let schema = serde_json::to_string_pretty(&pod_schema).unwrap(); - - // let schema: Value = serde_json::from_str(pod_schema)?; - - let compiled_schema = Validator::new(&pod_schema)?; - - let json: Value = serde_json::from_str(json_str)?; - - if compiled_schema.is_valid(&json) { - Ok(true) - } else { - // 如果验证失败,打印错误信息 - let errors: Vec<_> = compiled_schema - .validate(&json) - .expect_err("expected validation error") - .collect(); - - for error in errors { - println!("Validation error: {}", error); - } - - Ok(false) - } -} - diff --git a/src/cores/daemons/messaging.rs b/src/cores/daemons/messaging.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdb7153ced8ec63698de7dfb2c19d441908393ce --- /dev/null +++ b/src/cores/daemons/messaging.rs @@ -0,0 +1,450 @@ +use crate::cores::handlers::api_server::PluralOrKind; +use crate::cores::models::{ServerError, ServerResult}; +use crate::Params; +use feventbus::impls::messaging::messaging::Messaging; +use feventbus::message::Message; +use feventbus::message::NativeEventAction; +use feventbus::traits::consumer::Consumer; +use feventbus::traits::producer::Producer; +use fleetmod::utils::APIVersion; +use fleetmod::FleetResource; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; +use std::fmt::{Debug, Display, Formatter}; +use std::sync::Arc; +use std::time; +use strum::EnumIter; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::{mpsc, Mutex}; +use tokio::time::sleep; + +impl P2PEventTopic { + /// 返回带默认值的 P2PEventTopic 枚举 + pub fn create_p2p_topic(cluster_id: String) -> Vec { + vec![ + P2PEventTopic::Create(cluster_id.clone()), + P2PEventTopic::Update(cluster_id.clone()), + P2PEventTopic::Patch(cluster_id.clone()), + P2PEventTopic::Delete(cluster_id.clone()), + P2PEventTopic::List(cluster_id.clone()), + P2PEventTopic::Watch(cluster_id.clone()), + ] + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum EventTopic { + // P2P events表示其他组件向api server发送的点对点事件 + P2P(P2PEventTopic), + // PubSub events表示api server向其他组件发送的广播事件 + PubSub(PubSubEventTopic), +} + +impl Display for EventTopic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EventTopic::P2P(topic) => write!(f, "P2P.{}", topic), + EventTopic::PubSub(topic) => write!(f, "PubSub.{}", topic), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, EnumIter, PartialEq)] +pub enum P2PEventTopic { + Create(String), + Update(String), + Patch(String), + Delete(String), + List(String), + Watch(String), +} + +impl Display for P2PEventTopic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + P2PEventTopic::Create(s) => write!(f, "Create-{}", s), + P2PEventTopic::Update(s) => write!(f, "Update-{}", s), + P2PEventTopic::Patch(s) => write!(f, "Patch-{}", s), + P2PEventTopic::Delete(s) => write!(f, "Delete-{}", s), + P2PEventTopic::List(s) => write!(f, "List-{}", s), + P2PEventTopic::Watch(s) => write!(f, "Watch-{}", s), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum PubSubEventTopic { + Watch(ResourceCollectionIdentifier), +} + +impl Display for PubSubEventTopic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PubSubEventTopic::Watch(ri) => write!(f, "Watch.{}", ri), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct ResourceCollectionIdentifier { + pub kind: String, + pub api_version: String, + pub cluster_id: String, + pub name: Option, +} + +impl Into for ResourceCollectionIdentifier { + fn into(self) -> Params { + Params { + plural_or_kind: PluralOrKind::Kind(self.kind), + version: self.api_version.to_string(), + cluster_id: Some(self.cluster_id), + name: self.name, + ..Default::default() + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct ResourceCommon { + pub kind: String, + pub api_version: APIVersion, + pub metadata: fleetmod::metadata::Metadata, +} + +impl Display for ResourceCollectionIdentifier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let api_version_str = format!("{}", self.api_version); + let kind_str = format!(".{}", self.kind); + let cluster_id = format!(".{}", self.cluster_id); + write!(f, "{}{}{}", api_version_str, kind_str, cluster_id) + } +} + +impl ResourceCollectionIdentifier { + // 如果value是一个合法的FleetResource,并且符合当前identifier的筛选规则,则返回true + pub fn fits(&self, value: Value) -> bool { + // ResourceCommon包含api_version, kind, metadata等所有资源的共有字段 + let resource_common: ResourceCommon = match serde_json::from_value(value) { + Ok(v) => v, + Err(_) => return false, + }; + + // cluster_id 必须做匹配 + if resource_common.metadata.cluster_id.unwrap_or_default() != self.cluster_id { + return false; + } + // api_version, kind必须匹配 + if resource_common.api_version.to_string() != self.api_version { + return false; + } + if resource_common.kind != self.kind { + return false; + } + // 如果name字段不为空,那么name字段必须匹配 + if let Some(name) = &self.name { + if resource_common.metadata.name.eq(name) { + return false; + } + } + true + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WatchEventMessage { + pub values: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum WatchEventMessageValue { + Created(Value), + Updated(Value), + Deleted(Value), +} + +#[derive(Debug, Serialize, Clone)] +pub enum WatchEventMessageResource +where + T: FleetResource, +{ + Created(T), + Updated(T), + Deleted(T), +} + +impl TryFrom for WatchEventMessageResource +where + T: FleetResource, +{ + type Error = ServerError; + + fn try_from(value: WatchEventMessageValue) -> ServerResult { + match value { + WatchEventMessageValue::Created(v) => { + let r = serde_json::from_value(v)?; + Ok(WatchEventMessageResource::Created(r)) + } + WatchEventMessageValue::Updated(v) => { + let r = serde_json::from_value(v)?; + Ok(WatchEventMessageResource::Updated(r)) + } + WatchEventMessageValue::Deleted(v) => { + let r = serde_json::from_value(v)?; + Ok(WatchEventMessageResource::Deleted(r)) + } + } + } +} + +impl WatchEventMessageValue { + pub fn get_value(&self) -> &Value { + match self { + WatchEventMessageValue::Created(v) => v, + WatchEventMessageValue::Updated(v) => v, + WatchEventMessageValue::Deleted(v) => v, + } + } +} + +pub struct WatchDaemon { + pub msg_cli: Arc, + pub topics: Arc>>, + pub sender: Sender, + pub receiver: Arc>>, +} + +impl WatchDaemon { + pub fn new(msg_cli: Arc) -> Self { + let (sx, rx) = mpsc::channel(100); + + Self { + msg_cli, + // todo! pub_sub_event_topics在重启后会清空,需要持久化 + topics: Arc::new(Mutex::new(vec![])), + sender: sx, + receiver: Arc::new(Mutex::new(rx)), + } + } + + pub async fn start(&self) { + let rx = self.receiver.clone(); + let msg_cli = self.msg_cli.clone(); + let topics = self.topics.clone(); + tokio::spawn(async move { + log::info!("WatchEventPublisher started receiving messages"); + let mut rx = rx.lock().await; // 锁定接收器 + let buffer_limit = 32; + let mut buffer = Vec::with_capacity(buffer_limit); + loop { + sleep(time::Duration::from_millis(500)).await; + buffer.clear(); + let received_cnt = rx.recv_many(&mut buffer, buffer_limit).await; + if received_cnt == 0 { + continue; + } + log::info!("WatchEventPublisher Received {} messages", received_cnt); + Self::publish_watch_events_to_topics(msg_cli.clone(), topics.clone(), &buffer) + .await; + } + }); + } + + pub async fn add_pub_sub_event_topic(&self, topic: PubSubEventTopic) { + let mut topics = self.topics.lock().await; + if topics.contains(&topic) { + return; + } + topics.push(topic.clone()); + log::info!( + "WatchEventPublisher Added PubSubEventTopic: {:?}", + EventTopic::PubSub(topic) + ); + // todo! 持久化 + } + + pub async fn remove_pub_sub_event_topic(&self, topic: PubSubEventTopic) { + let mut topics = self.topics.lock().await; + if let Some(index) = topics.iter().position(|x| *x == topic) { + topics.remove(index); + } + } + + pub async fn publish_watch_events_to_topics( + msg_cli: Arc, + topics: Arc>>, + msg_values: &Vec, + ) { + let topics = topics.lock().await.clone(); + for topic in topics.iter() { + let mut identified_values = Vec::new(); + for msg_value in msg_values.iter() { + let identifier = match topic { + PubSubEventTopic::Watch(identifier) => identifier.clone(), + }; + let v = msg_value.get_value(); + if identifier.fits(v.clone()) { + identified_values.push(msg_value.clone()); + } + } + let cnt = identified_values.len(); + let content = WatchEventMessage { + values: identified_values, + }; + if cnt > 0 { + let topic = EventTopic::PubSub(topic.clone()); + let to_publish = Message::new( + topic.to_string(), + NativeEventAction::Other, // 该字段暂时无用 + None, // metadata + Some(serde_json::to_value(content).unwrap()), // body + None, // created_at + ); + let msg_cli = msg_cli.clone(); + // 另起一个协程来发布消息 + log::debug!( + "WatchEventPublisher Publishing {} message(s) to topic {}", + cnt, + topic + ); + if let Err(e) = msg_cli.publish(to_publish).await { + log::error!("WatchEventPublisher Failed to publish event: {}", e); + } + log::debug!( + "WatchEventPublisher Published {} message(s) to topic {}", + cnt, + topic + ); + } + } + } + + pub async fn publish_create_event(&self, data: Value) { + if let Err(e) = self + .sender + .send(WatchEventMessageValue::Created(data)) + .await + { + log::error!("Failed to publish create event: {}", e); + } + } + + pub async fn publish_update_event(&self, data: Value) { + if let Err(e) = self + .sender + .send(WatchEventMessageValue::Updated(data)) + .await + { + log::error!("Failed to publish update event: {}", e); + } + } + + pub async fn publish_delete_event(&self, data: Value) { + if let Err(e) = self + .sender + .send(WatchEventMessageValue::Deleted(data)) + .await + { + log::error!("Failed to publish delete event: {}", e); + } + } +} + +#[allow(unused)] +pub async fn watch_raw( + msg_cli: Arc, + ri: ResourceCollectionIdentifier, +) -> ServerResult> { + let topic = EventTopic::PubSub(PubSubEventTopic::Watch(ri)); + let topic_str = topic.to_string(); + let topic_str_clone = topic_str.clone(); + let (sx, rx) = mpsc::channel(32); + tokio::spawn(async move { + let subscribe_result = msg_cli + .subscribe( + topic_str.as_str(), + Arc::new(move |message: Message| { + let sx = sx.clone(); + let topic_str = topic_str_clone.clone(); + Box::pin(async move { + if message.body.is_none() { + log::warn!( + "Watcher on {} Received message with no body", + topic_str.as_str() + ); + return Ok("".to_string()); + } + let body = + serde_json::from_value::(message.body.unwrap()) + .unwrap(); + for value in body.values.into_iter() { + let value = serde_json::to_value(value); + if let Err(e) = value { + log::error!( + "Watcher on {} Failed to convert value: {}", + topic_str.as_str(), + e + ); + continue; + } + if let Err(e) = sx.send(value.unwrap()).await { + log::warn!( + "Watcher on {} Failed to send message to channel: {}", + topic_str.as_str(), + e + ); + } + } + Ok("".to_string()) + }) + }), + ) + .await; + if let Err(e) = subscribe_result { + log::error!( + "Watcher on {} Failed to subscribe: {}.", + topic_str.as_str(), + e + ); + } + }); + Ok(rx) +} + +#[allow(unused)] +pub async fn watch_resource( + msg_cli: Arc, + ri: ResourceCollectionIdentifier, +) -> ServerResult>> +where + T: FleetResource + 'static, +{ + let mut rx_from = watch_raw(msg_cli, ri).await?; + let (sx, rx) = mpsc::channel(32); + tokio::spawn(async move { + while let Some(value) = rx_from.recv().await { + log::debug!("watch resource received value: {:?}", value); + let message_value: WatchEventMessageValue = match serde_json::from_value(value) { + Ok(v) => v, + Err(e) => { + log::error!("Failed to convert WatchEventMessageValue: {}", e); + continue; + } + }; + log::debug!("watch resource received WatchEventMessageValue: {:?}", message_value); + let resource = match WatchEventMessageResource::::try_from(message_value) { + Ok(r) => r, + Err(e) => { + log::error!("Failed to convert WatchEventMessageResource: {}", e); + continue; + } + }; + if let Err(e) = sx.send(resource).await { + log::error!("Failed to send WatchEventMessageResource: {}", e); + } + } + }); + Ok(rx) +} diff --git a/src/cores/daemons/mod.rs b/src/cores/daemons/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..9c1c4fd64802dab1640f39e22c89177f8dcc667e --- /dev/null +++ b/src/cores/daemons/mod.rs @@ -0,0 +1 @@ +pub mod messaging; diff --git a/src/cores/events.rs b/src/cores/events.rs deleted file mode 100644 index 7488dc537941e2f516a8974492f0e5558dbadc51..0000000000000000000000000000000000000000 --- a/src/cores/events.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::cores::apiserver::AppState; -use crate::cores::services::{APIServerError, APIServerResult, APIServerService, APIServerServiceParams, PluralOrKind}; -use anyhow::Result; -use feventbus::err::Error as FEventBusError; -use feventbus::impls::messaging::messaging::Messaging; -use feventbus::message::{Message}; -use feventbus::traits::consumer::{Consumer, MessageHandler}; -use serde::de::DeserializeOwned; -use serde::{Serialize}; -use serde_json::error::Error as SerdeError; -use serde_json::Value; -use std::future::Future; -use std::sync::Arc; -use client_rust::event_client::{DeleteEventRequest, DeleteEventResponse, EndsWatchRequest, EndsWatchResponse, EventTopic, P2PEventTopic, PubSubEventTopic, ReadEventRequest, ReadEventResponse, ResourceCollectionIdentifier, ResourceDefinition, ShangzhuUpdateRequest, ShangzhuUpdateResponse, StartsWatchRequest, StartsWatchResponse, WriteEventRequest, WriteEventResponse}; -use client_rust::event_client::PluralOrKind as cPluralOrKind; -use client_rust::result::{ServerError, ServerResult}; - -impl From for APIServerServiceParams { - fn from(ri: ResourceCollectionIdentifier) -> Self { - APIServerServiceParams { - group: ri.api_version.group, - plural_or_kind: PluralOrKind::Kind(ri.kind), - version: ri.api_version.version, - namespace: ri.namespace, - name: None, - } - } -} -impl From for PluralOrKind { - fn from(client: cPluralOrKind) -> Self { - match client { - cPluralOrKind::Plural(value) => PluralOrKind::Plural(value), - cPluralOrKind::Kind(value) => PluralOrKind::Kind(value), - } - } -} - - -fn convert_api_error_to_server_error(api_error: APIServerError) -> ServerError { - ServerError { - message: format!( - "StatusCode: {:?}, Message: {}", - api_error.status_code, api_error.message - ), - } -} - -pub fn convert_api_result_to_server_result(api_result: APIServerResult) -> ServerResult { - api_result.map_err(convert_api_error_to_server_error) -} - -impl From for APIServerServiceParams { - fn from(rd: ResourceDefinition) -> Self { - APIServerServiceParams { - group: rd.group, - plural_or_kind: rd.plural_or_kind.into(), - version: rd.version, - namespace: rd.namespace, - name: rd.name, - } - } -} - - -pub struct P2PEventServer { - app_state: Arc, - service: Arc, -} - -impl P2PEventServer { - pub fn new(app_state: Arc) -> Self { - Self { - app_state, - service: Arc::new(APIServerService::new()), - } - } - - pub async fn start(&self) { - let nats_cli = self.app_state.nats_cli.clone(); - self.start_reply(nats_cli.clone(), self.app_state.cluster_id.clone()).await; - } - - pub async fn reply_list(service: Arc, app_state: Arc, req: ReadEventRequest) -> ReadEventResponse { - let res = service.get_resource(req.params.into(), req.query, app_state.clone()).await; - - let server_result: ServerResult = convert_api_result_to_server_result(res); - ReadEventResponse { res: server_result } - } - - pub async fn reply_create(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { - let _lock = crate::cores::GLOBAL_SQ_LOCK.lock().await; - - let res = service.create_resource(req.params.into(), req.data, app_state.clone()).await; - match res.clone() { - Ok(val) => { log::info!("--------create resource {} succeed--------",val) } - Err(e) => { log::info!("!!!!!!!!!create res failed {}!!!!!!!!!",e) } - } - let server_result: ServerResult = convert_api_result_to_server_result(res); - - WriteEventResponse { res: server_result } - } - - pub async fn reply_update(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { - let _lock = crate::cores::GLOBAL_SQ_LOCK.lock().await; - let res = service.update_resource(req.params.into(), req.data, app_state.clone()).await; - match res.clone() { - Ok(val) => { log::info!("--------update resource {} succeed--------",val) } - Err(e) => { log::info!("!!!!!!!!!update resource failed {}!!!!!!!!!",e) } - } - let server_result: ServerResult = convert_api_result_to_server_result(res); - - WriteEventResponse { res: server_result } - } - - pub async fn reply_patch(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { - let _lock = crate::cores::GLOBAL_SQ_LOCK.lock().await; - - let res = service.patch_resource(req.params.into(), req.data, app_state).await; - match res.clone() { - Ok(val) => { log::info!("--------patch resource {} succeed--------",val) } - Err(e) => { log::info!("!!!!!!!!!patch resource failed {}!!!!!!!!!",e) } - } - let server_result: ServerResult = convert_api_result_to_server_result(res); - - WriteEventResponse { res: server_result } - } - - pub async fn reply_delete(service: Arc, app_state: Arc, req: DeleteEventRequest) -> DeleteEventResponse { - let _lock = crate::cores::GLOBAL_SQ_LOCK.lock().await; - let res = service.delete_resource(req.params.into(), app_state).await; - match res.clone() { - Ok(val) => { log::info!("--------delete resource {} succeed--------",val) } - Err(e) => { log::info!("!!!!!!!!!delete resource failed {}!!!!!!!!!",e) } - } - let server_result: ServerResult = convert_api_result_to_server_result(res); - - DeleteEventResponse { res: server_result } - } - - pub async fn reply_starts_watch(_: Arc, app_state: Arc, req: StartsWatchRequest) -> StartsWatchResponse { - let watch_event = PubSubEventTopic::Watch(req.ri); - app_state.watch_event_publisher.add_pub_sub_event_topic(watch_event).await; - StartsWatchResponse {} - } - - pub async fn reply_ends_watch(_service: Arc, _app_state: Arc, _req: EndsWatchRequest) -> EndsWatchResponse { - // 暂时先不实现取消watch:需要维持观察者的状态,以便在取消watch时取消订阅,可以通过心跳来实现 - todo!() - } - - pub async fn reply_shangzhu_update(_service: Arc, _app_state: Arc, req: ShangzhuUpdateRequest) -> ShangzhuUpdateResponse { - let res = req.data.clone(); - - let server_result: ServerResult = convert_api_result_to_server_result(Ok(res)); - - ShangzhuUpdateResponse { res: server_result } - } - - pub async fn do_reply(service: Arc, app_state: Arc, body: Value, reply: F) -> Result - where - I: DeserializeOwned, - O: Serialize, - F: Fn(Arc, Arc, I) -> Fut, - Fut: Future, - { - let req: I = serde_json::from_value(body).map_err(|e: SerdeError| FEventBusError::MessageHandling(e.to_string()))?; - let res = reply(service, app_state, req).await; - Ok(serde_json::to_string(&res).unwrap()) - } - - pub fn get_reply_handler(&self, event_topic: P2PEventTopic) -> MessageHandler - { - let service = self.service.clone(); - let app_state = self.app_state.clone(); - let reply_handler: MessageHandler = Arc::new(move |msg: Message| { - let event_topic = event_topic.clone(); - let service = service.clone(); - let app_state = app_state.clone(); - Box::pin(async move { - if msg.body.is_none() { - return Err(FEventBusError::MessageHandling( - "Message body is null".to_string(), - )); - } - let body = msg.body.unwrap(); - match event_topic { - P2PEventTopic::List(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_list).await, - P2PEventTopic::Create(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_create).await, - P2PEventTopic::Update(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_update).await, - P2PEventTopic::Patch(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_patch).await, - P2PEventTopic::Delete(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_delete).await, - P2PEventTopic::NotifyWatch(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_starts_watch).await, - P2PEventTopic::StopWatch(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_ends_watch).await, - P2PEventTopic::ShangzhuUpdate(_s) => Self::do_reply(service, app_state, body, P2PEventServer::reply_shangzhu_update).await - } - }) - }); - reply_handler - } - - pub async fn start_reply(&self, nats_cli: Arc, cluster_id: String) { - let mut topics = Vec::new(); - let p2p_topics =P2PEventTopic::create_p2p_topic(cluster_id); - for p2p in p2p_topics { - topics.push(EventTopic::P2P(p2p.clone())); - } - for t in topics { - let topic_str = t.to_string(); - let p2p_topic = match t { - EventTopic::P2P(topic) => topic, - _ => unreachable!(), - }; - let reply_handler = self.get_reply_handler(p2p_topic); - let nats_cli = nats_cli.clone(); - tokio::spawn(async move { - log::info!("Registering reply handler for topic {}", topic_str); - if let Err(e) = nats_cli - .reply(topic_str.as_str(), reply_handler) - .await { - log::error!("Failed to register reply handler for topic {}: {}", topic_str, e); - } - }); - } - } -} - - diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs deleted file mode 100644 index fdc49f7b3e72465d40c2c1bf4ae4051628572404..0000000000000000000000000000000000000000 --- a/src/cores/handlers.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::cores::apiserver::{AppState, K8sStylePathParams}; -use crate::cores::services::{APIServerResult, APIServerService, APIServerStatusCode}; -use actix_web::HttpResponse; -use async_trait::async_trait; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::sync::Arc; - -#[async_trait] -pub trait Handler { - async fn create_resource( - &self, - params: K8sStylePathParams, - data: Value, - app_state: Arc, - ) -> HttpResponse; - - async fn delete_resource( - &self, - params: K8sStylePathParams, - app_state: Arc, - ) -> HttpResponse; - - async fn update_resource( - &self, - params: K8sStylePathParams, - data: Value, - app_state: Arc, - ) -> HttpResponse; - - async fn get_resource( - &self, - params: K8sStylePathParams, - _query: Value, - app_state: Arc, - ) -> HttpResponse; - - async fn patch_resource( - &self, - params: K8sStylePathParams, - data: Value, - app_state: Arc, - ) -> HttpResponse; - - // 不满足以上请求路径的处理 - fn default(&self) -> DefaultHandler; -} - -#[derive(Clone)] -pub struct DefaultHandler { - service: APIServerService, -} - -impl DefaultHandler { - pub fn new() -> Self { - Self { - service: APIServerService::new() - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct APIServerResponse -{ - pub status_code: APIServerStatusCode, - pub message: String, - pub data: Option, -} - -impl From> for APIServerResponse -where - T: Serialize + DeserializeOwned + Clone, -{ - fn from(result: APIServerResult) -> Self { - match result { - Ok(data) => APIServerResponse { - status_code: APIServerStatusCode::OK, - message: "Success".to_string(), - data: Some(data), - }, - Err(err) => APIServerResponse { - status_code: err.status_code, - message: err.message, - data: None, - } - } - } -} - -impl From> for HttpResponse -where - T: Serialize + DeserializeOwned + Clone, -{ - fn from(response: APIServerResponse) -> Self { - HttpResponse::Ok().json(response) - } -} - -#[async_trait] -impl Handler for DefaultHandler { - async fn create_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { - HttpResponse::from(APIServerResponse::from(self.service.create_resource(params.into(), data, app_state).await)) - } - - async fn delete_resource(&self, params: K8sStylePathParams, app_state: Arc) -> HttpResponse { - HttpResponse::from(APIServerResponse::from(self.service.delete_resource(params.into(), app_state).await)) - } - - async fn update_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { - HttpResponse::from(APIServerResponse::from(self.service.update_resource(params.into(), data, app_state).await)) - } - - async fn get_resource(&self, params: K8sStylePathParams, query: Value, app_state: Arc) -> HttpResponse { - HttpResponse::from(APIServerResponse::from(self.service.get_resource(params.into(), query, app_state).await)) - } - - async fn patch_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { - HttpResponse::from(APIServerResponse::from(self.service.patch_resource(params.into(), data, app_state).await)) - } - - fn default(&self) -> Self { - Self { - service: APIServerService::new() - } - } -} - diff --git a/src/cores/handlers/api_server.rs b/src/cores/handlers/api_server.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2de3c8d45b8ecfa69c326531202c928b288405e --- /dev/null +++ b/src/cores/handlers/api_server.rs @@ -0,0 +1,620 @@ +use crate::cores::daemons::messaging::{ + watch_raw, PubSubEventTopic, ResourceCollectionIdentifier, WatchDaemon, +}; +use crate::cores::models::{AppState, ServerError, ServerRequest, ServerResult}; +use crate::db::check_exist::{check_kine, check_metadata}; +use crate::db::db::DbConnection; +use crate::db::delete::delete_from_kine; +use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; +use crate::db::insert::{insert_kine, insert_metadata}; +use crate::db::update::update_data_in_kine; +use bon::Builder; +use fleetmod::FleetResource; +use json_patch::Patch; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::{Arc, LazyLock, Mutex}; +use tokio_stream::wrappers::ReceiverStream; + +// todo! 之后修改成缓存模块 +// 定义全局哈希表来获取model名 +static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { + let mut map = HashMap::new(); + map.insert("pods".to_string(), "Pod".to_string()); + map.insert("nodes".to_string(), "Node".to_string()); + map.insert("jobs".to_string(), "Job".to_string()); + map.insert("cronjobs".to_string(), "CronJob".to_string()); + map.insert("flowjobs".to_string(), "FlowJob".to_string()); + map.insert("events".to_string(), "Event".to_string()); + Mutex::new(map) +}); + +struct MetadataCache {} + +impl MetadataCache { + fn insert_key_value(key: &str, value: &str) { + let mut map = GLOBAL_HASHMAP.lock().unwrap(); + map.insert(key.to_string(), value.to_string()); + } + + fn get_map() -> HashMap { + let map = GLOBAL_HASHMAP.lock().unwrap(); + map.clone() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PluralOrKind { + Plural(String), + Kind(String), +} + +impl Default for PluralOrKind { + fn default() -> Self { + PluralOrKind::Plural("".to_string()) + } +} + +impl PluralOrKind { + pub fn get_kind(&self) -> ServerResult { + match self { + PluralOrKind::Plural(plural) => { + let plural_kind_map = MetadataCache::get_map(); + let kind = plural_kind_map.get(plural); + if kind.is_none() { + return Err(ServerError::bad_request("plural参数不合法")); + } + Ok(kind.unwrap().clone()) + } + PluralOrKind::Kind(kind) => Ok(kind.clone()), + } + } + + pub fn get_plural(&self) -> ServerResult { + match self { + PluralOrKind::Plural(plural) => Ok(plural.clone()), + PluralOrKind::Kind(kind) => { + let plural_kind_map = MetadataCache::get_map(); + let plural = plural_kind_map.iter().find(|(_, v)| v.eq(&kind)); + if plural.is_none() { + return Err(ServerError::bad_request("kind参数不合法")); + } + Ok(plural.unwrap().0.clone()) + } + } + } +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, Builder)] +#[builder(on(String, into))] +pub struct Params { + #[builder(field)] + pub plural_or_kind: PluralOrKind, + #[builder(field)] + pub query: HashMap, + pub version: String, + pub cluster_id: Option, + pub name: Option, + pub body: Option, +} + +impl ParamsBuilder { + pub fn plural(mut self, plural: impl ToString) -> Self { + self.plural_or_kind = PluralOrKind::Plural(plural.to_string()); + self + } + + pub fn kind(mut self, kind: impl ToString) -> Self { + self.plural_or_kind = PluralOrKind::Kind(kind.to_string()); + self + } + + pub fn add_query(mut self, key: impl ToString, value: impl ToString) -> Self { + self.query.insert(key.to_string(), value.to_string()); + self + } + + pub fn query(mut self, query: HashMap) -> Self { + self.query.extend(query); + self + } +} + +impl From<&T> for Params where T: FleetResource { + fn from(resource: &T) -> Self { + let kind = resource.get_kind(); + let metadata = resource.get_metadata(); + let api_version = resource.get_api_version(); + Params::builder() + .kind(kind) + .version(&api_version.version) + .name(&metadata.name) + .maybe_cluster_id(metadata.cluster_id.clone()) + .body(serde_json::to_value(resource).expect("resource to json failed")) + .build() + } +} + +impl TryInto for Params { + type Error = ServerError; + + fn try_into(self) -> ServerResult { + let mut params = HashMap::new(); + let plural = self.plural_or_kind.get_plural()?; + let version = self.version; + let name = self.name; + params.insert("plural".to_string(), plural); + params.insert("version".to_string(), version); + if name.is_some() { + params.insert("name".to_string(), name.unwrap()); + } + Ok(ServerRequest { + headers: HashMap::new(), + params, + body: self + .body + .and_then(|b| Some(serde_json::to_vec(&b).expect("body to json failed"))), + }) + } +} + +pub async fn create_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult { + let ServiceCtx { + mut db_conn, + message_daemon, + params, + .. + } = prepare_ctx(app_state, server_request, None, Some(true))?; + let Params { + plural_or_kind, + version, + body, + .. + } = params; + let body = body.expect("body is required"); + let plural = plural_or_kind.get_plural()?; + if plural == "crds" { + // 处理 CRD 资源注册 + let item_kind = body + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + let kind_upper = body + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("kind")) + .and_then(|kind| kind.as_str()) + .unwrap_or("error"); + + let metadata_exists = + check_metadata(db_conn.get_mut(), item_kind, version.as_str()).await?; + + if metadata_exists { + return Err(ServerError::duplicated("该CRD资源已存在,无需重复注册")); + } + + insert_metadata(db_conn.get_mut(), item_kind, version.as_str(), false, &body).await?; + + MetadataCache::insert_key_value(item_kind, kind_upper); + } else { + // 检查 metadata 是否存在 + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + + // 检查资源是否已存在 + let item_name = body + .get("metadata") + .and_then(|metadata| metadata.get("name")) + .and_then(|name| name.as_str()) + .unwrap_or("error"); + + let kine_exists = check_kine( + db_conn.get_mut(), + plural.as_str(), + item_name, + version.as_str(), + Some("default"), + ) + .await?; + + if kine_exists { + return Err(ServerError::duplicated("该资源已存在,请勿重复创建")); + } + + insert_kine( + db_conn.get_mut(), + plural.as_str(), + item_name, + &body, + version.as_str(), + Some("default"), + ) + .await?; + } + message_daemon.publish_create_event(body.clone()).await; + Ok(body.clone()) +} + +pub async fn delete_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult { + //log_request("delete_resource", ¶ms); + let ServiceCtx { + mut db_conn, + message_daemon, + params, + .. + } = prepare_ctx(app_state, server_request, Some(true), Some(false))?; + let Params { + name, + plural_or_kind, + version, + .. + } = params; + let name = name.expect("name is required"); + let plural = plural_or_kind.get_plural()?; + + // 检查 metadata + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + + // 检查资源是否存在 + let resource_json_string = get_data_from_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + ) + .await?; + if resource_json_string.is_none() { + return Err(ServerError::not_found("指定资源不存在")); + } + let resource = match serde_json::from_str::(resource_json_string.unwrap().as_str()) { + Ok(json_data) => Ok(json_data), + Err(_) => Err(ServerError::internal_error("资源格式错误,无法解析为 JSON")), + }?; + + // 删除资源 + let deleted = delete_from_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + ) + .await?; + + if !deleted { + return Err(ServerError::internal_error("指定资源不存在")); + } + + message_daemon.publish_delete_event(resource.clone()).await; + Ok(resource) +} + +pub async fn update_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult { + //log_request("update_resource", ¶ms); + let ServiceCtx { + mut db_conn, + message_daemon, + params, + .. + } = prepare_ctx(app_state, server_request, Some(true), Some(true))?; + let Params { + name, + plural_or_kind, + version, + body, + .. + } = params; + let name = name.expect("name is required"); + let body = body.expect("body is required"); + let plural = plural_or_kind.get_plural()?; + + // 检查 metadata 是否存在 + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + + // 检查资源是否存在 + let kine_exists = check_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + ) + .await?; + + if !kine_exists { + return Err(ServerError::internal_error("指定资源不存在")); + } + + // 更新资源 + let updated = update_data_in_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + &body, + ) + .await?; + + if !updated { + return Err(ServerError::internal_error("更新资源失败")); + } + + message_daemon.publish_update_event(body.clone()).await; + Ok(body.clone()) +} + +pub async fn get_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult { + //log_request("get_resource", ¶ms); + let ServiceCtx { + mut db_conn, + params, + .. + } = prepare_ctx(app_state, server_request, None, None)?; + let Params { + name, + plural_or_kind, + version, + .. + } = params; + let plural = plural_or_kind.get_plural()?; + // 检查 metadata 是否存在 + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + + if name.is_some() { + // 如果name不为空,查询单个resource数据 + log::debug!("get data from kine: {} {} {}", plural, name.as_ref().unwrap(), version); + if let Some(data) = get_data_from_kine( + db_conn.get_mut(), + plural.as_str(), + name.unwrap().as_str(), + version.as_str(), + Some("default"), + ) + .await? + { + // 将字符串解析为 JSON 格式 + return match serde_json::from_str::(&data) { + Ok(json_data) => Ok(json_data), + Err(_) => Err(ServerError::internal_error("资源格式错误,无法解析为 JSON")), + }; + } + // 如果资源不存在 + Err(ServerError::not_found("指定资源不存在")) + } else { + // 如果name为空,获取资源列表 + let data_list = get_all_data_from_kine( + db_conn.get_mut(), + plural.as_str(), + version.as_str(), + Some("default"), + ) + .await?; + + // 将所有字符串解析为 JSON 格式并收集到数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + // 返回结果 + Ok(json!(json_array)) + } +} + +pub async fn patch_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult { + let ServiceCtx { + mut db_conn, + message_daemon, + params, + } = prepare_ctx(app_state, server_request, Some(true), Some(true))?; + let Params { + name, + plural_or_kind, + version, + body, + .. + } = params; + let plural = plural_or_kind.get_plural()?; + let body = body.expect("body is required"); + let name = name.expect("name is required"); + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + + let mut curr_resource = { + let curr_resource = get_data_from_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + ) + .await?; + if curr_resource.is_none() { + return Err(ServerError::not_found("指定资源不存在")); + } + // 将字符串解析为 JSON 格式 + match serde_json::from_str::(&curr_resource.unwrap()) { + Ok(json_data) => json_data, + Err(_) => { + return Err(ServerError::internal_error( + "现有资源数据格式错误,无法解析为JSON", + )) + } + } + }; + let patch_json = body; + let patch_operation: Patch = serde_json::from_value(patch_json)?; + json_patch::patch(&mut curr_resource, &patch_operation) + .map_err(|e| ServerError::bad_request(format!("patch failed: {}", e).as_str()))?; + let updated = update_data_in_kine( + db_conn.get_mut(), + plural.as_str(), + name.as_str(), + version.as_str(), + Some("default"), + &curr_resource, + ) + .await?; + if !updated { + return Err(ServerError::internal_error("在Patch时更新资源失败")); + } + message_daemon + .publish_update_event(curr_resource.clone()) + .await; + Ok(curr_resource) +} + +pub async fn watch_resource( + app_state: Arc, + server_request: ServerRequest, +) -> ServerResult> { + let ServiceCtx { + mut db_conn, + message_daemon, + params, + } = prepare_ctx(app_state.clone(), server_request, None, Some(false))?; + let Params { + plural_or_kind, + version, + name, + cluster_id, + .. + } = params; + let cluster_id = cluster_id.unwrap_or_else(|| app_state.cluster_id.clone()); + let kind = plural_or_kind.get_kind()?; + let plural = plural_or_kind.get_plural()?; + check_metadata_exists(db_conn.get_mut(), plural.as_str(), version.as_str()).await?; + let ri = ResourceCollectionIdentifier { + cluster_id, + kind, + api_version: version.to_string(), + name, + }; + message_daemon + .add_pub_sub_event_topic(PubSubEventTopic::Watch(ri.clone())) + .await; + Ok(ReceiverStream::from( + watch_raw(app_state.message_cli.clone(), ri).await?, + )) +} + +struct ServiceCtx { + db_conn: RefCell, + message_daemon: Arc, + params: Params, +} + +impl TryFrom for Params { + type Error = ServerError; + fn try_from(server_request: ServerRequest) -> ServerResult { + let extract_required_param = |key: &str| { + server_request + .params + .get(key) + .ok_or_else(|| ServerError::bad_request(format!("{}参数未指定", key).as_str())) + .cloned() + }; + let extract_optional_param = |key: &str| server_request.params.get(key).cloned(); + let version = extract_required_param("version")?; + let plural = extract_optional_param("plural"); + let kind = extract_optional_param("kind"); + let name = extract_optional_param("name"); + let plural_kind_map = MetadataCache::get_map(); + let kind_plural_map = plural_kind_map + .iter() + .map(|(k, v)| (v.clone(), k.clone())) + .collect::>(); + let plural = if plural.is_some() { + plural.unwrap() + } else if kind.is_some() { + let plural = kind_plural_map + .get(kind.as_ref().unwrap().as_str()) + .cloned(); + if plural.is_none() { + return Err(ServerError::bad_request("kind参数不合法")); + } + plural.unwrap() + } else { + return Err(ServerError::bad_request("plural或kind参数未指定")); + }; + let body = if server_request.body.is_none() { + None + } else { + server_request.body + }; + Ok(Params::builder() + .plural(plural) + .version(version) + .maybe_name(name) + .maybe_body( + body.map(|b| serde_json::from_slice(b.as_slice()).expect("body to json failed")), + ) + .build()) + } +} + +fn prepare_ctx( + app_state: Arc, + server_request: ServerRequest, + name_required: Option, + body_required: Option, +) -> ServerResult { + let params = Params::try_from(server_request)?; + log::debug!("request params: {:?}", params); + if name_required.is_some_and(|required| required) && params.name.is_none() { + return Err(ServerError::bad_request("name参数未指定")); + } + if name_required.is_some_and(|required| !required) && params.name.is_some() { + return Err(ServerError::bad_request("name参数不应指定")); + } + if body_required.is_some_and(|required| required) && params.body.is_none() { + return Err(ServerError::bad_request("body参数未指定")); + } + let db_conn = app_state.db_pool.get_connection(); + if let Err(e) = db_conn { + log::error!("error getting db conn: {}", e); + return Err(ServerError::internal_error("DB pool error")); + } + let db_conn = db_conn.unwrap(); + let message_daemon = app_state.watch_daemon.clone(); + Ok(ServiceCtx { + db_conn: RefCell::new(db_conn), + message_daemon, + params, + }) +} + +#[allow(unused)] +async fn check_metadata_exists( + db_conn: &mut DbConnection, + plural: &str, + api_version_str: &str, +) -> ServerResult<()> { + // let metadata_exists = check_metadata(db_conn, plural, api_version_str).await?; + // + // if !metadata_exists { + // return Err(ServerError::not_found( + // "该plural不存在,请检查plural版本以及是否需要namespace", + // )); + // } + Ok(()) +} diff --git a/src/cores/handlers/cluster_info.rs b/src/cores/handlers/cluster_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..9877df13415ec19a338a682044c62dae5e2fdf81 --- /dev/null +++ b/src/cores/handlers/cluster_info.rs @@ -0,0 +1,9 @@ +use crate::cores::models::{AppState, ServerRequest, ServerResult}; +use serde_json::{json, Value}; +use std::sync::Arc; + +pub async fn get_cluster_info(app_state: Arc, _: ServerRequest) -> ServerResult { + Ok(json!({ + "cluster_id": app_state.cluster_id + })) +} diff --git a/src/datamgr_route/datamgr_api.rs b/src/cores/handlers/datamgr/datamgr_api.rs similarity index 58% rename from src/datamgr_route/datamgr_api.rs rename to src/cores/handlers/datamgr/datamgr_api.rs index bf8dafcba8bbaaf8ba3c017cb91b1f751d08ab42..a8309d7d6626b48223953b2049f408a08f0e52b0 100644 --- a/src/datamgr_route/datamgr_api.rs +++ b/src/cores/handlers/datamgr/datamgr_api.rs @@ -1,73 +1,92 @@ +pub const PROTOCOL_IPV4: u32 = 0; +pub const PROTOCOL_IPV6: u32 = 1; extern "C" { - pub fn NewPluginManager() -> *mut ::std::os::raw::c_void; + pub fn NewPluginManager(nodeId: ::std::os::raw::c_int) -> *mut ::std::os::raw::c_void; } extern "C" { pub fn DeletePluginManager(pluginManager: *mut ::std::os::raw::c_void); } extern "C" { - pub fn LoadPluginFromFile( + pub fn SetParameter( pluginManager: *mut ::std::os::raw::c_void, - path: *const ::std::os::raw::c_char, + key: *const ::std::os::raw::c_char, + value: *const ::std::os::raw::c_char, ); } extern "C" { - pub fn LoadPluginsFromDirectory( - pluginManager: *mut ::std::os::raw::c_void, - path: *const ::std::os::raw::c_char, - ); + pub fn LoadPlugins(pluginManager: *mut ::std::os::raw::c_void); } extern "C" { - pub fn UnloadAllPlugins(pluginManager: *mut ::std::os::raw::c_void); + pub fn UnloadPlugins(pluginManager: *mut ::std::os::raw::c_void); } +pub type MessageCallback = ::std::option::Option< + unsafe extern "C" fn( + topic: *const ::std::os::raw::c_char, + uuid: *const ::std::os::raw::c_char, + size: ::std::os::raw::c_int, + data: *const ::std::os::raw::c_char, + closure: *mut ::std::os::raw::c_void, + ), +>; extern "C" { - pub fn UploadFile( + pub fn StartUdp( pluginManager: *mut ::std::os::raw::c_void, - fileName: *const ::std::os::raw::c_char, - fileContent: *const ::std::os::raw::c_char, - fileSize: ::std::os::raw::c_int, - fileDescription: *const ::std::os::raw::c_char, - ) -> ::std::os::raw::c_int; + protocol: ::std::os::raw::c_int, + address: *const ::std::os::raw::c_char, + port: ::std::os::raw::c_int, + ); } extern "C" { - pub fn UploadStatus( - pluginManager: *mut ::std::os::raw::c_void, - status: *const ::std::os::raw::c_char, - ) -> ::std::os::raw::c_int; + pub fn StopUdp(pluginManager: *mut ::std::os::raw::c_void); } extern "C" { - pub fn UploadInstruction( + pub fn AddNodeToSync( pluginManager: *mut ::std::os::raw::c_void, - instruction: *const ::std::os::raw::c_char, - ) -> ::std::os::raw::c_int; + id: ::std::os::raw::c_int, + address: *const ::std::os::raw::c_char, + port: ::std::os::raw::c_int, + ); } extern "C" { - pub fn DownloadFile( + pub fn Publish( pluginManager: *mut ::std::os::raw::c_void, - fileName: *const ::std::os::raw::c_char, - fileSize: *mut ::std::os::raw::c_int, - ) -> *const ::std::os::raw::c_char; + topic: *const ::std::os::raw::c_char, + size: ::std::os::raw::c_int, + data: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; } extern "C" { - pub fn DeleteFile( + pub fn Subscribe( pluginManager: *mut ::std::os::raw::c_void, - fileName: *const ::std::os::raw::c_char, + topic: *const ::std::os::raw::c_char, + messageCallback: MessageCallback, + closure: *mut ::std::os::raw::c_void, ) -> ::std::os::raw::c_int; } extern "C" { - pub fn ListFiles( + pub fn Unsubscribe( pluginManager: *mut ::std::os::raw::c_void, - indexCount: *mut ::std::os::raw::c_int, - ) -> *mut *const ::std::os::raw::c_char; + topic: *const ::std::os::raw::c_char, + messageCallback: MessageCallback, + ) -> ::std::os::raw::c_int; } extern "C" { - pub fn QueryFile( + pub fn Request( pluginManager: *mut ::std::os::raw::c_void, - fileName: *const ::std::os::raw::c_char, - indexCount: *mut ::std::os::raw::c_int, - ) -> *mut *const ::std::os::raw::c_char; + topic: *const ::std::os::raw::c_char, + requestSize: ::std::os::raw::c_int, + requestData: *const ::std::os::raw::c_char, + responseCallback: MessageCallback, + closure: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; } extern "C" { - pub fn FreeData(data: *mut *const ::std::os::raw::c_char, indexCount: ::std::os::raw::c_int); + pub fn Reply( + pluginManager: *mut ::std::os::raw::c_void, + uuid: *const ::std::os::raw::c_char, + responseSize: ::std::os::raw::c_int, + responseData: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; } extern "C" { pub fn ReceiveNormalData( @@ -98,7 +117,7 @@ extern "C" { pub fn FindDataLocation( pluginManager: *mut ::std::os::raw::c_void, key: *const ::std::os::raw::c_char, - dataLocationBuffer: *const ::std::os::raw::c_char, + dataLocationBuffer: *mut ::std::os::raw::c_char, count: ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } @@ -106,7 +125,7 @@ extern "C" { pub fn ReadData( pluginManager: *mut ::std::os::raw::c_void, key: *const ::std::os::raw::c_char, - dataBuffer: *const ::std::os::raw::c_char, + dataBuffer: *mut ::std::os::raw::c_char, count: ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } @@ -126,3 +145,44 @@ extern "C" { count: ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn UploadFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + fileContent: *const ::std::os::raw::c_char, + fileSize: ::std::os::raw::c_int, + fileDescription: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn UploadStatus( + pluginManager: *mut ::std::os::raw::c_void, + status: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn UploadInstruction( + pluginManager: *mut ::std::os::raw::c_void, + instruction: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn DownloadFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + fileSize: *mut ::std::os::raw::c_int, + ) -> *const ::std::os::raw::c_char; +} +extern "C" { + pub fn ListFiles( + pluginManager: *mut ::std::os::raw::c_void, + indexCount: *mut ::std::os::raw::c_int, + ) -> *mut *const ::std::os::raw::c_char; +} +extern "C" { + pub fn QueryFile( + pluginManager: *mut ::std::os::raw::c_void, + fileName: *const ::std::os::raw::c_char, + indexCount: *mut ::std::os::raw::c_int, + ) -> *mut *const ::std::os::raw::c_char; +} \ No newline at end of file diff --git a/src/datamgr_route/mod.rs b/src/cores/handlers/datamgr/mod.rs similarity index 38% rename from src/datamgr_route/mod.rs rename to src/cores/handlers/datamgr/mod.rs index 36f05a1b1143a5ce82201e3ba6d1ae304ff64775..94a5b98b3bdf56f4dd4270e5fed82ecef17a9c25 100644 --- a/src/datamgr_route/mod.rs +++ b/src/cores/handlers/datamgr/mod.rs @@ -1,2 +1,4 @@ pub mod datamgr_api; -pub mod route; \ No newline at end of file +pub mod route; + +pub use route::*; diff --git a/src/cores/handlers/datamgr/route.rs b/src/cores/handlers/datamgr/route.rs new file mode 100644 index 0000000000000000000000000000000000000000..eb842bfb3fd80a4d2ca09e59e9bf60d0de4827bf --- /dev/null +++ b/src/cores/handlers/datamgr/route.rs @@ -0,0 +1,359 @@ +use crate::cores::handlers::datamgr::datamgr_api; +use crate::cores::models::{AppState, ServerRawResponse, ServerRequest, ServerResponse}; +use serde_json::json; +use std::collections::HashMap; +use std::ffi::CString; +use std::os::raw::c_char; +use std::sync::Arc; + +static mut DATA_PLUGIN_MANAGER: *mut ::std::os::raw::c_void = ::std::ptr::null_mut(); + +// /** +// * upload file +// * req-header: x-data-file-name: File name +// * req-body: file content +// * req-header: x-data-file-description: File description +// * res: Upload success +// */ +pub async fn upload_file_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + let headers = server_request.headers; + let body = server_request.body.as_ref().unwrap().as_slice(); + let bad_request = |body_str| { + ServerRawResponse::bad_request() + .body(Vec::from(body_str)) + .build() + .into() + }; + let header_file_name = match headers.get("x-data-file-name") { + Some(value) => value.to_string(), + None => return bad_request("Missing x-data-file-name header"), + }; + + let file_name = match CString::new(header_file_name) { + Ok(cstr) => cstr, + Err(_) => return bad_request("Invalid file name"), + }; + + let file_content = body.to_vec(); + + let header_file_description = match headers.get("x-data-file-description") { + Some(value) => value.to_string(), + None => return bad_request("Missing x-data-file-description header"), + }; + + let file_description = match CString::new(header_file_description) { + Ok(cstr) => cstr, + Err(_) => return bad_request("Invalid file description"), + }; + unsafe { + let ret = datamgr_api::UploadFile( + DATA_PLUGIN_MANAGER, + file_name.as_ptr(), + file_content.as_ptr() as *const i8, + file_content.len() as i32, + file_description.as_ptr(), + ); + if ret == 1 { + ServerRawResponse::ok() + .body(Vec::from("Upload file success")) + .build() + .into() + } else { + ServerRawResponse::internal_error() + .body(Vec::from("Upload file failed")) + .build() + .into() + } + } +} + +// /** +// * upload status +// * req-header: x-data-status: Status +// * res: Ok = success +// */ +pub async fn upload_status_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + let headers = &server_request.headers; + let bad_request = |body_str| { + ServerRawResponse::bad_request() + .body(Vec::from(body_str)) + .build() + .into() + }; + let header_status = match headers.get("x-data-status") { + Some(value) => value.to_string(), + None => return bad_request("Missing x-data-status header"), + }; + + let status = match CString::new(header_status) { + Ok(cstr) => cstr, + Err(_) => return bad_request("Invalid status"), + }; + + unsafe { + let ret = datamgr_api::UploadStatus(DATA_PLUGIN_MANAGER, status.as_ptr()); + if ret == 1 { + ServerRawResponse::ok() + .body(Vec::from("Upload status success")) + .build() + .into() + } else { + ServerRawResponse::internal_error() + .body(Vec::from("Upload status failed")) + .build() + .into() + } + } +} + +// /** +// * upload instruction +// * req-header: x-data-instruction: Instruction +// * res: Ok = success +// */ +pub async fn upload_instruction_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + let headers = &server_request.headers; + let bad_request = |body_str| { + ServerRawResponse::bad_request() + .body(Vec::from(body_str)) + .build() + .into() + }; + let header_instruction = match headers.get("x-data-instruction") { + Some(value) => value.to_string(), + None => return bad_request("Missing x-data-instruction header"), + }; + + let instruction = match CString::new(header_instruction) { + Ok(cstr) => cstr, + Err(_) => return bad_request("Invalid instruction"), + }; + + unsafe { + let ret = datamgr_api::UploadInstruction(DATA_PLUGIN_MANAGER, instruction.as_ptr()); + // println!("ret: {:?}", ret); + if ret == 1 { + ServerRawResponse::ok() + .body(Vec::from("Upload instruction success")) + .build() + .into() + } else { + ServerRawResponse::internal_error() + .body(Vec::from("Upload instruction failed")) + .build() + .into() + } + } +} + +// /** +// * download file +// * {filename}: File name in url +// * res-body: File content +// */ +pub async fn download_file_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + let file_name = server_request.params.get("filename").unwrap(); + let header_file_name = file_name.to_string(); + + let file_name = match CString::new(header_file_name) { + Ok(cstr) => cstr, + Err(_) => { + return ServerRawResponse::bad_request() + .body(Vec::from("Invalid file name")) + .build() + .into() + } + }; + + unsafe { + let mut file_length: i32 = 0; + let ret = + datamgr_api::DownloadFile(DATA_PLUGIN_MANAGER, file_name.as_ptr(), &mut file_length); + let res_content = + Vec::from_raw_parts(ret as *mut u8, file_length as usize, file_length as usize); + ServerRawResponse::ok().body(res_content).build().into() + } +} + +pub async fn receive_normal_data_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("receive_normal_data_handler"); + let headers = &server_request.headers; + let body = server_request.body.as_ref().unwrap().as_slice(); + let header_key = headers.get("x-data-key").unwrap().to_string(); + let key = CString::new(header_key).unwrap(); + let size_str = headers.get("x-data-size").unwrap().to_string(); + let size = size_str.parse::().unwrap(); + let mut buffer = body.to_vec(); + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = datamgr_api::ReceiveNormalData(DATA_PLUGIN_MANAGER, key.as_ptr(), c_buffer, size); + ServerRawResponse::ok() + .headers(HashMap::from([( + "x-data-size".to_string(), + ret.to_string(), + )])) + .build() + .into() + } +} + +pub async fn set_sync_and_recycle_strategy_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("set_sync_and_recycle_strategy_handler"); + let headers = &server_request.headers; + let header_key = headers.get("x-data-key").unwrap().to_string(); + let key = CString::new(header_key).unwrap(); + let header_sync_strategy = headers.get("x-data-sync-strategy").unwrap().to_string(); + let sync_strategy = CString::new(header_sync_strategy).unwrap(); + let header_recycle_time = headers.get("x-data-recycle-time").unwrap().to_string(); + let recycle_time = CString::new(header_recycle_time).unwrap(); + let header_recycle_cascade_keys = headers + .get("x-data-recycle-cascade-keys") + .unwrap() + .to_string(); + let recycle_cascade_keys = CString::new(header_recycle_cascade_keys).unwrap(); + + let _ret = datamgr_api::SetSyncAndRecycleStrategy( + DATA_PLUGIN_MANAGER, + key.as_ptr(), + sync_strategy.as_ptr(), + recycle_time.as_ptr(), + recycle_cascade_keys.as_ptr(), + ); + ServerRawResponse::ok().build().into() + } +} + +pub async fn submit_user_request_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("submit_user_request_handler"); + let headers = &server_request.headers; + let body = server_request.body.as_ref().unwrap().as_slice(); + let header_key = headers.get("x-data-key").unwrap().to_string(); + let key = CString::new(header_key).unwrap(); + let size_str = headers.get("x-data-size").unwrap().to_string(); + let size = size_str.parse::().unwrap(); + let mut buffer = body.to_vec(); + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = datamgr_api::SubmitUserRequest(DATA_PLUGIN_MANAGER, key.as_ptr(), c_buffer, size); + ServerRawResponse::ok() + .headers(HashMap::from([( + "x-data-size".to_string(), + ret.to_string(), + )])) + .build() + .into() + } +} + +pub async fn find_data_location_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("find_data_location_handler"); + let headers = &server_request.headers; + let _ = server_request.body.as_ref().unwrap().as_slice(); + let header_key = headers.get("x-data-key").unwrap().to_string(); + let key = CString::new(header_key).unwrap(); + let size = 1024; + let mut buffer: Vec = vec![0; size as usize]; + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = datamgr_api::FindDataLocation(DATA_PLUGIN_MANAGER, key.as_ptr(), c_buffer, size); + let headers = HashMap::from([( + "x-data-location".to_string(), + String::from_utf8_unchecked(buffer[0..ret as usize].to_vec().clone()), + )]); + ServerRawResponse::ok() + .headers(headers.into()) + .build() + .into() + } +} + +pub async fn read_data_handler(_: Arc, server_request: ServerRequest) -> ServerResponse { + unsafe { + println!("read_data_handler"); + let headers = &server_request.headers; + let header_key = headers.get("x-data-key").unwrap().to_string(); + let key = CString::new(header_key).unwrap(); + let size_str = headers.get("x-data-size").unwrap().to_string(); + let size = size_str.parse::().unwrap(); + let mut buffer: Vec = vec![0; size as usize]; + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = datamgr_api::ReadData(DATA_PLUGIN_MANAGER, key.as_ptr(), c_buffer, size); + ServerRawResponse::ok() + .append_header("x-data-size", ret) + .body(buffer[0..ret as usize].to_vec().clone()) + .build() + .into() + } +} + +pub async fn register_terminal_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("register_terminal_handler"); + let headers = &server_request.headers; + let body = server_request.body.as_ref().unwrap().as_slice(); + let header_user_token = headers.get("x-data-user-token").unwrap().to_string(); + let user_token = CString::new(header_user_token).unwrap(); + let size_str = headers.get("x-data-user-data-size").unwrap().to_string(); + let size = size_str.parse::().unwrap(); + let mut buffer = body.to_vec(); + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = + datamgr_api::RegisterTerminal(DATA_PLUGIN_MANAGER, user_token.as_ptr(), c_buffer, size); + ServerRawResponse::ok() + .append_header("x-data-user-data-size", ret) + .json(json!({ "x-data-user-data-size": ret })) + .build() + .into() + } +} + +pub async fn get_terminal_info_handler( + _: Arc, + server_request: ServerRequest, +) -> ServerResponse { + unsafe { + println!("get_terminal_info_handler"); + let headers = &server_request.headers; + let header_user_token = headers.get("x-data-user-token").unwrap().to_string(); + let user_token = CString::new(header_user_token).unwrap(); + let size_str = headers.get("x-data-user-data-size").unwrap().to_string(); + let size = size_str.parse::().unwrap(); + let mut buffer: Vec = vec![0; size as usize]; + let c_buffer = buffer.as_mut_ptr() as *mut c_char; + let ret = + datamgr_api::GetTerminalInfo(DATA_PLUGIN_MANAGER, user_token.as_ptr(), c_buffer, size); + ServerRawResponse::ok() + .append_header("x-data-user-data-size", ret) + .body(buffer[0..ret as usize].to_vec().clone()) + .build() + .into() + } +} diff --git a/src/cores/handlers/mod.rs b/src/cores/handlers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca3f5ef098963909b79d8d44243f5d86177fb201 --- /dev/null +++ b/src/cores/handlers/mod.rs @@ -0,0 +1,3 @@ +pub mod api_server; +pub mod cluster_info; +pub mod datamgr; diff --git a/src/cores/mod.rs b/src/cores/mod.rs index 2bce73738053c2474b20838edc82379eb7ed04bc..404fbf72e5428cb1bbc2866e145d0cac57b1c37b 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -1,108 +1,60 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use crate::cores::apiserver::{APIServer, AppState}; -use crate::cores::events::P2PEventServer; -use crate::cores::handlers::DefaultHandler; -use crate::cores::plugin::PluginManager; -use crate::db::db::DbPool; -use client_rust::event_client::WatchEventPublisher; -use feventbus::impls::messaging::messaging::Messaging; -use feventbus::traits::controller::EventBus; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; - -pub mod apiserver; -pub mod checker; -pub mod events; -pub mod handlers; -pub mod plugin; -pub mod services; -use std::fs; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; -use tokio::sync::Mutex; -use uuid::Uuid; - -lazy_static::lazy_static! { - pub static ref GLOBAL_SQ_LOCK: Arc> = Arc::new(Mutex::new(())); -} -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ResourcesMessage { - group: Option, - version: String, - namespace: Option, - plural: String, -} - -fn get_or_create_uuid(file_path: &PathBuf) -> io::Result { - let path = Path::new(file_path); - - if path.exists() { - let uuid = fs::read_to_string(path)?.trim().to_string(); - return Ok(uuid); - } - - let uuid = Uuid::new_v4().to_string(); - - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - - let mut file = fs::File::create(path)?; - file.write_all(uuid.as_bytes())?; - - Ok(uuid) -} - -pub async fn prepare_app_state(database_url: &str) -> anyhow::Result { - let db_pool = Arc::new(DbPool::new(database_url)?); - let handler: DefaultHandler = DefaultHandler::new(); - let messaging = Arc::new(Messaging::new().await?); - let watch_event_publisher = Arc::new(WatchEventPublisher::new(messaging.clone())); - // let file_path = "/root/iscas/fleet/uuid.conf"; - let config_dir = dirs::config_dir().expect("config dir not found"); - let file_path = config_dir.join("iscas").join("fleet").join("uuid.conf"); - let cluster_id = get_or_create_uuid(&file_path)?; - - Ok(AppState { - db_pool, - handler: Arc::new(handler), - nats_cli: messaging, - watch_event_publisher, - cluster_id, - }) -} - -/// 启动 Web 服务器 -/// -/// # 参数 -/// - `database_url`: 数据库连接字符串 -/// - `address`: 服务监听地址(如 "0.0.0.0:8080") -/// -/// # 返回值 -/// - 异步运行结果 -pub async fn start_server( - database_url: &str, - address: &str, - plugin_manager: Arc, -) -> anyhow::Result<()> { - let app_state = prepare_app_state(database_url).await?; - // 启动watch相关事件监听协程 - app_state.clone().watch_event_publisher.start(); - // 启动P2P事件监听协程 - P2PEventServer::new(Arc::from(app_state.clone())) - .start() - .await; - - // 从插件管理器加载路由 - let custom_route_providers = Arc::new(plugin_manager.get_providers()); - - APIServer::new() - .start(address, app_state, custom_route_providers) - .await?; - Ok(()) -} +pub mod daemons; +pub mod handlers; +pub mod models; +pub(crate) mod router; +pub mod servers; + +use crate::cores::daemons::messaging::WatchDaemon; +use crate::cores::models::AppState; +use crate::cores::router::Router; +use crate::db::db::DbPool; +use crate::utils::get_or_create_uuid; +use feventbus::impls::messaging::messaging::Messaging; +use std::sync::Arc; +use crate::cores::servers::{MessagingServer, Server}; + +pub async fn prepare_app_state(database_url: &str, msg_cli: Arc) -> anyhow::Result { + let db_pool = Arc::new(DbPool::new(database_url)?); + let watch_event_publisher = Arc::new(WatchDaemon::new(msg_cli.clone())); + let config_dir = dirs::config_dir().expect("config dir not found"); + let file_path = config_dir.join("iscas").join("fleet").join("uuid.conf"); + let cluster_id = get_or_create_uuid(&file_path)?; + let router = Arc::new(Router::init().await); + + Ok(AppState { + router, + db_pool, + message_cli: msg_cli, + watch_daemon: watch_event_publisher, + cluster_id, + }) +} + +/// 启动 Web 服务器 +/// +/// # 参数 +/// - `database_url`: 数据库连接字符串 +/// - `address`: 服务监听地址(如 "0.0.0.0:8080") +/// +/// # 返回值 +/// - 异步运行结果 +pub async fn start_server(database_url: &str, actix_web_address: &str, msg_cli: Arc) -> anyhow::Result<()> { + let app_state = prepare_app_state(database_url, msg_cli).await?; + // 启动watch相关事件监听协程 + let app_state_watch = app_state.clone(); + tokio::spawn(async move { + app_state_watch.watch_daemon.start().await; + }); + + // 启动各个server + let messaging_server: Box = Box::new(MessagingServer); + let actix_web_server: Box = Box::new(servers::ActixWebServer::new(actix_web_address)); + let servers: Vec> = vec![messaging_server, actix_web_server]; + for server in servers { + let app_state_moved = app_state.clone(); + tokio::spawn(async move { + server.start(app_state_moved).await; + }); + } + Ok(()) +} diff --git a/src/cores/models.rs b/src/cores/models.rs new file mode 100644 index 0000000000000000000000000000000000000000..483b71871dbaa683559a9d2443a12258e6ca7fe2 --- /dev/null +++ b/src/cores/models.rs @@ -0,0 +1,250 @@ +use crate::cores::daemons::messaging::WatchDaemon; +use crate::cores::router::Router; +use crate::db::db::DbPool; +use bon::Builder; +use derive_more::From; +use enum_as_inner::EnumAsInner; +use feventbus::impls::messaging::messaging::Messaging; +use paperclip::actix::Apiv2Schema; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::num::NonZeroU16; +use std::sync::Arc; +use serde::de::DeserializeOwned; +use tokio_stream::wrappers::ReceiverStream; + +#[derive(Clone)] +pub struct AppState { + pub router: Arc, + pub db_pool: Arc, + pub message_cli: Arc, + pub watch_daemon: Arc, + pub cluster_id: String, +} + +#[derive(Clone)] +pub struct ServerRequest { + pub headers: HashMap, + pub params: HashMap, + pub body: Option>, +} + +#[derive(Debug, Serialize, Deserialize, EnumAsInner, From)] +pub enum ServerResponse { + Json(ServerJsonResponse), + Raw(ServerRawResponse), + JsonStream(ServerJsonStreamResponse), +} + +#[derive(Default, Debug, Serialize, Deserialize, Builder, Apiv2Schema)] +pub struct ServerJsonResponse { + pub status_code: ServerStatusCode, + #[builder(into)] + pub message: String, + pub data: Option, +} + +impl Into> for ServerJsonResponse +where T: DeserializeOwned { + fn into(self) -> ServerResult { + match self.status_code { + ServerStatusCode::OK => { + let obj: T = serde_json::from_value(self.data.unwrap_or_default()).map_err(|e| ServerError::internal_error(e.to_string().as_str()))?; + Ok(obj) + }, + _ => Err(ServerError::new(self.status_code, self.message.as_str())) + } + } +} + +#[derive(Debug, Serialize, Deserialize, Builder)] +pub struct ServerRawResponse { + #[builder(field)] + pub headers: HashMap, + #[builder(field)] + pub body: Option>, + pub status_code: NonZeroU16, + #[builder(into)] + pub content_type: Option, +} + +impl ServerRawResponseBuilder { + pub fn append_header(mut self, key: impl ToString, value: impl ToString) -> Self { + self.headers.insert(key.to_string(), value.to_string()); + self + } + + pub fn headers(mut self, headers: HashMap) -> Self { + self.headers.extend(headers); + self + } + + pub fn body(mut self, body: Vec) -> Self { + self.body = Some(body); + self.into() + } + + pub fn json(self, body: Value) -> Self { + self.body(serde_json::to_vec(&body).expect("serialize json failed")) + } +} + +impl Default for ServerRawResponse { + fn default() -> Self { + Self { + status_code: NonZeroU16::new(200).unwrap(), + headers: HashMap::new(), + body: None, + content_type: None, + } + } +} + +macro_rules! define_status_code_builder_for_raw { + ($name:ident, $code:expr) => { + pub fn $name() -> ServerRawResponseBuilder { + ServerRawResponse::builder().status_code(NonZeroU16::new($code).unwrap()) + } + }; +} + +impl ServerRawResponse { + define_status_code_builder_for_raw!(ok, 200); + define_status_code_builder_for_raw!(not_found, 404); + define_status_code_builder_for_raw!(bad_request, 400); + define_status_code_builder_for_raw!(internal_error, 500); +} + +#[derive(Debug, Builder, Serialize, Deserialize)] +pub struct ServerJsonStreamResponse { + #[serde(skip)] + pub stream: Option>, +} + +pub type ServerResult = Result; + +impl From> for ServerResponse { + fn from(result: ServerResult) -> Self { + match result { + Ok(data) => ServerJsonResponse::builder() + .status_code(ServerStatusCode::OK) + .message("success".to_string()) + .data(serde_json::to_value(data).expect("serialize data failed")) + .build() + .into(), + Err(err) => ServerJsonResponse::builder() + .status_code(err.status_code) + .message(err.message) + .build() + .into(), + } + } +} + +impl From>> for ServerResponse { + fn from(result: ServerResult>) -> Self { + match result { + Ok(stream) => ServerJsonStreamResponse::builder() + .stream(stream) + .build() + .into(), + Err(err) => ServerJsonResponse::builder() + .status_code(err.status_code) + .message(err.message) + .build() + .into(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Apiv2Schema)] +pub enum ServerStatusCode { + // 正常流程 + OK = 20000, + + // 内部错误流程 + InternalError = 50000, + + // 请求方错误流程 + NotFound = 40004, + BadRequest = 40000, + Duplicated = 40001, + Timeout = 40002, +} + +impl Default for ServerStatusCode { + fn default() -> Self { + ServerStatusCode::OK + } +} + +impl From for ServerStatusCode { + fn from(code: actix_web::http::StatusCode) -> Self { + match code { + actix_web::http::StatusCode::OK => ServerStatusCode::OK, + actix_web::http::StatusCode::NOT_FOUND => ServerStatusCode::NotFound, + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR => ServerStatusCode::InternalError, + _ => ServerStatusCode::InternalError, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ServerError { + pub status_code: ServerStatusCode, + pub message: String, +} + +macro_rules! define_server_error_fn { + ($func:ident, $code:ident) => { + pub fn $func(message: &str) -> Self { + Self::new(ServerStatusCode::$code, message) + } + }; +} + +impl ServerError { + pub fn new(t: ServerStatusCode, msg: &str) -> Self { + Self { + status_code: t, + message: msg.to_string(), + } + } + define_server_error_fn!(internal_error, InternalError); + define_server_error_fn!(not_found, NotFound); + define_server_error_fn!(bad_request, BadRequest); + define_server_error_fn!(duplicated, Duplicated); + define_server_error_fn!(timeout, Timeout); +} + +impl Display for ServerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self, f) + } +} + +impl Error for ServerError {} + +impl From for ServerError { + fn from(error: diesel::result::Error) -> Self { + Self::internal_error(error.to_string().as_str()) + } +} + +impl From for ServerError { + fn from(value: serde_json::Error) -> Self { + ServerError::internal_error(format!("serde error: {}", value).as_str()) + } +} + +impl Into for ServerError { + fn into(self) -> ServerJsonResponse { + ServerJsonResponse::builder() + .status_code(self.status_code) + .message(self.message) + .build() + } +} diff --git a/src/cores/plugin.rs b/src/cores/plugin.rs deleted file mode 100644 index 7f207865aef15917ce78c8409a191af1fa387abb..0000000000000000000000000000000000000000 --- a/src/cores/plugin.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::sync::{Arc, Mutex}; -use crate::cores::apiserver::CustomRouteProvider; - -pub struct PluginManager { - providers: Mutex>>, -} - -impl PluginManager { - pub fn new() -> Self { - Self { - providers: Mutex::new(Vec::new()), - } - } - - pub fn register(&self, provider: Arc) { - let mut providers = self.providers.lock().unwrap(); - providers.push(provider); - } - - pub fn get_providers(&self) -> Vec> { - let providers = self.providers.lock().unwrap(); - providers.clone() - } -} diff --git a/src/cores/router.rs b/src/cores/router.rs new file mode 100644 index 0000000000000000000000000000000000000000..85a04e8c789709400382bb8903347fc908c2140a --- /dev/null +++ b/src/cores/router.rs @@ -0,0 +1,218 @@ +use crate::cores::handlers; +use crate::cores::models::{AppState, ServerRequest, ServerResponse}; +use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use strum::Display; +use tokio::sync::RwLock; + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Display)] +pub enum RouterKey { + // api server router key + ResourceCreate, + ResourceDelete, + ResourceUpdate, + ResourceGet, + ResourcePatch, + ResourceWatch, + + // cluster info router key + ClusterInfoGet, + + // Data mgr router key + DataMgrUploadFile, + DataMgrUploadStatus, + DataMgrUploadInstruction, + DataMgrDownloadFile, + DataMgrDeleteFile, + DataMgrListFile, + DataMgrQueryFile, + DataMgrReceiveNormalData, + DataMgrSetSyncAndRecycleStrategy, + DataMgrSubmitUserRequest, + DataMgrFindDataLocation, + DataMgrReadData, + DataMgrRegisterTerminal, + DataMgrGetTerminalInfo, +} + +impl RouterKey { + pub fn is_api_server_router_key(key: &RouterKey) -> bool { + match key { + RouterKey::ResourceCreate + | RouterKey::ResourceDelete + | RouterKey::ResourceUpdate + | RouterKey::ResourceGet + | RouterKey::ResourcePatch + | RouterKey::ResourceWatch => true, + _ => false, + } + } +} + +pub type Handler = + fn(Arc, ServerRequest) -> Pin + Send>>; + +macro_rules! pin_box_handler { + ($fn_call:path) => {{ + pub fn handler_boxed( + app_state: Arc, + server_request: ServerRequest, + ) -> Pin + Send>> { + Box::pin(async move { $fn_call(app_state, server_request).await.into() }) + } + handler_boxed + }}; +} + +macro_rules! add_route { + ($router:ident, $router_key_path:path, $handler_path:path) => { + let handler = pin_box_handler!($handler_path); + $router.add_route($router_key_path, handler).await; + }; +} + +pub struct Router { + routes: RwLock>, +} + +impl Router { + pub async fn init() -> Self { + let router = Router { + routes: RwLock::new(HashMap::new()), + }; + + // api server routers + + { + add_route!( + router, + RouterKey::ResourceCreate, + handlers::api_server::create_resource + ); + add_route!( + router, + RouterKey::ResourceDelete, + handlers::api_server::delete_resource + ); + add_route!( + router, + RouterKey::ResourceUpdate, + handlers::api_server::update_resource + ); + add_route!( + router, + RouterKey::ResourceGet, + handlers::api_server::get_resource + ); + add_route!( + router, + RouterKey::ResourcePatch, + handlers::api_server::patch_resource + ); + add_route!( + router, + RouterKey::ResourceWatch, + handlers::api_server::watch_resource + ); + } + + // cluster info routers + { + add_route!( + router, + RouterKey::ClusterInfoGet, + handlers::cluster_info::get_cluster_info + ); + } + + // data mgr routers + { + add_route!( + router, + RouterKey::DataMgrUploadFile, + handlers::datamgr::upload_file_handler + ); + add_route!( + router, + RouterKey::DataMgrUploadStatus, + handlers::datamgr::upload_status_handler + ); + add_route!( + router, + RouterKey::DataMgrUploadInstruction, + handlers::datamgr::upload_instruction_handler + ); + add_route!( + router, + RouterKey::DataMgrDownloadFile, + handlers::datamgr::download_file_handler + ); + // add_route!( + // router, + // RouterKey::DataMgrDeleteFile, + // handlers::datamgr::delete_file_handler + // ); + // add_route!( + // router, + // RouterKey::DataMgrListFile, + // handlers::datamgr::list_file_handler + // ); + // add_route!( + // router, + // RouterKey::DataMgrQueryFile, + // handlers::datamgr::query_file_handler + // ); + add_route!( + router, + RouterKey::DataMgrReceiveNormalData, + handlers::datamgr::receive_normal_data_handler + ); + add_route!( + router, + RouterKey::DataMgrSetSyncAndRecycleStrategy, + handlers::datamgr::set_sync_and_recycle_strategy_handler + ); + add_route!( + router, + RouterKey::DataMgrSubmitUserRequest, + handlers::datamgr::submit_user_request_handler + ); + add_route!( + router, + RouterKey::DataMgrFindDataLocation, + handlers::datamgr::find_data_location_handler + ); + add_route!( + router, + RouterKey::DataMgrReadData, + handlers::datamgr::read_data_handler + ); + add_route!( + router, + RouterKey::DataMgrRegisterTerminal, + handlers::datamgr::register_terminal_handler + ); + add_route!( + router, + RouterKey::DataMgrGetTerminalInfo, + handlers::datamgr::get_terminal_info_handler + ); + } + + router + } + + pub async fn add_route(&self, router_key: RouterKey, handler: Handler) { + self.routes.write().await.insert(router_key, handler); + } + + pub async fn get_routes(&self) -> HashMap { + self.routes.read().await.clone() + } + + pub async fn get_route(&self, router_key: RouterKey) -> Option { + self.routes.read().await.get(&router_key).cloned() + } +} diff --git a/src/cores/servers/actix_web.rs b/src/cores/servers/actix_web.rs new file mode 100644 index 0000000000000000000000000000000000000000..3fe9175bbff93290ed7c0ac2d7d94f13b4ece71e --- /dev/null +++ b/src/cores/servers/actix_web.rs @@ -0,0 +1,533 @@ +use crate::cores::router::RouterKey; +use crate::cores::models::{ + AppState, ServerJsonResponse, ServerJsonStreamResponse, ServerRawResponse, ServerRequest, + ServerResponse, +}; +use crate::cores::servers::Server; +use crate::utils::headers; +use actix_web::http::header; +use actix_web::http::StatusCode; +use actix_web::{App, Error, HttpRequest, HttpResponse, HttpServer}; +use async_trait::async_trait; +use futures::StreamExt; +use paperclip::actix::Apiv2Schema; +use paperclip::actix::{ + api_v2_operation, delete, get, head, patch, post, put, + web::{Bytes, Data, Json, Path, Query}, +}; +use paperclip::actix::{web, OpenApiExt}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use paperclip::v2::models::{DefaultApiRaw, Tag}; +use tokio_stream::wrappers::ReceiverStream; + +pub struct ActixWebServer { + pub addr: String, +} + +impl ActixWebServer { + pub fn new(addr: &str) -> Self { + ActixWebServer { + addr: addr.to_string() + } + } +} + +#[async_trait] +impl Server for ActixWebServer { + async fn start(&self, app_state: AppState) { + log::info!("Starting Actix web server at {}", self.addr); + let mut spec = DefaultApiRaw::default(); + spec.tags = vec![ + Tag { + name: "ApiServer".to_string(), + description: Some("API Server routes".to_string()), + external_docs: None, + }, + Tag { + name: "DataMgr".to_string(), + description: Some("Data Manager routes".to_string()), + external_docs: None, + }, + Tag { + name: "ClusterInfo".to_string(), + description: Some("Cluster Info routes".to_string()), + external_docs: None, + }, + ]; + let server = HttpServer::new(move || { + App::new() + .wrap_api_with_spec(spec.clone()) + .app_data(Data::new(app_state.clone())) + .configure(api_server::configure_routes) + .configure(cluster_info::configure_routes) + .configure(datamgr::configure_routes) + .with_json_spec_at("/api/spec/v2") + .with_swagger_ui_at("/api/doc") + .build() + }) + .bind(self.addr.as_str()) + .expect("Failed to bind Actix web server"); + server.run().await.expect("Failed to run Actix web server"); + } +} + +fn extract_params(path_params: impl Serialize, query: impl Serialize) -> HashMap { + let params_map = obj_to_hash_map(&path_params).expect("convert struct to hashmap failed"); + let query_map = obj_to_hash_map(&query).expect("convert struct to hashmap failed"); + let mut final_map = HashMap::new(); + final_map.extend(params_map); + final_map.extend(query_map); + log::debug!("extract params: {:?}", final_map); + final_map +} + +fn obj_to_hash_map(data: &T) -> Result, Error> { + // 将结构体序列化为JSON Value + let value = serde_json::to_value(data)?; + + // 确保Value是Object类型(即JSON对象) + let mut hashmap = HashMap::new(); + if let Value::Object(map) = value { + // 遍历每个键值对 + for (key, val) in map.into_iter() { + let str_val = match val { + // 字符串类型直接提取内容 + Value::String(s) => s.clone(), + // 其他类型转为字符串形式(如数字、布尔) + _ => val.to_string(), + }; + hashmap.insert(key.clone(), str_val); + } + return Ok(hashmap); + } + Ok(HashMap::new()) +} + +macro_rules! construct_request { + (with_json_body $params:ident, $headers:ident, $data:ident) => {{ + let body = $data.into_inner(); + let req = ServerRequest { + headers: $headers, + params: $params, + body: Some(serde_json::to_vec(&body).expect("serialize body failed")), + }; + req + }}; + (with_bytes_body $params:ident, $headers:ident, $data:ident) => {{ + let body: Vec = $data.into(); + let req = ServerRequest { + headers: $headers, + params: $params, + body: Some(body), + }; + req + }}; + (without_body $params:ident, $headers:ident) => {{ + let req = ServerRequest { + headers: $headers, + params: $params, + body: None, + }; + req + }}; +} + +macro_rules! call_route { + ($app_state:ident, $req:ident, $router_key:path) => {{ + let app_state_cloned = $app_state.clone(); + let res = $app_state + .router + .get_route($router_key) + .await + .expect("route not found")(app_state_cloned.into_inner(), $req) + .await; + res + }}; +} + +// 通用宏定义 +macro_rules! define_json_response_method { + (body_required $name:ident, $method:ident, $route:expr, $path_type:ty, $query_type:ty, $router_key:path, $tag:expr) => { + #[api_v2_operation(tags($tag))] + #[$method($route)] + pub async fn $name( + app_state: Data, + req: HttpRequest, + path_params: Path<$path_type>, + query: Query<$query_type>, + data: Json, + ) -> Json { + let params = extract_params(path_params.into_inner(), query.into_inner()); + let headers: HashMap = headers::into_map(req.headers().clone()); + let req = construct_request!(with_json_body params, headers, data); + let res: ServerResponse = call_route!(app_state, req, $router_key); + let json_response: ServerJsonResponse = res.into_json().unwrap(); + Json(json_response) + } + }; + (body_unrequired $name:ident, $method:ident, $route:expr, $path_type:ty, $query_type:ty, $router_key:path, $tag:expr) => { + #[api_v2_operation(tags($tag))] + #[$method($route)] + pub async fn $name( + app_state: Data, + req: HttpRequest, + path_params: Path<$path_type>, + query: Query<$query_type>, + ) -> Json { + let params = extract_params(path_params.into_inner(), query.into_inner()); + let headers = headers::into_map(req.headers().clone()); + let req = construct_request!(without_body params, headers); + let res: ServerResponse = call_route!(app_state, req, $router_key); + let json_response: ServerJsonResponse = res.into_json().unwrap(); + Json(json_response) + } + }; +} + +macro_rules! define_raw_response_method { + ($name:ident, $method:ident, $route:expr, $path_type:ty, $query_type:ty, $router_key:path, $tag:expr) => { + #[api_v2_operation(tags($tag))] + #[$method($route)] + pub async fn $name( + app_state: Data, + req: HttpRequest, + params: Path<$path_type>, + query: Query<$query_type>, + data: Bytes, + ) -> HttpResponse { + + let params = extract_params(params.into_inner(), query.into_inner()); + let headers = headers::into_map(req.headers().clone()); + let req = construct_request!(with_bytes_body params, headers, data); + let res: ServerResponse = call_route!(app_state, req, $router_key); + let raw_response: ServerRawResponse = res.into_raw().unwrap(); + let mut response = HttpResponse::build(StatusCode::from_u16(raw_response.status_code.into()).unwrap()); + raw_response.headers.iter().for_each(|(key, value)| { + response.append_header((key.clone(), value.clone())); + }); + if let Some(content_type) = raw_response.content_type { + response.content_type(content_type); + } + + if let Some(body) = raw_response.body { + response.body(body); + } + response.finish() + } + }; +} + +macro_rules! define_json_stream_response_method { + ($name:ident, $method:ident, $route:expr, $path_type:ty, $query_type:ty, $router_key:path, $tag:expr) => { + #[api_v2_operation(tags($tag))] + #[$method($route)] + pub async fn $name( + app_state: Data, + req: HttpRequest, + params: Path<$path_type>, + query: Query<$query_type>, + data: Json, + ) -> HttpResponse { + let params = extract_params(params.into_inner(), query.into_inner()); + let headers = headers::into_map(req.headers().clone()); + let req = construct_request!(with_json_body params, headers, data); + + let res: ServerResponse = call_route!(app_state, req, $router_key); + if res.is_json_stream() { + let json_stream_response: ServerJsonStreamResponse = res.into_json_stream().unwrap(); + let stream: ReceiverStream = json_stream_response.stream.unwrap(); + // 将 ReceiverStream 转换为 Stream> + let sse_stream = stream.map(|value| { + // 序列化 Value 为 JSON 字符串 + match serde_json::to_string(&value) { + Ok(json) => { + // 格式化为 SSE 的 data 字段,注意末尾的两个换行符 + let data = format!("data: {}\n\n", json); + Ok(Bytes::from(data)) + } + Err(e) => { + // 将序列化错误转换为 actix_web::Error + Err(Error::from(e)) + } + } + }); + + HttpResponse::Ok() + .content_type("text/event-stream") + // 设置正确的 Content-Type 和缓存头 + .append_header((header::CACHE_CONTROL, "no-cache")) + .streaming(sse_stream) + } else { + let json_error_response = res.into_json().unwrap(); + HttpResponse::Ok().json(json_error_response) + } + + } + }; +} + +pub mod api_server { + use super::*; + pub fn configure_routes(app: &mut web::ServiceConfig) { + app.service( + web::scope("/resource") + .service(create) + .service(delete) + .service(update) + .service(patch) + .service(get) + .service(list) + ); + } + + #[derive(Serialize, Deserialize, Apiv2Schema)] + pub struct PathParamsWithName { + pub version: String, + pub plural: String, + pub name: String, + } + + #[derive(Serialize, Deserialize, Apiv2Schema)] + pub struct PathParamsWithoutName { + pub version: String, + pub plural: String, + } + // 1.1 创建指定资源 + // 请求类型:POST + // 请求的URL:/resource/create/{version}/{plural} + // 请求body:Node、EVent、Pod等资源对应的JSON + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_required create, post, "/create/{version}/{plural}", PathParamsWithoutName, (), RouterKey::ResourceCreate, "ApiServer"); + // 1.2 删除指定资源 + // 请求类型:DELETE + // 请求的URL:/resource/delete/{version}/{plural}/{name} + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_unrequired delete, delete, "/delete/{version}/{plural}/{name}", PathParamsWithName, (), RouterKey::ResourceDelete, "ApiServer"); + // 1.3 更新指定资源 + // 请求类型:PUT + // 请求的URL:/resource/update/{version}/{plural}/{name} + // 请求body:Node、EVent、Pod等资源对应的JSON + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_required update, put, "/update/{version}/{plural}/{name}", PathParamsWithName, (), RouterKey::ResourceUpdate, "ApiServer"); + // 1.4 修改指定资源 + // 请求类型:PATCH + // 请求的URL:/resource/patch/{version}/{plural}/{name} + // 请求body:Node、EVent、Pod等资源对应的patch JSON + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_required patch, patch, "/patch/{version}/{plural}/{name}", PathParamsWithName, (), RouterKey::ResourcePatch, "ApiServer"); + // 1.5 获取单一资源 + // 请求类型:GET + // 请求的URL:/resource/get/{version}/{plural}/{name} + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_unrequired get, get, "/get/{version}/{plural}/{name}", PathParamsWithName, (), RouterKey::ResourceGet, "ApiServer"); + // 1.6 获取多个资源 + // 请求类型:GET + // 请求的URL:/resource/get/{version}/{plural} + // 返回值:Node、EVent、Pod等资源对应的JSON + define_json_response_method!(body_unrequired list, get, "/get/{version}/{plural}", PathParamsWithoutName, (), RouterKey::ResourceGet, "ApiServer"); + + #[derive(Serialize, Deserialize, Apiv2Schema)] + pub struct WatchQuery { + pub cluster_id: Option, + } + // 1.7 监听单一资源 + // 请求类型:GET + // 请求的URL:/resource/watch/{version}/{plural}/{name} + // 返回值:Node、EVent、Pod等资源对应的JSON的长链接 + define_json_stream_response_method!( + watch_one, + get, + "/watch/{version}/{plural}/{name}", + PathParamsWithName, + WatchQuery, + RouterKey::ResourceWatch, + "ApiServer" + ); + // 1.8 监听多个资源 + // 请求类型:GET + // 请求的URL:/resource/watch/{version}/{plural} + // 返回值:Node、EVent、Pod等资源对应的JSON的长链接 + define_json_stream_response_method!( + watch_all, + get, + "/watch/{version}/{plural}", + PathParamsWithoutName, + WatchQuery, + RouterKey::ResourceWatch, + "ApiServer" + ); +} + +pub mod cluster_info { + use super::*; + + pub fn configure_routes(app: &mut web::ServiceConfig) { + app.service( + web::scope("/cluster") + .service(get_cluster_info) + ); + } + + define_json_response_method!(body_unrequired get_cluster_info, get, "/info", (), (), RouterKey::ClusterInfoGet, "ClusterInfo"); +} + +pub mod datamgr { + use super::*; + use paperclip::actix::Apiv2Schema; + use serde::{Deserialize, Serialize}; + + pub fn configure_routes(app: &mut web::ServiceConfig) { + app.service( + web::scope("/data") + .service(upload_file) + .service(upload_status) + .service(upload_instruction) + .service(download_file) + .service(delete_file) + .service(list_file) + .service(query_file) + .service(receive_normal_data) + .service(set_sync_and_recycle_strategy) + .service(submit_user_request) + .service(find_data_location) + .service(read_data) + .service(register_terminal) + .service(get_terminal_info) + ); + + } + + #[derive(Serialize, Deserialize, Apiv2Schema)] + pub struct FileNamePath { + pub filename: String, + } + + define_raw_response_method!( + upload_file, + post, + "/v1/files", + (), + (), + RouterKey::DataMgrUploadFile, + "DataMgr" + ); + define_raw_response_method!( + upload_status, + post, + "/v1/status", + (), + (), + RouterKey::DataMgrUploadStatus, + "DataMgr" + ); + define_raw_response_method!( + upload_instruction, + post, + "/v1/instructions", + (), + (), + RouterKey::DataMgrUploadInstruction, + "DataMgr" + ); + define_raw_response_method!( + download_file, + get, + "/v1/files/{filename}", + FileNamePath, + (), + RouterKey::DataMgrDownloadFile, + "DataMgr" + ); + define_raw_response_method!( + delete_file, + delete, + "/v1/files/{filename}", + FileNamePath, + (), + RouterKey::DataMgrDeleteFile, + "DataMgr" + ); + define_raw_response_method!( + list_file, + get, + "/v1/files", + (), + (), + RouterKey::DataMgrListFile, + "DataMgr" + ); + define_raw_response_method!( + query_file, + get, + "/v1/fileinfos/{filename}", + FileNamePath, + (), + RouterKey::DataMgrQueryFile, + "DataMgr" + ); + define_raw_response_method!( + receive_normal_data, + post, + "/v1/normal", + (), + (), + RouterKey::DataMgrReceiveNormalData, + "DataMgr" + ); + define_raw_response_method!( + set_sync_and_recycle_strategy, + post, + "/v1/strategy", + (), + (), + RouterKey::DataMgrSetSyncAndRecycleStrategy, + "DataMgr" + ); + define_raw_response_method!( + submit_user_request, + post, + "/v1/request", + (), + (), + RouterKey::DataMgrSubmitUserRequest, + "DataMgr" + ); + define_raw_response_method!( + find_data_location, + head, + "/v1/intelligence", + (), + (), + RouterKey::DataMgrFindDataLocation, + "DataMgr" + ); + define_raw_response_method!( + read_data, + get, + "/v1/intelligence", + (), + (), + RouterKey::DataMgrReadData, + "DataMgr" + ); + define_raw_response_method!( + register_terminal, + post, + "/v1/terminal", + (), + (), + RouterKey::DataMgrRegisterTerminal, + "DataMgr" + ); + define_raw_response_method!( + get_terminal_info, + get, + "/v1/terminal", + (), + (), + RouterKey::DataMgrGetTerminalInfo, + "DataMgr" + ); +} diff --git a/src/cores/servers/message.rs b/src/cores/servers/message.rs new file mode 100644 index 0000000000000000000000000000000000000000..633e0f5d3172d5e2330869b1051ba761316216bd --- /dev/null +++ b/src/cores/servers/message.rs @@ -0,0 +1,128 @@ +use crate::cores::daemons::messaging::{EventTopic, P2PEventTopic}; +use crate::cores::handlers::api_server; +use crate::cores::models::{AppState, ServerRequest, ServerResponse}; +use crate::cores::servers::Server; +use async_trait::async_trait; +use feventbus::err::Error as FEventBusError; +use feventbus::message::Message; +use feventbus::traits::consumer::{Consumer, MessageHandler}; +use serde::de::DeserializeOwned; +use serde_json::{Error as SerdeError, Value}; +use std::collections::HashMap; +use std::sync::Arc; +use crate::cores::router::RouterKey; + +pub struct MessagingServer; + +#[async_trait] +impl Server for MessagingServer { + async fn start(&self, app_state: AppState) { + log::info!("Starting Messaging Server"); + let msg_cli = app_state.message_cli.clone(); + let mut topics = Vec::new(); + let p2p_topics = P2PEventTopic::create_p2p_topic(app_state.cluster_id.clone()); + for p2p in p2p_topics { + topics.push(EventTopic::P2P(p2p.clone())); + } + log::debug!("Before registering reply handlers, topics: {:?}", topics); + for t in topics { + let topic_str = t.to_string(); + let p2p_topic = match t { + EventTopic::P2P(topic) => topic, + _ => unreachable!(), + }; + let reply_handler = Self::get_reply_handler(app_state.clone(), p2p_topic); + let msg_cli = msg_cli.clone(); + log::info!("Registering reply handler for topic {}", topic_str); + if let Err(e) = msg_cli.reply(topic_str.as_str(), reply_handler).await { + log::error!( + "Failed to register reply handler for topic {}: {}", + topic_str, + e + ); + } + } + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + log::info!("Messaging Server started."); + } +} + +impl MessagingServer { + pub async fn do_reply( + router_key: RouterKey, + headers: HashMap, + app_state: AppState, + msg_body: Value, + ) -> Result + where + I: DeserializeOwned + TryInto, + { + let params: I = serde_json::from_value(msg_body) + .map_err(|e: SerdeError| FEventBusError::MessageHandling(e.to_string()))?; + let mut request = params.try_into().map_err(|_| { + FEventBusError::MessageHandling("error converting params into request".to_string()) + })?; + request.headers = headers; + let handler = app_state.router.get_route(router_key).await; + if handler.is_none() { + return Err(FEventBusError::MessageHandling( + "No handler found".to_string(), + )); + } + let res: ServerResponse = handler.unwrap()(app_state.into(), request).await; + match res { + ServerResponse::Json(res) => Ok(serde_json::to_string(&res).map_err(|e| { + FEventBusError::MessageHandling(format!( + "Failed to convert json response to string: {}", + e + )) + })?), + ServerResponse::Raw(res) => Ok(String::from_utf8(res.body.unwrap_or(Vec::default())) + .map_err(|e| { + FEventBusError::MessageHandling(format!( + "Failed to convert raw response body to string: {}", + e + )) + })?), + ServerResponse::JsonStream(_) => Ok( + "Stream response cannot be replied. Use pub/sub to follow up messages.".to_string(), + ), + } + } + + fn get_reply_handler(app_state: AppState, event_topic: P2PEventTopic) -> MessageHandler { + let reply_handler: MessageHandler = Arc::new(move |msg: Message| { + let event_topic = event_topic.clone(); + let app_state = app_state.clone(); + Box::pin(async move { + if msg.body.is_none() { + return Err(FEventBusError::MessageHandling( + "Message body is null".to_string(), + )); + } + let body = msg.body.unwrap(); + let headers = msg.metadata.unwrap_or_default(); + let router_key = Self::get_router_key(event_topic); + if RouterKey::is_api_server_router_key(&router_key) { + Self::do_reply::(router_key, headers, app_state, body).await + } else { + Err(FEventBusError::MessageHandling( + "Invalid router key".to_string(), + )) + } + }) + }); + reply_handler + } + + fn get_router_key(p2p_event_topic: P2PEventTopic) -> RouterKey { + match p2p_event_topic { + P2PEventTopic::List(_) => RouterKey::ResourceGet, + P2PEventTopic::Create(_) => RouterKey::ResourceCreate, + P2PEventTopic::Update(_) => RouterKey::ResourceUpdate, + P2PEventTopic::Patch(_) => RouterKey::ResourcePatch, + P2PEventTopic::Delete(_) => RouterKey::ResourceDelete, + P2PEventTopic::Watch(_) => RouterKey::ResourceWatch, + } + } +} diff --git a/src/cores/servers/mod.rs b/src/cores/servers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3698f4122d16a3ab1d080d66f148c473d7984893 --- /dev/null +++ b/src/cores/servers/mod.rs @@ -0,0 +1,13 @@ +pub mod actix_web; +pub mod message; + +use crate::cores::models::AppState; +use async_trait::async_trait; +pub use message::MessagingServer; +pub use actix_web::ActixWebServer; + +#[async_trait] +pub trait Server: Send { + async fn start(&self, app_state: AppState); +} + diff --git a/src/cores/services.rs b/src/cores/services.rs deleted file mode 100644 index f6c2a2e8cf55ec3666bc1621711e7ba4eafb3b71..0000000000000000000000000000000000000000 --- a/src/cores/services.rs +++ /dev/null @@ -1,581 +0,0 @@ -use crate::cores::apiserver::{AppState, K8sStylePathParams}; -use crate::db::check_exist::{check_kine, check_metadata}; -use crate::db::db::DbConnection; -use crate::db::delete::delete_from_kine; -use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; -use crate::db::insert::{insert_kine, insert_metadata}; -use crate::db::update::update_data_in_kine; -use feventbus::err::Error as FEventBusError; -use fleetmod::utils::APIVersion; -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value, Error as SerdeError}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::sync::{Arc, LazyLock, Mutex}; -use client_rust::event_client::WatchEventPublisher; -use fleetmod::FleetResource; -use json_patch::Patch; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum APIServerStatusCode { - // 正常流程 - OK = 20000, - - // 内部错误流程 - InternalError = 50000, - - // 请求方错误流程 - NotFound = 40004, - BadRequest = 40000, - Duplicated = 40001, - Timeout = 40002, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct APIServerError { - pub status_code: APIServerStatusCode, - pub message: String, -} - - - - -impl From for APIServerError { - fn from(err: FEventBusError) -> Self { - match err { - FEventBusError::Timeout(msg) => APIServerError::timeout(msg.as_str()), - _ => APIServerError::internal_error(err.to_string().as_str()), - } - } -} - -impl From for APIServerError { - fn from(value: SerdeError) -> Self { - APIServerError::internal_error(format!("serde error: {}", value).as_str()) - } -} - -// todo! 使用macro实现 -impl APIServerError { - pub fn new(t: APIServerStatusCode, msg: &str) -> Self { - Self { - status_code: t, - message: msg.to_string(), - } - } - - pub fn internal_error(message: &str) -> Self { - Self::new(APIServerStatusCode::InternalError, message) - } - - pub fn not_found(message: &str) -> APIServerError { - Self::new(APIServerStatusCode::NotFound, message) - } - - pub fn bad_request(message: &str) -> APIServerError { - Self::new(APIServerStatusCode::BadRequest, message) - } - - pub fn duplicated(message: &str) -> APIServerError { - Self::new(APIServerStatusCode::Duplicated, message) - } - - pub fn timeout(message: &str) -> APIServerError { - Self::new(APIServerStatusCode::Timeout, message) - } -} - -impl Display for APIServerError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self, f) - } -} - -impl Error for APIServerError {} - -impl From for APIServerError { - fn from(error: diesel::result::Error) -> Self { - Self::internal_error(error.to_string().as_str()) - } -} - -pub type APIServerResult = Result; - -// todo! 之后修改成缓存模块 -// 定义全局哈希表来获取model名 -static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { - let mut map = HashMap::new(); - map.insert("pods".to_string(), "Pod".to_string()); - map.insert("nodes".to_string(), "Node".to_string()); - map.insert("jobs".to_string(), "Job".to_string()); - map.insert("cronjobs".to_string(), "CronJob".to_string()); - map.insert("flowjobs".to_string(), "FlowJob".to_string()); - map.insert("events".to_string(), "Event".to_string()); - Mutex::new(map) -}); - -struct MetadataCache {} - -impl MetadataCache { - fn insert_key_value(key: &str, value: &str) { - let mut map = GLOBAL_HASHMAP.lock().unwrap(); - map.insert(key.to_string(), value.to_string()); - } - - fn get_map() -> HashMap { - let map = GLOBAL_HASHMAP.lock().unwrap(); - map.clone() - } -} - - -#[derive(Clone)] -pub struct APIServerService {} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum PluralOrKind { - Plural(String), - Kind(String), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct APIServerServiceParams { - pub group: Option, - pub plural_or_kind: PluralOrKind, - pub version: String, - pub namespace: Option, - pub name: Option, -} - -impl APIServerServiceParams { - pub fn from_resource(resource: &T) -> APIServerServiceParams - where - T: FleetResource, - { - let kind = resource.get_kind(); - let metadata = resource.get_metadata(); - let api_version = resource.get_api_version(); - let mut builder = APIServerServiceParamsBuilder::new() - .kind(kind.to_string()) - .version(api_version.version.clone()) - .name(metadata.name.clone()); - if metadata.namespace.is_some() { - builder = builder.namespace(metadata.namespace.clone().unwrap()); - } - if api_version.group.is_some() { - builder = builder.group(api_version.group.clone().unwrap()); - } - builder.build().unwrap() - } -} - -impl From for APIServerServiceParams { - fn from(params: K8sStylePathParams) -> Self { - APIServerServiceParams { - group: params.group, - plural_or_kind: PluralOrKind::Plural(params.plural), - version: params.version, - namespace: params.namespace, - name: params.name, - } - } -} - -pub struct APIServerServiceParamsBuilder { - pub group: Option, - pub plural_or_kind: Option, - pub version: Option, - pub namespace: Option, - pub name: Option, -} - -impl APIServerServiceParamsBuilder { - pub fn new() -> Self { - Self { - group: None, - plural_or_kind: None, - version: None, - namespace: None, - name: None, - } - } - - pub fn group(mut self, group: String) -> Self { - self.group = Some(group); - self - } - - pub fn plural(mut self, plural: String) -> Self { - self.plural_or_kind = Some(PluralOrKind::Plural(plural)); - self - } - - pub fn kind(mut self, kind: String) -> Self { - self.plural_or_kind = Some(PluralOrKind::Kind(kind)); - self - } - - pub fn version(mut self, version: String) -> Self { - self.version = Some(version); - self - } - - pub fn namespace(mut self, namespace: String) -> Self { - self.namespace = Some(namespace); - self - } - - pub fn name(mut self, name: String) -> Self { - self.name = Some(name); - self - } - - pub fn build(self) -> anyhow::Result { - if self.plural_or_kind.is_none() { - return Err(anyhow::anyhow!("plural_or_kind is required")); - } - if self.version.is_none() { - return Err(anyhow::anyhow!("version is required")); - } - Ok(APIServerServiceParams { - group: self.group, - plural_or_kind: self.plural_or_kind.unwrap(), - version: self.version.unwrap(), - namespace: self.namespace, - name: self.name, - }) - } -} - -impl APIServerService { - pub fn new() -> Self { - Self {} - } - - pub async fn create_resource( - &self, - params: APIServerServiceParams, - data: Value, - app_state: Arc, - ) -> APIServerResult { - //Self::log_request("create_resource", ¶ms); - let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, .. } = Self::prepare_ctx(params, app_state, Some(false))?; - - if plural == "crds" { - // 处理 CRD 资源注册 - let item_kind = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("plural")) - .and_then(|plural| plural.as_str()) - .unwrap_or("error"); - - let kind_upper = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("kind")) - .and_then(|kind| kind.as_str()) - .unwrap_or("error"); - - let metadata_exists = check_metadata(db_conn.get_mut(), item_kind, api_version_str.as_str()) - .await?; - - if metadata_exists { - return Err(APIServerError::duplicated("该CRD资源已存在,无需重复注册")); - } - - insert_metadata(db_conn.get_mut(), item_kind, api_version_str.as_str(), namespace.is_some(), &data) - .await?; - - MetadataCache::insert_key_value(item_kind, kind_upper); - } else { - // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str()) - .await?; - - // 检查资源是否已存在 - let item_name = data - .get("metadata") - .and_then(|metadata| metadata.get("name")) - .and_then(|name| name.as_str()) - .unwrap_or("error"); - - let kine_exists = check_kine( - db_conn.get_mut(), - plural.as_str(), - item_name, - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - - if kine_exists { - return Err(APIServerError::duplicated("该资源已存在,请勿重复创建")); - } - - insert_kine( - db_conn.get_mut(), - plural.as_str(), - item_name, - &data, - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - } - watch_event_publisher.publish_create_event(data.clone()).await; - Ok(data) - } - - pub async fn delete_resource( - &self, - params: APIServerServiceParams, - app_state: Arc, - ) -> APIServerResult { - //Self::log_request("delete_resource", ¶ms); - let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; - let name = name.unwrap(); - - // 检查 metadata - Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str()) - .await?; - - // 检查资源是否存在 - let resource_json_string = get_data_from_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - if resource_json_string.is_none() { - return Err(APIServerError::not_found("指定资源不存在")); - } - let resource = match serde_json::from_str::(resource_json_string.unwrap().as_str()) { - Ok(json_data) => Ok(json_data), - Err(_) => Err(APIServerError::internal_error("资源格式错误,无法解析为 JSON")), - }?; - - // 删除资源 - let deleted = delete_from_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - - if !deleted { - return Err(APIServerError::internal_error("指定资源不存在")); - } - - watch_event_publisher.publish_delete_event(resource.clone()).await; - Ok(resource) - } - - pub async fn update_resource( - &self, - params: APIServerServiceParams, - data: Value, - app_state: Arc, - ) -> APIServerResult { - //Self::log_request("update_resource", ¶ms); - let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; - let name = name.unwrap(); - - // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str()) - .await?; - - // 检查资源是否存在 - let kine_exists = check_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - - if !kine_exists { - return Err(APIServerError::internal_error("指定资源不存在")); - } - - // 更新资源 - let updated = update_data_in_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - &data, - ).await?; - - if !updated { - return Err(APIServerError::internal_error("更新资源失败")); - } - - watch_event_publisher.publish_update_event(data.clone()).await; - Ok(data) - } - - pub async fn get_resource( - &self, - params: APIServerServiceParams, - _query: Value, - app_state: Arc, - ) -> APIServerResult { - //Self::log_request("get_resource", ¶ms); - let ServiceCtx { mut db_conn, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, None)?; - // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str()) - .await?; - - if name.is_some() { - // 如果name不为空,查询单个resource数据 - if let Some(data) = get_data_from_kine( - db_conn.get_mut(), - plural.as_str(), - name.unwrap().as_str(), - api_version_str.as_str(), - namespace.as_deref(), - ).await? { - // 将字符串解析为 JSON 格式 - return match serde_json::from_str::(&data) { - Ok(json_data) => Ok(json_data), - Err(_) => Err(APIServerError::internal_error("资源格式错误,无法解析为 JSON")), - }; - } - // 如果资源不存在 - Err(APIServerError::not_found("指定资源不存在")) - } else { - // 如果name为空,获取资源列表 - let data_list = get_all_data_from_kine(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.as_deref()) - .await?; - - // 将所有字符串解析为 JSON 格式并收集到数组中 - let json_array: Vec = data_list - .into_iter() - .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 - .collect(); - // 返回结果 - Ok(json!(json_array)) - } - } - - pub async fn patch_resource( - &self, - params: APIServerServiceParams, - data: Value, - app_state: Arc, - ) -> APIServerResult { - //Self::log_request("patch_resource", ¶ms); - let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; - let name = name.unwrap(); - Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str()) - .await?; - - let mut curr_resource = { - let curr_resource = get_data_from_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - ).await?; - if curr_resource.is_none() { - return Err(APIServerError::not_found("指定资源不存在")); - } - // 将字符串解析为 JSON 格式 - match serde_json::from_str::(&curr_resource.unwrap()) { - Ok(json_data) => json_data, - Err(_) => return Err(APIServerError::internal_error("现有资源数据格式错误,无法解析为JSON")), - } - }; - let patch_json = data; - let patch_operation: Patch = serde_json::from_value(patch_json)?; - json_patch::patch(&mut curr_resource, &patch_operation).map_err(|e| { - APIServerError::bad_request(format!("patch failed: {}", e).as_str()) - })?; - let updated = update_data_in_kine( - db_conn.get_mut(), - plural.as_str(), - name.as_str(), - api_version_str.as_str(), - namespace.as_deref(), - &curr_resource, - ).await?; - if !updated { - return Err(APIServerError::internal_error("在Patch时更新资源失败")); - } - watch_event_publisher.publish_update_event(curr_resource.clone()).await; - Ok(curr_resource) - } -} - -struct ServiceCtx { - db_conn: RefCell, - watch_event_publisher: Arc, - api_version_str: String, - plural: String, - namespace: Option, - name: Option, -} - -impl APIServerService { - fn log_request(method: &str, params: &APIServerServiceParams) { - log::debug!("APIServerService收到请求,方法:{:?}, 参数为:{:?}", method, params); - } - - fn prepare_ctx(params: APIServerServiceParams, app_state: Arc, name_required: Option) -> APIServerResult { - // 获取 path 参数 - let APIServerServiceParams { group, version, plural_or_kind, namespace, name } = params; - if name_required.is_some_and(|r| r) && name.is_none() { - return Err(APIServerError::bad_request("name参数未指定")); - } - if name_required.is_some_and(|r| !r) && name.is_some() { - return Err(APIServerError::bad_request("name参数不应指定")); - } - let res = app_state.db_pool.get_connection(); - if let Err(e) = res { - log::error!("error getting db conn: {}", e); - return Err(APIServerError::internal_error("DB pool error")); - } - let conn = res.unwrap(); - let watch_event_publisher = app_state.watch_event_publisher.clone(); - let plural_kind_map = MetadataCache::get_map(); - let kind_plural_map = plural_kind_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect::>(); - let plural = match plural_or_kind { - PluralOrKind::Plural(p) => { - p.to_string() - } - PluralOrKind::Kind(k) => { - let plural = kind_plural_map.get(k.as_str()); - if plural.is_none() { - return Err(APIServerError::bad_request("未知的资源类型")); - } - plural.unwrap().clone() - } - }; - Ok(ServiceCtx { - db_conn: RefCell::new(conn), - watch_event_publisher, - api_version_str: APIVersion { - group, - version, - }.to_string(), - plural, - namespace, - name, - }) - } - - async fn check_metadata_exists(db_conn: &mut DbConnection, plural: &str, api_version_str: &str) -> APIServerResult<()> { - let metadata_exists = check_metadata(db_conn, plural, api_version_str) - .await?; - - if !metadata_exists { - return Err(APIServerError::not_found("该plural不存在,请检查plural版本以及是否需要namespace")); - } - Ok(()) - } -} diff --git a/src/custom_route/custom_example.rs b/src/custom_route/custom_example.rs deleted file mode 100644 index 82a0d54cfd40f98ae35994148500288546a7d639..0000000000000000000000000000000000000000 --- a/src/custom_route/custom_example.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde_json::{json, Value}; -use std::sync::Arc; -use crate::cores::apiserver::{CustomRouteProvider, APIServerRoute}; -use crate::cores::plugin::PluginManager; -use actix_web::{web, HttpResponse, Route}; -use actix_web::http::Method; - - -pub struct SimpleRoute { - pub method: Method, - pub path: String, - pub handler: fn(data: Value) -> HttpResponse, -} - -impl APIServerRoute for SimpleRoute { - fn get_method(&self) -> Method { - self.method.clone() - } - - fn get_path(&self) -> String { - self.path.clone() - } - - fn get_web_route(&self) -> Route { - let handler = self.handler; - web::route() - .method(self.method.clone()) - .to(move |data: web::Json| async move { - handler(data.into_inner()) - }) - } -} - - -pub struct MyCustomRouteProvider; - -impl CustomRouteProvider for MyCustomRouteProvider { - fn get_routes(&self) -> Vec> { - vec![ - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/custom_get".to_string(), - handler: get_custom_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/custom_post".to_string(), - handler: post_custom_handler, - }), - ] - } -} - - -fn get_custom_handler(query: Value) -> HttpResponse { - log::info!("Received query: {:?}", query); - HttpResponse::Ok().json(json!({ - "message": "GET handler response", - "received_data": query, - })) -} - - -fn post_custom_handler(data: Value) -> HttpResponse { - log::info!("Received data: {:?}", data); - HttpResponse::Ok().json(json!({ - "message": "POST handler response", - "received_data": data, - })) -} - - -pub fn init_plugin(plugin_manager: Arc) { - plugin_manager.register(Arc::new(MyCustomRouteProvider {})); -} diff --git a/src/custom_route/mod.rs b/src/custom_route/mod.rs deleted file mode 100644 index 85cd6e86ea15cd0e10110a70e8884ce8b51c7198..0000000000000000000000000000000000000000 --- a/src/custom_route/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod custom_example; \ No newline at end of file diff --git a/src/datamgr_route/route.rs b/src/datamgr_route/route.rs deleted file mode 100644 index b41ef0e90ade38f833abadb7136dca195e3ca192..0000000000000000000000000000000000000000 --- a/src/datamgr_route/route.rs +++ /dev/null @@ -1,636 +0,0 @@ -use crate::datamgr_route::datamgr_api; -use std::ffi::CString; -use std::ffi::CStr; -use std::os::raw::c_char; -use std::sync::Arc; -use crate::cores::apiserver::{CustomRouteProvider, APIServerRoute}; -use crate::cores::plugin::PluginManager; -use actix_web::{web, HttpRequest, HttpResponse, Route}; -use actix_web::http::Method; -use actix_web::web::{Bytes}; -use serde_json::json; - - - -pub struct SimpleRoute { - pub method: Method, - pub path: String, - pub handler: fn(request: HttpRequest, body: Bytes) -> HttpResponse, -} - -impl APIServerRoute for SimpleRoute { - fn get_method(&self) -> Method { - self.method.clone() - } - - fn get_path(&self) -> String { - self.path.clone() - } - - fn get_web_route(&self) -> Route { - let handler = self.handler; - web::route() - .method(self.method.clone()) - .to(move |request: HttpRequest, body: Bytes| async move { - handler(request, body) - }) - } -} - -struct DataApiRouteProvider; - -static mut DATA_PLUGIN_MANAGER: *mut ::std::os::raw::c_void = ::std::ptr::null_mut(); - -impl DataApiRouteProvider { - // /** - // * upload file - // * req-header: x-data-file-name: File name - // * req-body: file content - // * req-header: x-data-file-description: File description - // * res: Upload success - // */ - fn upload_file_handler(request: HttpRequest, body: Bytes) -> HttpResponse { - - let header_file_name = match request.headers().get("x-data-file-name") { - Some(value) => match value.to_str() { - Ok(s) => s.to_string(), - Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-name header"), - }, - None => return HttpResponse::BadRequest().body("Missing x-data-file-name header"), - }; - - let file_name = match CString::new(header_file_name) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), - }; - - let file_content = body.to_vec(); - - let header_file_description = match request.headers().get("x-data-file-description") { - Some(value) => match value.to_str() { - Ok(s) => s.to_string(), - Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-file-description header"), - }, - None => return HttpResponse::BadRequest().body("Missing x-data-file-description header"), - }; - - let file_description = match CString::new(header_file_description) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid file description"), - }; - unsafe { - let ret = datamgr_api::UploadFile( - DATA_PLUGIN_MANAGER, - file_name.as_ptr(), - file_content.as_ptr() as *const i8, - file_content.len() as i32, - file_description.as_ptr() - ); - if ret == 1 { - HttpResponse::Ok().body("Upload file success") - } else { - HttpResponse::InternalServerError().finish() - } - } - } - - // /** - // * upload status - // * req-header: x-data-status: Status - // * res: Ok = success - // */ - fn upload_status_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - - let header_status = match request.headers().get("x-data-status") { - Some(value) => match value.to_str() { - Ok(s) => s.to_string(), - Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-status header"), - }, - None => return HttpResponse::BadRequest().body("Missing x-data-status header"), - }; - - let status = match CString::new(header_status) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid status"), - }; - - unsafe { - let ret = datamgr_api::UploadStatus( - DATA_PLUGIN_MANAGER, - status.as_ptr() - ); - if ret == 1 { - HttpResponse::Ok().body("Upload status success") - } else { - HttpResponse::InternalServerError().finish() - } - } - } - - // /** - // * upload instruction - // * req-header: x-data-instruction: Instruction - // * res: Ok = success - // */ - fn upload_instruction_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - - let header_instruction = match request.headers().get("x-data-instruction") { - Some(value) => match value.to_str() { - Ok(s) => s.to_string(), - Err(_) => return HttpResponse::BadRequest().body("Invalid x-data-instruction header"), - }, - None => return HttpResponse::BadRequest().body("Missing x-data-instruction header"), - }; - - let instruction = match CString::new(header_instruction) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid instruction"), - }; - - unsafe { - let ret = datamgr_api::UploadInstruction( - DATA_PLUGIN_MANAGER, - instruction.as_ptr() - ); - // println!("ret: {:?}", ret); - if ret == 1 { - HttpResponse::Ok().body("Upload instruction success") - } else { - HttpResponse::InternalServerError().finish() - } - } - } - - // /** - // * download file - // * {filename}: File name in url - // * res-body: File content - // */ - fn download_file_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - - let file_name = request.match_info().get("filename").unwrap(); - let header_file_name = file_name.to_string(); - - let file_name = match CString::new(header_file_name) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), - }; - - unsafe { - let mut file_length: i32 = 0; - let ret = datamgr_api::DownloadFile( - DATA_PLUGIN_MANAGER, - file_name.as_ptr(), - &mut file_length - ); - let res_content = Vec::from_raw_parts(ret as *mut u8, file_length as usize, file_length as usize); - HttpResponse::Ok() - .body(res_content) - } - } - - // delete file - // {filename}: File name in url - // res: Ok = success - fn delete_file_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - - let file_name = request.match_info().get("filename").unwrap(); - let header_file_name = file_name.to_string(); - - let file_name = match CString::new(header_file_name) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), - }; - - unsafe { - let ret = datamgr_api::DeleteFile( - DATA_PLUGIN_MANAGER, - file_name.as_ptr() - ); - if ret == 1 { - HttpResponse::Ok().body("Delete file success") - } else { - HttpResponse::InternalServerError().finish() - } - } - } - - // /** - // * list file - // * res-body: File list - // */ - fn list_file_handler(_request: HttpRequest, _body: Bytes) -> HttpResponse { - unsafe { - let mut index_count: i32 = 0; - let ret = datamgr_api::ListFiles(DATA_PLUGIN_MANAGER, &mut index_count); - let c_array = ret as *const *const c_char; - let mut vec = Vec::new(); - for i in 0..index_count { - let c_str = *c_array.offset(i as isize); - let bytes = CStr::from_ptr(c_str).to_bytes(); - let str_slice = std::str::from_utf8(bytes).unwrap(); - vec.push(str_slice.to_string()); - } - - // free - datamgr_api::FreeData(ret , index_count); - - let json_response = match serde_json::to_string(&vec) { - Ok(json) => json, - Err(e) => { - eprintln!("Failed to convert Vec to JSON: {}", e); - return HttpResponse::InternalServerError().finish(); - } - }; - HttpResponse::Ok() - .content_type("application/json") - .body(json_response) - } - } - - // /** - // * query file - // * {filename}: File name in url - // * res-body: File information - // */ - fn query_file_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - - let file_name = request.match_info().get("filename").unwrap(); - let header_file_name = file_name.to_string(); - - let file_name = match CString::new(header_file_name) { - Ok(cstr) => cstr, - Err(_) => return HttpResponse::BadRequest().body("Invalid file name"), - }; - - unsafe { - let mut index_count: i32 = 0; - let ret = datamgr_api::QueryFile( - DATA_PLUGIN_MANAGER, - file_name.as_ptr(), - &mut index_count - ); - let c_array = ret as *const *const c_char; - let mut vec = Vec::new(); - for i in 0..index_count { - let c_str = *c_array.offset(i as isize); - let bytes = CStr::from_ptr(c_str).to_bytes(); - let str_slice = std::str::from_utf8(bytes).unwrap(); - vec.push(str_slice.to_string()); - } - - // free - datamgr_api::FreeData(ret , index_count); - - let json_response: Vec<_> = vec.iter().map(|s| { - let parts: Vec<&str> = s.split('|').collect(); - json!({ - "file_name": parts.get(0).unwrap_or(&""), - "owner": parts.get(1).unwrap_or(&""), - "description": parts.get(2).unwrap_or(&""), - "avaliability": parts.get(3).unwrap_or(&""), - "operation_type": parts.get(4).unwrap_or(&""), - "operation_detail": parts.get(5).unwrap_or(&""), - "operation_time": parts.get(6).unwrap_or(&"") - }) - }).collect(); - let json_res = match serde_json::to_string(&json_response) { - Ok(json) => json, - Err(e) => { - eprintln!("Failed to convert Vec to JSON: {}", e); - return HttpResponse::InternalServerError().finish(); - } - }; - HttpResponse::Ok() - .content_type("application/json") - .body(json_res) - } - } - - fn receive_normal_data_handler(request: HttpRequest, body: Bytes) -> HttpResponse { - unsafe { - println!("receive_normal_data_handler"); - let header_key = request - .headers() - .get("x-data-key") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let key = CString::new(header_key).unwrap(); - let size_str = request - .headers() - .get("x-data-size") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let size = size_str.parse::().unwrap(); - let mut buffer = body.clone().to_vec(); - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::ReceiveNormalData( - DATA_PLUGIN_MANAGER, - key.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-size", ret)) - .finish() - } - } - - fn set_sync_and_recycle_strategy_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - unsafe { - println!("set_sync_and_recycle_strategy_handler"); - let header_key = request - .headers() - .get("x-data-key") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let key = CString::new(header_key).unwrap(); - let header_sync_strategy = request - .headers() - .get("x-data-sync-strategy") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let sync_strategy = CString::new(header_sync_strategy).unwrap(); - let header_recycle_time = request - .headers() - .get("x-data-recycle-time") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let recycle_time = CString::new(header_recycle_time).unwrap(); - let header_recycle_cascade_keys = request - .headers() - .get("x-data-recycle-cascade-keys") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let recycle_cascade_keys = CString::new(header_recycle_cascade_keys).unwrap(); - - let ret = datamgr_api::SetSyncAndRecycleStrategy( - DATA_PLUGIN_MANAGER, - key.as_ptr(), - sync_strategy.as_ptr(), - recycle_time.as_ptr(), - recycle_cascade_keys.as_ptr(), - ); - if ret == 1 { - // HttpResponse::Ok().finish() - } else { - // HttpResponse::InternalServerError().finish() - } - HttpResponse::Ok().finish() - } - } - - fn submit_user_request_handler(request: HttpRequest, body: Bytes) -> HttpResponse { - unsafe { - println!("submit_user_request_handler"); - let header_key = request - .headers() - .get("x-data-key") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let key = CString::new(header_key).unwrap(); - let size_str = request - .headers() - .get("x-data-size") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let size = size_str.parse::().unwrap(); - let mut buffer = body.clone().to_vec(); - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::SubmitUserRequest( - DATA_PLUGIN_MANAGER, - key.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-size", ret)) - .finish() - } - } - - fn find_data_location_handler(request: HttpRequest, body: Bytes) -> HttpResponse { - unsafe { - println!("find_data_location_handler"); - let header_key = request - .headers() - .get("x-data-key") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let key = CString::new(header_key).unwrap(); - let size = 1024; - let mut buffer = body.clone().to_vec(); - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::FindDataLocation( - DATA_PLUGIN_MANAGER, - key.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-location", buffer[0..ret as usize].to_vec().clone())) - .finish() - } - } - - fn read_data_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - unsafe { - println!("read_data_handler"); - let header_key = request - .headers() - .get("x-data-key") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let key = CString::new(header_key).unwrap(); - let size_str = request - .headers() - .get("x-data-size") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let size = size_str.parse::().unwrap(); - let mut buffer: Vec = vec![0; size as usize]; - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::ReadData( - DATA_PLUGIN_MANAGER, - key.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-size", ret)) - .body(buffer[0..ret as usize].to_vec().clone()) - } - } - - fn register_terminal_handler(request: HttpRequest, body: Bytes) -> HttpResponse { - unsafe { - println!("register_terminal_handler"); - let header_user_token = request - .headers() - .get("x-data-user-token") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let user_token = CString::new(header_user_token).unwrap(); - let size_str = request - .headers() - .get("x-data-user-data-size") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let size = size_str.parse::().unwrap(); - let mut buffer = body.clone().to_vec(); - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::RegisterTerminal( - DATA_PLUGIN_MANAGER, - user_token.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-user-data-size", ret)) - .finish() - } - } - - fn get_terminal_info_handler(request: HttpRequest, _body: Bytes) -> HttpResponse { - unsafe { - println!("get_terminal_info_handler"); - let header_user_token = request - .headers() - .get("x-data-user-token") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let user_token = CString::new(header_user_token).unwrap(); - let size_str = request - .headers() - .get("x-data-user-data-size") - .unwrap() - .to_str() - .unwrap_or_default() - .to_string(); - let size = size_str.parse::().unwrap(); - let mut buffer: Vec = vec![0; size as usize]; - let c_buffer = buffer.as_mut_ptr() as *mut c_char; - let ret = datamgr_api::GetTerminalInfo( - DATA_PLUGIN_MANAGER, - user_token.as_ptr(), - c_buffer, - size, - ); - HttpResponse::Ok() - .append_header(("x-data-user-data-size", ret)) - .body(buffer[0..ret as usize].to_vec().clone()) - } - } -} - -impl CustomRouteProvider for DataApiRouteProvider { - fn get_routes(&self) -> Vec> { - vec![ - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/files".to_string(), - handler: Self::upload_file_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/status".to_string(), - handler: Self::upload_status_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/instructions".to_string(), - handler: Self::upload_instruction_handler, - }), - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/files/{filename}".to_string(), - handler: Self::download_file_handler, - }), - Box::new(SimpleRoute { - method: Method::DELETE, - path: "/data/v1/files/{filename}".to_string(), - handler: Self::delete_file_handler, - }), - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/files".to_string(), - handler: Self::list_file_handler, - }), - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/fileinfos/{filename}".to_string(), - handler: Self::query_file_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/normal".to_string(), - handler: Self::receive_normal_data_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/strategy".to_string(), - handler: Self::set_sync_and_recycle_strategy_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/request".to_string(), - handler: Self::submit_user_request_handler, - }), - Box::new(SimpleRoute { - method: Method::HEAD, - path: "/data/v1/intelligence".to_string(), - handler: Self::find_data_location_handler, - }), - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/intelligence".to_string(), - handler: Self::read_data_handler, - }), - Box::new(SimpleRoute { - method: Method::POST, - path: "/data/v1/terminal".to_string(), - handler: Self::register_terminal_handler, - }), - Box::new(SimpleRoute { - method: Method::GET, - path: "/data/v1/terminal".to_string(), - handler: Self::get_terminal_info_handler, - }), - ] - } -} - -pub fn init_plugin(plugin_manager: Arc, data_plugin_manager: *mut ::std::os::raw::c_void) { - unsafe { - DATA_PLUGIN_MANAGER = data_plugin_manager; - } - plugin_manager.register(Arc::new(DataApiRouteProvider {})); -} diff --git a/src/db/check_exist.rs b/src/db/check_exist.rs index 6d1f82b43212aef58e8ea641c673db09cdc46f29..d50498dcf3f57fe096199b3f0ff166e2d30cd0e9 100644 --- a/src/db/check_exist.rs +++ b/src/db/check_exist.rs @@ -1,186 +1,190 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use diesel::{QueryDsl, QueryResult, RunQueryDsl, ExpressionMethods}; -use crate::db::db::DbConnection; - -// 查询 metadata 表中的 plural 是否存在,并检查 namespace 要求是否满足 -pub async fn check_metadata( - conn: &mut DbConnection, - plural: &str, - version: &str, -) -> QueryResult { - use diesel::dsl::count_star; - use crate::schema::metadata::dsl as metadata_dsl; - use crate::schema::metadata_replica1::dsl as replica1_dsl; - use crate::schema::metadata_replica2::dsl as replica2_dsl; - - let count; - let count_replica1; - let count_replica2; - match conn { - DbConnection::Pg(pg_conn) => { - count = metadata_dsl::metadata - .filter(metadata_dsl::name.eq(plural)) - .filter(metadata_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::metadata_replica1 - .filter(replica1_dsl::name.eq(plural)) - .filter(replica1_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::metadata_replica2 - .filter(replica2_dsl::name.eq(plural)) - .filter(replica2_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(pg_conn)?; - }, - DbConnection::Sqlite(sqlite_conn) => { - count = metadata_dsl::metadata - .filter(metadata_dsl::name.eq(plural)) - .filter(metadata_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::metadata_replica1 - .filter(replica1_dsl::name.eq(plural)) - .filter(replica1_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::metadata_replica2 - .filter(replica2_dsl::name.eq(plural)) - .filter(replica2_dsl::apigroup.eq(version)) - .select(count_star()) - .first::(sqlite_conn)?; - } - } - let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); - Ok(positive_count >= 2) -} - - - -// 查询 kine 表中指定的数据是否存在 -pub async fn check_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult { - use diesel::dsl::count_star; - use crate::schema::kine::dsl as kine_dsl; - use crate::schema::kine_replica1::dsl as replica1_dsl; - use crate::schema::kine_replica2::dsl as replica2_dsl; - - let count; - let count_replica1; - let count_replica2; - match conn { - DbConnection::Pg(pg_conn) => { - if let Some(_) = item_namespace { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .filter(kine_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .filter(replica1_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .filter(replica2_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - } else { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - } - }, - DbConnection::Sqlite(sqlite_conn) => { - if let Some(_) = item_namespace { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .filter(kine_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .filter(replica1_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .filter(replica2_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - } else { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - } - } - } - let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); - Ok(positive_count >= 2) -} \ No newline at end of file +use crate::db::db::DbConnection; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{ExpressionMethods, QueryDsl, QueryResult, RunQueryDsl}; + +// 查询 metadata 表中的 plural 是否存在,并检查 namespace 要求是否满足 +pub async fn check_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, +) -> QueryResult { + use crate::schema::metadata::dsl as metadata_dsl; + use crate::schema::metadata_replica1::dsl as replica1_dsl; + use crate::schema::metadata_replica2::dsl as replica2_dsl; + use diesel::dsl::count_star; + + let count; + let count_replica1; + let count_replica2; + match conn { + DbConnection::Pg(pg_conn) => { + count = metadata_dsl::metadata + .filter(metadata_dsl::name.eq(plural)) + .filter(metadata_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::metadata_replica1 + .filter(replica1_dsl::name.eq(plural)) + .filter(replica1_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::metadata_replica2 + .filter(replica2_dsl::name.eq(plural)) + .filter(replica2_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(pg_conn)?; + } + DbConnection::Sqlite(sqlite_conn) => { + count = metadata_dsl::metadata + .filter(metadata_dsl::name.eq(plural)) + .filter(metadata_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::metadata_replica1 + .filter(replica1_dsl::name.eq(plural)) + .filter(replica1_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::metadata_replica2 + .filter(replica2_dsl::name.eq(plural)) + .filter(replica2_dsl::apigroup.eq(version)) + .select(count_star()) + .first::(sqlite_conn)?; + } + } + let positive_count = [count, count_replica1, count_replica2] + .iter() + .filter(|&&x| x > 0) + .count(); + Ok(positive_count >= 2) +} + +// 查询 kine 表中指定的数据是否存在 +pub async fn check_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult { + use crate::schema::kine::dsl as kine_dsl; + use crate::schema::kine_replica1::dsl as replica1_dsl; + use crate::schema::kine_replica2::dsl as replica2_dsl; + use diesel::dsl::count_star; + + let count; + let count_replica1; + let count_replica2; + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(_) = item_namespace { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .filter(kine_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .filter(replica1_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .filter(replica2_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + } else { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(_) = item_namespace { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .filter(kine_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .filter(replica1_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .filter(replica2_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + } else { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + } + } + } + let positive_count = [count, count_replica1, count_replica2] + .iter() + .filter(|&&x| x > 0) + .count(); + Ok(positive_count >= 2) +} diff --git a/src/db/db.rs b/src/db/db.rs index c621d712d72be33661aac37ca5cf69880bea8acd..887ebe92e1218bd8557195ab9f44c639fb1399a2 100644 --- a/src/db/db.rs +++ b/src/db/db.rs @@ -1,357 +1,347 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use std::collections::HashMap; -use chrono::Utc; -use diesel::pg::PgConnection; -use diesel::{sql_query, QueryResult, RunQueryDsl}; -use diesel::sqlite::SqliteConnection; -use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use serde_json::{json, Value}; -use diesel::connection::Connection; -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); - -#[derive(Debug)] -pub enum DbPool { - Pg(Pool>), - Sqlite(Pool>), -} - -pub enum DbConnection { - Pg(PooledConnection>), - Sqlite(PooledConnection>), -} - -// 定义所有资源模板的静态映射 -fn resource_templates() -> HashMap<&'static str, (bool, Value)> { - let mut templates = HashMap::new(); - templates.insert( - "pods", - ( - true, - json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": "string", - "annotations": "object" - }, - "spec": { - "containers": [ - { - "name": "string", - "image": "string", - "command": "array" - } - ] - } - }), - ), - ); - templates.insert( - "nodes", - ( - false, - json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "string", - "labels": "object" - }, - "spec": { - "podCIDR": "string", - "providerID": "string" - } - }), - ), - ); - templates.insert( - "cronjobs", - ( - true, - json!({ - "apiVersion": "batch/v1", - "kind": "CronJob", - "metadata": { - "name": "string", - "labels": "object" - }, - "spec": { - "template": { - "metadata": { - "labels": "object" - }, - "spec": { - "containers": [ - { - "name": "string", - "image": "string", - "args": "array" - } - ] - } - } - } - }), - ), - ); - templates.insert( - "flowjobs", - ( - true, - json!({ - "apiVersion": "batch/v1", - "kind": "FlowJob", - "metadata": { - "name": "string", - "labels": "object" - }, - "spec": { - "template": { - "metadata": { - "labels": "object" - }, - "spec": { - // todo! - } - } - } - }), - ), - ); - templates.insert( - "jobs", - ( - true, - json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "string", - "labels": "object" - }, - "spec": { - "template": { - "metadata": { - "labels": "object" - }, - "spec": { - "containers": [ - { - "name": "string", - "image": "string", - "args": "array" - } - ] - } - } - } - }), - ), - ); - templates.insert( - "events", - ( - true, - json!({ - "apiVersion": "v1", - "kind": "Event", - "metadata": { - "name": "string", - "labels": "object" - }, - "spec": { - "template": { - "metadata": { - "labels": "object" - }, - "spec": { - "containers": [ - { - "name": "string", - "image": "string", - "args": "array" - } - ] - } - } - } - }), - ), - ); - // 其他资源可以继续添加 - templates -} - -fn initialize_metadata_in_transaction_pg( - transaction: &mut PgConnection, - templates: &HashMap<&str, (bool, Value)>, -) -> QueryResult<()> { - for (resource_name, (supports_namespace, template)) in templates.iter() { - let apigroup = template - .get("apiVersion") - .and_then(Value::as_str) - .unwrap_or("v1"); - - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - let insert_metadata_query = format!( - "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES ('{}', {}, '{}', '{}', '{}', '{}') - ON CONFLICT DO NOTHING;", - table_name, - resource_name, - supports_namespace, - apigroup, - template.to_string(), - Utc::now().naive_utc().to_string(), - Utc::now().naive_utc().to_string() - ); - - sql_query(insert_metadata_query).execute(transaction)?; - } - } - Ok(()) -} - -fn initialize_metadata_in_transaction_sqlite( - transaction: &mut SqliteConnection, - templates: &HashMap<&str, (bool, Value)>, -) -> QueryResult<()> { - for (resource_name, (supports_namespace, template)) in templates.iter() { - let apigroup = template - .get("apiVersion") - .and_then(Value::as_str) - .unwrap_or("v1"); - - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES ('{}', {}, '{}', '{}', '{}', '{}');", - table_name, - resource_name, - supports_namespace, - apigroup, - template.to_string(), - Utc::now().naive_utc().to_string(), - Utc::now().naive_utc().to_string() - ); - - sql_query(insert_metadata_query).execute(transaction)?; - } - } - Ok(()) -} - - -impl DbPool { - - // 初始化所有资源的 metadata 表 - pub fn initialize_metadata(conn: &mut DbConnection) -> QueryResult<()> { - let templates = resource_templates(); - - match conn { - DbConnection::Pg(pg_conn) => { - pg_conn.transaction(|transaction| { - initialize_metadata_in_transaction_pg(transaction, &templates) - }) - } - DbConnection::Sqlite(sqlite_conn) => { - sqlite_conn.transaction(|transaction| { - initialize_metadata_in_transaction_sqlite(transaction, &templates) - }) - } - } - } - - - -/* pub fn initialize_metadata(conn: &mut DbConnection) -> QueryResult<()> { - let templates = resource_templates(); - - for (resource_name, (supports_namespace, template)) in templates.iter() { - - // 从模板中提取 `apiVersion` 字段作为 `apigroup` - let apigroup = template.get("apiVersion") - .and_then(Value::as_str) - .unwrap_or("v1"); - - let table_array: [String; 3] = [ - String::from("metadata"), - String::from("metadata_replica1"), - String::from("metadata_replica2"), - ]; - - for table_name in table_array { - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES ('{}', {}, '{}', '{}', '{}', '{}');", - table_name, - resource_name, - *supports_namespace, - apigroup, - template.to_string(), - Utc::now().naive_utc().to_string(), - Utc::now().naive_utc().to_string() - ); - - match conn { - DbConnection::Pg(pg_conn) => { - sql_query(insert_metadata_query).execute(pg_conn)?; - }, - DbConnection::Sqlite(sqlite_conn) => { - sql_query(insert_metadata_query).execute(sqlite_conn)?; - } - } - } - - } - Ok(()) - }*/ - - // 内存数据库,用于编写单元测试函数 - pub fn new_in_memory() -> Result { - let manager = ConnectionManager::::new("file::memory:?cache=shared"); - let pool = Pool::builder().build(manager)?; - let mut conn = pool.get()?; - conn.run_pending_migrations(MIGRATIONS).unwrap(); - Self::initialize_metadata(&mut DbPool::Sqlite(pool.clone()).get_connection()?).unwrap(); - Ok(DbPool::Sqlite(pool)) - } - - pub fn get_connection(&self) -> Result { - match self { - DbPool::Pg(pool) => pool.get().map(DbConnection::Pg), - DbPool::Sqlite(pool) => pool.get().map(DbConnection::Sqlite), - } - } - pub fn new(database_url: &str) -> Result { - if database_url.starts_with("postgres://") { - let manager = ConnectionManager::::new(database_url); - let pool = Pool::builder().build(manager)?; - // 运行迁移 - let mut conn = pool.get()?; - conn.run_pending_migrations(MIGRATIONS).unwrap(); - Self::initialize_metadata(&mut DbPool::Pg(pool.clone()).get_connection()?).unwrap(); - Ok(DbPool::Pg(pool)) - } else { - let manager = ConnectionManager::::new(database_url); - let pool = Pool::builder().build(manager)?; - // 运行迁移 - let mut conn = pool.get()?; - conn.run_pending_migrations(MIGRATIONS).unwrap(); - Self::initialize_metadata(&mut DbPool::Sqlite(pool.clone()).get_connection()?).unwrap(); - Ok(DbPool::Sqlite(pool)) - } - } -} - +use chrono::Utc; +use diesel::connection::Connection; +use diesel::pg::PgConnection; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; +use diesel::sqlite::SqliteConnection; +use diesel::{sql_query, QueryResult, RunQueryDsl}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use serde_json::{json, Value}; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use std::collections::HashMap; +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + +#[derive(Debug)] +pub enum DbPool { + Pg(Pool>), + Sqlite(Pool>), +} + +pub enum DbConnection { + Pg(PooledConnection>), + Sqlite(PooledConnection>), +} + +// 定义所有资源模板的静态映射 +fn resource_templates() -> HashMap<&'static str, (bool, Value)> { + let mut templates = HashMap::new(); + templates.insert( + "pods", + ( + true, + json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "string", + "annotations": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "command": "array" + } + ] + } + }), + ), + ); + templates.insert( + "nodes", + ( + false, + json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "podCIDR": "string", + "providerID": "string" + } + }), + ), + ); + templates.insert( + "cronjobs", + ( + true, + json!({ + "apiVersion": "batch/v1", + "kind": "CronJob", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "template": { + "metadata": { + "labels": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "args": "array" + } + ] + } + } + } + }), + ), + ); + templates.insert( + "flowjobs", + ( + true, + json!({ + "apiVersion": "batch/v1", + "kind": "FlowJob", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "template": { + "metadata": { + "labels": "object" + }, + "spec": { + // todo! + } + } + } + }), + ), + ); + templates.insert( + "jobs", + ( + true, + json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "template": { + "metadata": { + "labels": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "args": "array" + } + ] + } + } + } + }), + ), + ); + templates.insert( + "events", + ( + true, + json!({ + "apiVersion": "v1", + "kind": "Event", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "template": { + "metadata": { + "labels": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "args": "array" + } + ] + } + } + } + }), + ), + ); + // 其他资源可以继续添加 + templates +} + +fn initialize_metadata_in_transaction_pg( + transaction: &mut PgConnection, + templates: &HashMap<&str, (bool, Value)>, +) -> QueryResult<()> { + for (resource_name, (supports_namespace, template)) in templates.iter() { + let apigroup = template + .get("apiVersion") + .and_then(Value::as_str) + .unwrap_or("v1"); + + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + let insert_metadata_query = format!( + "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES ('{}', {}, '{}', '{}', '{}', '{}') + ON CONFLICT DO NOTHING;", + table_name, + resource_name, + supports_namespace, + apigroup, + template.to_string(), + Utc::now().naive_utc().to_string(), + Utc::now().naive_utc().to_string() + ); + + sql_query(insert_metadata_query).execute(transaction)?; + } + } + Ok(()) +} + +fn initialize_metadata_in_transaction_sqlite( + transaction: &mut SqliteConnection, + templates: &HashMap<&str, (bool, Value)>, +) -> QueryResult<()> { + for (resource_name, (supports_namespace, template)) in templates.iter() { + let apigroup = template + .get("apiVersion") + .and_then(Value::as_str) + .unwrap_or("v1"); + + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES ('{}', {}, '{}', '{}', '{}', '{}');", + table_name, + resource_name, + supports_namespace, + apigroup, + template.to_string(), + Utc::now().naive_utc().to_string(), + Utc::now().naive_utc().to_string() + ); + + sql_query(insert_metadata_query).execute(transaction)?; + } + } + Ok(()) +} + +impl DbPool { + // 初始化所有资源的 metadata 表 + pub fn initialize_metadata(conn: &mut DbConnection) -> QueryResult<()> { + let templates = resource_templates(); + + match conn { + DbConnection::Pg(pg_conn) => pg_conn.transaction(|transaction| { + initialize_metadata_in_transaction_pg(transaction, &templates) + }), + DbConnection::Sqlite(sqlite_conn) => sqlite_conn.transaction(|transaction| { + initialize_metadata_in_transaction_sqlite(transaction, &templates) + }), + } + } + + /* pub fn initialize_metadata(conn: &mut DbConnection) -> QueryResult<()> { + let templates = resource_templates(); + + for (resource_name, (supports_namespace, template)) in templates.iter() { + + // 从模板中提取 `apiVersion` 字段作为 `apigroup` + let apigroup = template.get("apiVersion") + .and_then(Value::as_str) + .unwrap_or("v1"); + + let table_array: [String; 3] = [ + String::from("metadata"), + String::from("metadata_replica1"), + String::from("metadata_replica2"), + ]; + + for table_name in table_array { + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES ('{}', {}, '{}', '{}', '{}', '{}');", + table_name, + resource_name, + *supports_namespace, + apigroup, + template.to_string(), + Utc::now().naive_utc().to_string(), + Utc::now().naive_utc().to_string() + ); + + match conn { + DbConnection::Pg(pg_conn) => { + sql_query(insert_metadata_query).execute(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + sql_query(insert_metadata_query).execute(sqlite_conn)?; + } + } + } + + } + Ok(()) + }*/ + + // 内存数据库,用于编写单元测试函数 + pub fn new_in_memory() -> Result { + let manager = ConnectionManager::::new("file::memory:?cache=shared"); + let pool = Pool::builder().build(manager)?; + let mut conn = pool.get()?; + conn.run_pending_migrations(MIGRATIONS).unwrap(); + Self::initialize_metadata(&mut DbPool::Sqlite(pool.clone()).get_connection()?).unwrap(); + Ok(DbPool::Sqlite(pool)) + } + + pub fn get_connection(&self) -> Result { + match self { + DbPool::Pg(pool) => pool.get().map(DbConnection::Pg), + DbPool::Sqlite(pool) => pool.get().map(DbConnection::Sqlite), + } + } + pub fn new(database_url: &str) -> Result { + if database_url.starts_with("postgres://") { + let manager = ConnectionManager::::new(database_url); + let pool = Pool::builder().build(manager)?; + // 运行迁移 + let mut conn = pool.get()?; + conn.run_pending_migrations(MIGRATIONS).unwrap(); + Self::initialize_metadata(&mut DbPool::Pg(pool.clone()).get_connection()?).unwrap(); + Ok(DbPool::Pg(pool)) + } else { + let manager = ConnectionManager::::new(database_url); + let pool = Pool::builder().build(manager)?; + // 运行迁移 + let mut conn = pool.get()?; + conn.run_pending_migrations(MIGRATIONS).unwrap(); + Self::initialize_metadata(&mut DbPool::Sqlite(pool.clone()).get_connection()?).unwrap(); + Ok(DbPool::Sqlite(pool)) + } + } +} diff --git a/src/db/delete.rs b/src/db/delete.rs index a0de98d9345d27b8af62c364c17e4f06ed34c801..0ea057b67ae76851a69dd4d254cbd9095dc31e4f 100644 --- a/src/db/delete.rs +++ b/src/db/delete.rs @@ -1,92 +1,92 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use diesel::{QueryResult, RunQueryDsl}; -use crate::db::db::DbConnection; - -// 从 kine 表中删除特定 name 的记录 -pub async fn delete_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult { - use diesel::sql_types::Text; - - // 表名列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 遍历每个表,执行删除操作 - let mut total_rows_affected = 0; - - for &table in &tables { - let delete_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "DELETE FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", - table - ), - DbConnection::Sqlite(_) => format!( - "DELETE FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "DELETE FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "DELETE FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - // 执行删除 - let rows_affected = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(pg_conn)? - } else { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(pg_conn)? - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(sqlite_conn)? - } else { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(sqlite_conn)? - } - } - }; - - total_rows_affected += rows_affected; - } - - // 如果至少有两个表进行了删除,则返回 true - Ok(total_rows_affected > 1) -} \ No newline at end of file +use crate::db::db::DbConnection; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{QueryResult, RunQueryDsl}; + +// 从 kine 表中删除特定 name 的记录 +pub async fn delete_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult { + use diesel::sql_types::Text; + + // 表名列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 遍历每个表,执行删除操作 + let mut total_rows_affected = 0; + + for &table in &tables { + let delete_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "DELETE FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", + table + ), + DbConnection::Sqlite(_) => format!( + "DELETE FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "DELETE FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "DELETE FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + // 执行删除 + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + + total_rows_affected += rows_affected; + } + + // 如果至少有两个表进行了删除,则返回 true + Ok(total_rows_affected > 1) +} diff --git a/src/db/get.rs b/src/db/get.rs index 0cad4bf6bb83db3301bf066cf11305f0f57d7dc9..654cbb26cf4d8e3d45487797f93d2625f901d59b 100644 --- a/src/db/get.rs +++ b/src/db/get.rs @@ -1,313 +1,312 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use diesel::{OptionalExtension, QueryResult, QueryableByName, RunQueryDsl}; -use diesel::sql_types::Text; -use crate::db::db::DbConnection; - -// 辅助查询函数,用于获取数据的 `data` 字段 -#[derive(QueryableByName)] -struct DataResult { - #[diesel(sql_type = Text)] - data: String, -} -pub async fn get_data_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - use diesel::sql_types::Text; - use std::collections::HashMap; - - // 表名列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 存储每个表的查询结果 - let mut results = HashMap::new(); - - for &table in &tables { - let select_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - let data_result = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(pg_conn) - .optional()? - .map(|res| res.data) - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(pg_conn) - .optional()? - .map(|res| res.data) - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional()? - .map(|res| res.data) - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional()? - .map(|res| res.data) - } - } - }; - - if let Some(data) = data_result { - *results.entry(data).or_insert(0) += 1; - } - } - - // 按少数服从多数规则返回数据 - if results.len() == 1 { - // 如果三个表的结果一致,直接返回任意结果 - Ok(results.into_iter().next().map(|(data, _)| data)) - } else if results.values().all(|&count| count == 1) { - // 如果所有表结果不同,直接回退到 kine 表的数据 - get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) - } else if let Some((data, _)) = results.into_iter().max_by_key(|&(_, count)| count) { - // 如果有多数一致的数据,返回该数据 - Ok(Some(data)) - } else { - // 默认回退到 kine 表的数据 - get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) - } -} - -/// 获取主表 `kine` 的数据 -fn get_data_from_kine_primary( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - let fallback_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4".to_string(), - DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?".to_string(), - } - } else { - match conn { - DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND apigroup = $3".to_string(), - DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND apigroup = ?".to_string(), - } - }; - - match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(pg_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } else { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(pg_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } else { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } - } - } -} - - - - -// 辅助函数:从指定表中获取所有符合条件的数据的 `data` 字段 -pub async fn get_all_data_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - use diesel::sql_types::Text; - use std::collections::HashMap; - - // 定义需要查询的表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 存储每个表的查询结果 - let mut table_results: HashMap<&str, Vec> = HashMap::new(); - - // 遍历每个表进行查询 - for &table in &tables { - let select_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND namespace = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND apigroup = $2", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND apigroup = ?", - table - ), - } - }; - - // 执行查询 - let results: Vec = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(namespace) - .bind::(item_version) - .load::(pg_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_version) - .load::(pg_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(namespace) - .bind::(item_version) - .load::(sqlite_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_version) - .load::(sqlite_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } - } - }; - - table_results.insert(table, results); - } - - // 检查所有表的数据是否一致 - let vec1 = table_results[tables[0]].clone(); - let vec2 = table_results[tables[1]].clone(); - let vec3 = table_results[tables[2]].clone(); - - let max_len = vec1.len().max(vec2.len()).max(vec3.len()); - let mut filtered_results = Vec::new(); - - // 筛选出出现次数大于等于 2 的数据 - for i in 0..max_len { - // 获取当前索引的值,越界的填入 None - let value1 = vec1.get(i); - let value2 = vec2.get(i); - let value3 = vec3.get(i); - - // 统计每个值的出现次数 - let mut counts = HashMap::new(); - if let Some(v) = value1 { - *counts.entry(v).or_insert(0) += 1; - } - if let Some(v) = value2 { - *counts.entry(v).or_insert(0) += 1; - } - if let Some(v) = value3 { - *counts.entry(v).or_insert(0) += 1; - } - - // 找出出现次数大于等于 2 的值 - if let Some((value, &_)) = counts.iter().find(|(_, &count)| count >= 2) { - filtered_results.push((*value).clone()); // 将该值存入结果 - } - - } - - Ok(filtered_results) -} +use crate::db::db::DbConnection; +use diesel::sql_types::Text; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{OptionalExtension, QueryResult, QueryableByName, RunQueryDsl}; + +// 辅助查询函数,用于获取数据的 `data` 字段 +#[derive(QueryableByName)] +struct DataResult { + #[diesel(sql_type = Text)] + data: String, +} +pub async fn get_data_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + use diesel::sql_types::Text; + use std::collections::HashMap; + + // 表名列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 存储每个表的查询结果 + let mut results = HashMap::new(); + + for &table in &tables { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + let data_result = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(pg_conn) + .optional()? + .map(|res| res.data) + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(pg_conn) + .optional()? + .map(|res| res.data) + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional()? + .map(|res| res.data) + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional()? + .map(|res| res.data) + } + } + }; + + if let Some(data) = data_result { + *results.entry(data).or_insert(0) += 1; + } + } + + // 按少数服从多数规则返回数据 + if results.len() == 1 { + // 如果三个表的结果一致,直接返回任意结果 + Ok(results.into_iter().next().map(|(data, _)| data)) + } else if results.values().all(|&count| count == 1) { + // 如果所有表结果不同,直接回退到 kine 表的数据 + get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) + } else if let Some((data, _)) = results.into_iter().max_by_key(|&(_, count)| count) { + // 如果有多数一致的数据,返回该数据 + Ok(Some(data)) + } else { + // 默认回退到 kine 表的数据 + get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) + } +} + +/// 获取主表 `kine` 的数据 +fn get_data_from_kine_primary( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + let fallback_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4".to_string(), + DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?".to_string(), + } + } else { + match conn { + DbConnection::Pg(_) => { + "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND apigroup = $3".to_string() + } + DbConnection::Sqlite(_) => { + "SELECT data FROM kine WHERE kind = ? AND name = ? AND apigroup = ?".to_string() + } + } + }; + + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + } + } +} + +// 辅助函数:从指定表中获取所有符合条件的数据的 `data` 字段 +pub async fn get_all_data_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + use diesel::sql_types::Text; + use std::collections::HashMap; + + // 定义需要查询的表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 存储每个表的查询结果 + let mut table_results: HashMap<&str, Vec> = HashMap::new(); + + // 遍历每个表进行查询 + for &table in &tables { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND namespace = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND apigroup = $2", + table + ), + DbConnection::Sqlite(_) => { + format!("SELECT data FROM {} WHERE kind = ? AND apigroup = ?", table) + } + } + }; + + // 执行查询 + let results: Vec = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(namespace) + .bind::(item_version) + .load::(pg_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_version) + .load::(pg_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(namespace) + .bind::(item_version) + .load::(sqlite_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_version) + .load::(sqlite_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } + } + }; + + table_results.insert(table, results); + } + + // 检查所有表的数据是否一致 + let vec1 = table_results[tables[0]].clone(); + let vec2 = table_results[tables[1]].clone(); + let vec3 = table_results[tables[2]].clone(); + + let max_len = vec1.len().max(vec2.len()).max(vec3.len()); + let mut filtered_results = Vec::new(); + + // 筛选出出现次数大于等于 2 的数据 + for i in 0..max_len { + // 获取当前索引的值,越界的填入 None + let value1 = vec1.get(i); + let value2 = vec2.get(i); + let value3 = vec3.get(i); + + // 统计每个值的出现次数 + let mut counts = HashMap::new(); + if let Some(v) = value1 { + *counts.entry(v).or_insert(0) += 1; + } + if let Some(v) = value2 { + *counts.entry(v).or_insert(0) += 1; + } + if let Some(v) = value3 { + *counts.entry(v).or_insert(0) += 1; + } + + // 找出出现次数大于等于 2 的值 + if let Some((value, &_)) = counts.iter().find(|(_, &count)| count >= 2) { + filtered_results.push((*value).clone()); // 将该值存入结果 + } + } + + Ok(filtered_results) +} diff --git a/src/db/insert.rs b/src/db/insert.rs index 528c9efb199e2dee7ebb336f155c704dac32de19..4ce77ac5ab9b5f96ef9721b9f1d1e5b2f8ce7aec 100644 --- a/src/db/insert.rs +++ b/src/db/insert.rs @@ -1,197 +1,211 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use chrono::Utc; -use diesel::{sql_query, Connection, PgConnection, QueryResult, RunQueryDsl, SqliteConnection}; -use diesel::sql_types::Text; -use serde_json::Value; -use crate::db::db::DbConnection; - -fn insert_metadata_in_transaction_pg( - transaction: &mut PgConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value, -) -> QueryResult<()> { - use diesel::sql_types::{Bool}; - - // 表名列表 - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT DO NOTHING;", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(plural) // 名称 - .bind::(namespace_required) // 是否需要命名空间 - .bind::(version) // 版本 - .bind::(json_data.to_string()) // JSON 数据 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -fn insert_metadata_in_transaction_sqlite( - transaction: &mut SqliteConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value, -) -> QueryResult<()> { - use diesel::sql_types::{Bool}; - - // 表名列表 - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES (?, ?, ?, ?, ?, ?);", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(plural) // 名称 - .bind::(namespace_required) // 是否需要命名空间 - .bind::(version) // 版本 - .bind::(json_data.to_string()) // JSON 数据 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -pub async fn insert_metadata( - conn: &mut DbConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value -) -> QueryResult<()> { - match conn { - DbConnection::Pg(pg_conn) => { - pg_conn.transaction(|transaction| { - insert_metadata_in_transaction_pg(transaction, plural, version, namespace_required, json_data) - }) - } - DbConnection::Sqlite(sqlite_conn) => { - sqlite_conn.transaction(|transaction| { - insert_metadata_in_transaction_sqlite(transaction, plural, version, namespace_required, json_data) - }) - } - }.expect("unknow conn in insert_metadata"); - Ok(()) -} - -fn insert_kine_in_transaction_pg( - transaction: &mut PgConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - - // 表列表 - let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT DO NOTHING;", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace.unwrap_or("")) // 空字符串作为默认 namespace - .bind::(version) - .bind::(json_data.to_string()) // 将 JSON 数据转换为字符串 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -fn insert_kine_in_transaction_sqlite( - transaction: &mut SqliteConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - - let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; - - for table_name in table_array { - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) - VALUES (?, ?, ?, ?, ?, ?, ?);", - table_name - ); - - sql_query(insert_metadata_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace.unwrap_or("")) // 使用 Nullable 处理空值 - .bind::(version) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .execute(transaction)?; - } - Ok(()) -} - - -// 在 kine 表中插入新记录 -pub async fn insert_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - match conn { - DbConnection::Pg(pg_conn) => { - pg_conn.transaction(|transaction| { - insert_kine_in_transaction_pg(transaction, item_kind, item_name, json_data, version, namespace) - }) - } - DbConnection::Sqlite(sqlite_conn) => { - sqlite_conn.transaction(|transaction| { - insert_kine_in_transaction_sqlite(transaction, item_kind, item_name, json_data, version, namespace) - }) - } - }.expect("unknow conn in insert_kine"); - Ok(()) -} \ No newline at end of file +use crate::db::db::DbConnection; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use chrono::Utc; +use diesel::sql_types::Text; +use diesel::{sql_query, Connection, PgConnection, QueryResult, RunQueryDsl, SqliteConnection}; +use serde_json::Value; + +fn insert_metadata_in_transaction_pg( + transaction: &mut PgConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value, +) -> QueryResult<()> { + use diesel::sql_types::Bool; + + // 表名列表 + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT DO NOTHING;", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(plural) // 名称 + .bind::(namespace_required) // 是否需要命名空间 + .bind::(version) // 版本 + .bind::(json_data.to_string()) // JSON 数据 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + +fn insert_metadata_in_transaction_sqlite( + transaction: &mut SqliteConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value, +) -> QueryResult<()> { + use diesel::sql_types::Bool; + + // 表名列表 + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES (?, ?, ?, ?, ?, ?);", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(plural) // 名称 + .bind::(namespace_required) // 是否需要命名空间 + .bind::(version) // 版本 + .bind::(json_data.to_string()) // JSON 数据 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + +pub async fn insert_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value, +) -> QueryResult<()> { + match conn { + DbConnection::Pg(pg_conn) => pg_conn.transaction(|transaction| { + insert_metadata_in_transaction_pg( + transaction, + plural, + version, + namespace_required, + json_data, + ) + }), + DbConnection::Sqlite(sqlite_conn) => sqlite_conn.transaction(|transaction| { + insert_metadata_in_transaction_sqlite( + transaction, + plural, + version, + namespace_required, + json_data, + ) + }), + } + .expect("unknow conn in insert_metadata"); + Ok(()) +} + +fn insert_kine_in_transaction_pg( + transaction: &mut PgConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + // 表列表 + let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ON CONFLICT DO NOTHING;", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace.unwrap_or("")) // 空字符串作为默认 namespace + .bind::(version) + .bind::(json_data.to_string()) // 将 JSON 数据转换为字符串 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + +fn insert_kine_in_transaction_sqlite( + transaction: &mut SqliteConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; + + for table_name in table_array { + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) + VALUES (?, ?, ?, ?, ?, ?, ?);", + table_name + ); + + sql_query(insert_metadata_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace.unwrap_or("")) // 使用 Nullable 处理空值 + .bind::(version) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .execute(transaction)?; + } + Ok(()) +} + +// 在 kine 表中插入新记录 +pub async fn insert_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + match conn { + DbConnection::Pg(pg_conn) => pg_conn.transaction(|transaction| { + insert_kine_in_transaction_pg( + transaction, + item_kind, + item_name, + json_data, + version, + namespace, + ) + }), + DbConnection::Sqlite(sqlite_conn) => sqlite_conn.transaction(|transaction| { + insert_kine_in_transaction_sqlite( + transaction, + item_kind, + item_name, + json_data, + version, + namespace, + ) + }), + } + .expect("unknow conn in insert_kine"); + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs index efafe2300b8c4e24f285238376e3a0db3ba03a33..df26d31a51c54f7e115bdea3cce3dfa23bf2a415 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,12 +1,12 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -pub mod db; -pub mod check_exist; -pub mod insert; -pub mod update; -pub mod get; -pub mod delete; \ No newline at end of file +pub mod check_exist; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +pub mod db; +pub mod delete; +pub mod get; +pub mod insert; +pub mod update; diff --git a/src/db/update.rs b/src/db/update.rs index 9bc38498bd68d09327bce5d3ba7eea567a708226..3d31f6e055495844166f8cf512a68ef53e3a8959 100644 --- a/src/db/update.rs +++ b/src/db/update.rs @@ -1,100 +1,100 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ -use diesel::{QueryResult, RunQueryDsl}; -use serde_json::Value; -use crate::db::db::DbConnection; - -pub async fn update_data_in_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, - json_data: &Value, -) -> QueryResult { - use diesel::sql_types::Text; - use chrono::Utc; - - // 需要更新的表列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - let mut total_rows_affected = 0; - - for &table in &tables { - let update_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND namespace = $5 AND apigroup = $6", - table - ), - DbConnection::Sqlite(_) => format!( - "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND apigroup = $5", - table - ), - DbConnection::Sqlite(_) => format!( - "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - // 执行更新操作 - let rows_affected = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(pg_conn)? - } else { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(pg_conn)? - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(sqlite_conn)? - } else { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(sqlite_conn)? - } - } - }; - - total_rows_affected += rows_affected; - } - - // 如果至少有两个表更新成功,则返回 true - Ok(total_rows_affected > 1) -} +use crate::db::db::DbConnection; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{QueryResult, RunQueryDsl}; +use serde_json::Value; + +pub async fn update_data_in_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, + json_data: &Value, +) -> QueryResult { + use chrono::Utc; + use diesel::sql_types::Text; + + // 需要更新的表列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + let mut total_rows_affected = 0; + + for &table in &tables { + let update_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND namespace = $5 AND apigroup = $6", + table + ), + DbConnection::Sqlite(_) => format!( + "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND apigroup = $5", + table + ), + DbConnection::Sqlite(_) => format!( + "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + // 执行更新操作 + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + + total_rows_affected += rows_affected; + } + + // 如果至少有两个表更新成功,则返回 true + Ok(total_rows_affected > 1) +} diff --git a/src/lib.rs b/src/lib.rs index 6eb2a4dec3bdb4a98eb87fcef313186fc768495d..fa8796fd7b0fdcb776c69bb70bed104a1e8f625f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,16 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -pub mod cores; -pub mod schema; -pub mod db; -pub mod custom_route; -pub mod datamgr_route; -pub mod cluster_info_route; - -pub use cores::{prepare_app_state, start_server}; +pub mod cores; +pub mod db; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +pub mod schema; +pub mod utils; + +pub use cores::{prepare_app_state, start_server}; + +pub use cores::handlers::api_server::Params; +pub use cores::models::{ServerResponse, ServerRawResponse, ServerJsonStreamResponse, ServerJsonResponse, ServerError, ServerStatusCode}; +pub use cores::daemons::messaging; \ No newline at end of file diff --git a/src/utils/headers.rs b/src/utils/headers.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f264467a2c601ece2fbb5a1a3c84cf6c3ca995b --- /dev/null +++ b/src/utils/headers.rs @@ -0,0 +1,14 @@ +use actix_http::header::HeaderMap; +use std::collections::HashMap; + +pub fn into_map(header_map: HeaderMap) -> HashMap { + let mut headers = HashMap::new(); + for (key, value) in header_map.iter() { + let value_str = value.to_str(); + if value_str.is_err() { + continue; + } + headers.insert(key.as_str().to_string(), value_str.unwrap().to_string()); + } + headers +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5d6d7da6c2ece643ca6c577282c7ee32a8853aed --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod headers; +mod uuid; +pub mod test; + +pub use uuid::*; diff --git a/src/utils/test.rs b/src/utils/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..cd6f864ae1dd497358c4cdc2d2cb42ace8e6c21d --- /dev/null +++ b/src/utils/test.rs @@ -0,0 +1,82 @@ +use std::ffi::CString; +use std::sync::Arc; +use feventbus::impls::messaging::messaging::Messaging; +use feventbus::traits::controller::EventBus; +use crate::cores::handlers::datamgr::datamgr_api; +use crate::cores::models::AppState; +use crate::cores::servers; +use crate::cores::servers::{MessagingServer, Server}; +use crate::prepare_app_state; + +#[allow(unused)] +pub async fn setup_message_cli() -> Messaging { + let my_id = 0; + + let data_plugin_manager = unsafe { datamgr_api::NewPluginManager(my_id) }; + + let plugin_to_load_key = CString::new("core.pluginsToLoad").unwrap(); + let plugin_to_load_value = CString::new("Messaging Storage Portal").unwrap(); + + unsafe { + datamgr_api::SetParameter( + data_plugin_manager, + plugin_to_load_key.as_ptr(), + plugin_to_load_value.as_ptr(), + ); + datamgr_api::LoadPlugins(data_plugin_manager); + } + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + let mut messaging_client = Messaging::new().await.unwrap(); + messaging_client.set_plugin_manager(data_plugin_manager); + messaging_client +} + +#[allow(unused)] +pub fn tear_down_message_cli(messaging_client: Arc) { + let plugin_manager = messaging_client.get_plugin_manager().expect("Plugin manager is not set"); + unsafe { + datamgr_api::StopUdp(plugin_manager); + datamgr_api::UnloadPlugins(plugin_manager); + datamgr_api::DeletePluginManager(plugin_manager); + } +} + +#[allow(unused)] +pub async fn start_test_api_server(app_state: AppState, address: &str) -> AppState { + // 启动watch相关事件监听协程 + let app_state_watch = app_state.clone(); + tokio::spawn(async move { + app_state_watch.watch_daemon.start().await; + }); + + log::info!("Starting test API server"); + // 启动各个server + let messaging_server: Box = Box::new(MessagingServer); + let actix_web_server: Box = Box::new(servers::ActixWebServer::new(address)); + let messaging_server_app_state = app_state.clone(); + tokio::spawn(async move { + messaging_server.start(messaging_server_app_state).await; + }); + let actix_web_server_app_state = app_state.clone(); + tokio::spawn(async move { + let _ = actix_web_server.start(actix_web_server_app_state).await; + }); + app_state +} + +#[allow(unused)] +pub async fn setup_test_env(address: Option<&str>) -> (Arc, Arc) { + const DATABASE_PATH: &str = "./test-database.sqlite"; + const DATABASE_URL_PREFIX: &str = "sqlite://"; + let address: String = address.unwrap_or("127.0.0.1:39090").to_string(); + // 删除数据库文件 + std::fs::remove_file(DATABASE_PATH).unwrap_or_default(); + let msg_cli = Arc::new(setup_message_cli().await); + let app_state = prepare_app_state(format!("{}{}", DATABASE_URL_PREFIX, DATABASE_PATH).as_str(), msg_cli.clone()).await.expect("prepare app state failed"); + let app_state_moved = app_state.clone(); + tokio::spawn(async move { + start_test_api_server(app_state_moved, address.as_str()).await; + }); + (msg_cli, Arc::new(app_state)) +} \ No newline at end of file diff --git a/src/utils/uuid.rs b/src/utils/uuid.rs new file mode 100644 index 0000000000000000000000000000000000000000..daf9112d6409d62c2754c6be318ae996747253a3 --- /dev/null +++ b/src/utils/uuid.rs @@ -0,0 +1,24 @@ +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use uuid::Uuid; + +pub fn get_or_create_uuid(file_path: &PathBuf) -> io::Result { + let path = Path::new(file_path); + + if path.exists() { + let uuid = fs::read_to_string(path)?.trim().to_string(); + return Ok(uuid); + } + + let uuid = Uuid::new_v4().to_string(); + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let mut file = fs::File::create(path)?; + file.write_all(uuid.as_bytes())?; + + Ok(uuid) +} diff --git a/tests/datamgr_test.rs b/tests/datamgr_test.rs index 17f9e7f25888e80a2a903e4720f48b9a962f7d1e..ff932238e99d9ddea86ebfcdfe12480bfb0da8eb 100644 --- a/tests/datamgr_test.rs +++ b/tests/datamgr_test.rs @@ -1,14 +1,14 @@ use actix_web::rt::Runtime; -use reqwest::Client; +use fleet_apiserver::cores::plugin::PluginManager; +use fleet_apiserver::datamgr_route::datamgr_api; +use fleet_apiserver::datamgr_route::route; use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest::Client; +use serde_json::Value; use std::error::Error; use std::sync::Arc; use std::sync::Mutex; use std::thread; -use serde_json::Value; -use fleet_apiserver::datamgr_route::datamgr_api; -use fleet_apiserver::datamgr_route::route; -use fleet_apiserver::cores::plugin::PluginManager; const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; const ADDRESS: &str = "0.0.0.0:8080"; @@ -21,25 +21,32 @@ unsafe impl Send for SafePtr {} #[link(name = "fleet-datamgr")] extern "C" {} - #[tokio::test] async fn test_datamgr() -> Result<(), Box> { - unsafe { let plugin_manager = Arc::new(PluginManager::new()); let data_plugin_manager = Arc::new(Mutex::new(SafePtr(datamgr_api::NewPluginManager()))); - - // portal-plugin.so 及 storage-plugin.so 的位置 - let plugin_file_path = std::ffi::CString::new("/root/project/fleet-datamgr/plugins/release").unwrap(); + + // portal-plugin.so 及 storage-plugin.so 的位置 + let plugin_file_path = + std::ffi::CString::new("/root/project/fleet-datamgr/plugins/release").unwrap(); let plugin_manager = plugin_manager.clone(); let data_plugin_manager = Arc::clone(&data_plugin_manager); let server_handle = thread::spawn(move || { let rt = Runtime::new().unwrap(); rt.block_on(async { - datamgr_api::LoadPluginsFromDirectory(data_plugin_manager.lock().unwrap().0, plugin_file_path.as_ptr()); - route::init_plugin(plugin_manager.clone(), data_plugin_manager.lock().unwrap().0); - if let Err(e) = fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await { + datamgr_api::LoadPluginsFromDirectory( + data_plugin_manager.lock().unwrap().0, + plugin_file_path.as_ptr(), + ); + route::init_plugin( + plugin_manager.clone(), + data_plugin_manager.lock().unwrap().0, + ); + if let Err(e) = + fleet_apiserver::start_server(DATABASE_URL, ADDRESS, plugin_manager).await + { eprintln!("Failed to start server: {}", e); } datamgr_api::UnloadAllPlugins(data_plugin_manager.lock().unwrap().0); @@ -48,19 +55,14 @@ async fn test_datamgr() -> Result<(), Box> { }); server_handle.join().unwrap(); - } - // 等待服务器启动 tokio::time::sleep(std::time::Duration::from_secs(3)).await; - - - let client = Client::new(); let mut headers = HeaderMap::new(); - + // uplaod file test // url: http://localhost:8080/data/self/file // method: POST @@ -68,7 +70,7 @@ async fn test_datamgr() -> Result<(), Box> { // req-header: x-data-file-description: this is a test file // req-body: file content: "hello world" // res: 200 OK - + // headers.insert("x-data-file-name", HeaderValue::from_str("test2.txt")?); // headers.insert("x-data-file-description", HeaderValue::from_str("this is a test file")?); let res = client @@ -80,13 +82,12 @@ async fn test_datamgr() -> Result<(), Box> { let status = res.status(); println!("{:?}", status); - // upload status // url: http://localhost:8080/data/other/status // method: POST // req-header: x-data-status: Status = "success" // res: Ok = success - + headers.clear(); headers.insert("x-data-status", HeaderValue::from_str("success")?); let res = client @@ -96,12 +97,11 @@ async fn test_datamgr() -> Result<(), Box> { .await?; println!("{:?}", res.status()); - // upload instruction // url: http://localhost:8080/data/user/instruction // req-header: x-data-instruction: Instruction = "Install" // res: Ok = success - + headers.clear(); headers.insert("x-data-instruction", HeaderValue::from_str("Install")?); let res = client @@ -111,14 +111,13 @@ async fn test_datamgr() -> Result<(), Box> { .await?; println!("{:?}", res.status()); - // download file // url: http://localhost:8080/data/self/file // method: GET // req-header: x-data-file-name: File name = "test.txt" // res-header: x-data-file-name: File name = "test.txt" // res-body: File content - + headers.clear(); headers.insert("x-data-file-name", HeaderValue::from_str("test.txt")?); let res = client @@ -131,13 +130,12 @@ async fn test_datamgr() -> Result<(), Box> { println!("{}", text); } - // query file // url: http://localhost:8080/data/self/fileinfo // method: GET // req-header: x-data-file-name: File name = "test.txt" // res-body: File info - + headers.clear(); headers.insert("x-data-file-name", HeaderValue::from_str("test")?); let res = client @@ -149,4 +147,4 @@ async fn test_datamgr() -> Result<(), Box> { println!("{:?}", json); Ok(()) -} \ No newline at end of file +} diff --git a/tests/server_tests.rs b/tests/server_tests.rs index 9bd4331b845adc648fe82d50a4ada6e0d339d76e..35a2c1f4b239f069a31b136f00fbefb61208f098 100644 --- a/tests/server_tests.rs +++ b/tests/server_tests.rs @@ -1,426 +1,402 @@ -#[cfg(test)] -mod tests { - use super::*; - use actix_web::{test, web, App, Error, HttpResponse}; - use anyhow::Result; - use env_logger::{Builder, Target}; - use fleet_apiserver::cores::apiserver::{AppState, K8sStylePathParams, K8sStyleRoute}; - use fleet_apiserver::cores::events::{APIServerEventClient, EventTopic, P2PEventServer, P2PEventTopic, PubSubEventTopic, ResourceCollectionIdentifier, WatchEventMessage, WatchEventMessageResource}; - use fleet_apiserver::cores::handlers::APIServerResponse; - use fleet_apiserver::cores::services::{APIServerResult, APIServerServiceParams, APIServerServiceParamsBuilder, APIServerStatusCode}; - use fleet_apiserver::prepare_app_state; - use fleetmod::pod::Pod; - use serde_json::{json, Value}; - use serial_test::serial; - use std::sync::Arc; - use std::sync::atomic::AtomicUsize; - use std::time; - use feventbus::impls::nats::nats::NatsCli; - use feventbus::message::Message; - use feventbus::traits::consumer::Consumer; - use feventbus::traits::controller::EventBus; - use fleetmod::utils::APIVersion; - use tokio::time::{sleep, timeout}; - - - const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; - - #[macro_export] - macro_rules! setup_test_app {() => { - { - let app_state = setup().await.unwrap(); - let app = App::new().app_data(web::Data::new(app_state.clone())); - let routes = K8sStyleRoute::get_routes(); - let app = routes.iter().fold(app, |app, route| { - log::info!("register route: {} {}", route.get_method(), route.get_path()); - app.route(route.get_path().as_str(), route.get_web_route()) - }); - let app = app.default_service(web::to(move || async { - log::error!("invalid url"); - Ok::( - HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) - })); - let app = test::init_service(app).await; - (app, app_state) - }}; - } - - async fn setup() -> Result { - let mut builder = Builder::from_default_env(); - builder.target(Target::Stdout); - let _ = builder.try_init(); - - log::info!("Setting DATABASE_URL to {}", DATABASE_URL); - let app_state = prepare_app_state(DATABASE_URL).await; - if let Err(e) = app_state { - log::error!("Failed to prepare app state: {:?}", e); - return Err(e); - } - let app_state = app_state?; - // 启动watch相关事件监听协程 - app_state.clone().watch_event_publisher.start(); - // 启动P2P事件监听协程 - P2PEventServer::new(Arc::from(app_state.clone())).start().await; - // 等待启动 - sleep(time::Duration::from_secs(2)).await; - Ok(app_state.clone()) - } - - #[tokio::test] - #[serial] - async fn test_pod_basic() { - let (app, app_state) = setup_test_app!(); - - let name = "test-create-pod".to_string(); - let namespace = "ns1".to_string(); - let version = "v1".to_string(); - let plural = "pods".to_string(); - let pod_value = mock_pod(name.clone(), Some(namespace.clone())); - let pod: Pod = serde_json::from_value(pod_value.clone()).unwrap(); - let with_name_params = K8sStylePathParams { group: None, version: version.clone(), namespace: Some(namespace.clone()), name: Some(name.clone()), plural: plural.clone() }; - let without_name_params = K8sStylePathParams { group: None, version: version.clone(), namespace: Some(namespace.clone()), name: None, plural: plural.clone() }; - - // delete it first - let pod_delete_url = get_url(with_name_params.clone()); - log::info!("pod_delete_url: {}", pod_delete_url); - let req = test::TestRequest::delete().uri(pod_delete_url.as_str()).to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("delete res {:?}", res); - - // test create - let pod_create_url = get_url(without_name_params.clone()); - let req = test::TestRequest::post() - .uri(pod_create_url.as_str()) - .set_json(&pod_value) - .to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("create res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::OK); - let pod_from_create = serde_json::from_value(res.data.unwrap()).unwrap(); - assert_eq!(pod, pod_from_create); - - // test get one - let pod_getone_url = get_url(with_name_params.clone()); - let req = test::TestRequest::get().uri(pod_getone_url.as_str()).to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("getone res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::OK); - assert!(res.data.is_some()); - let pod_from_getone: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); - assert_eq!(pod, pod_from_getone); - - // test list - let pod_list_url = get_url(without_name_params.clone()); - let req = test::TestRequest::get().uri(pod_list_url.as_str()).to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("list res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::OK); - assert!(res.data.is_some()); - let pods_from_list: Vec = serde_json::from_value(res.data.unwrap()).unwrap(); - let found = pods_from_list.iter().any(|p| p.metadata.name == name); - assert!(found); - let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == name).unwrap(); - assert_eq!(pod, *pod_from_list); - - // test patch - let pod_patch_url = get_url(with_name_params.clone()); - let mut target_pod = pod.clone(); - target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); - target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); - let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(target_pod.clone()).unwrap()); - let patch = serde_json::to_value(&diff).unwrap(); - let req = test::TestRequest::patch() - .uri(pod_patch_url.as_str()) - .set_json(patch) - .to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("patch res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::OK); - let pod_patched: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); - assert_eq!(target_pod, pod_patched); - - // test delete - let pod_delete_url = get_url(with_name_params.clone()); - let req = test::TestRequest::delete().uri(pod_delete_url.as_str()).to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("delete res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::OK); - let pod_deleted: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); - assert_eq!(target_pod, pod_deleted); - let req = test::TestRequest::get().uri(pod_getone_url.as_str()).to_request(); - let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; - log::info!("get res {:?}", res); - assert_eq!(res.status_code, APIServerStatusCode::NotFound); - - // test by eventbus - let event_cli = APIServerEventClient::new(app_state.nats_cli, None); - let with_name_params = APIServerServiceParams::from(with_name_params.clone()); - let without_name_params = APIServerServiceParams::from(without_name_params.clone()); - - // test create - let res = event_cli - .create_by_resource(pod.clone()) - .await; - log::info!("event cli create res {:?}", res); - assert_eq!(pod, res.ok().unwrap()); - - // test get one - let res = event_cli - .get(with_name_params.clone(), Value::Null) - .await; - log::info!("event cli getone res {:?}", res); - assert!(res.is_ok()); - assert_eq!(pod, res.ok().unwrap()); - - // test list - let res = event_cli - .get(without_name_params.clone(), Value::Null) - .await; - log::info!("event cli list res {:?}", res); - assert!(res.is_ok()); - let pods_from_list: Vec = res.ok().unwrap(); - let found = pods_from_list.iter().any(|p| p.metadata.name == name); - assert!(found); - let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == name).unwrap(); - assert_eq!(pod, *pod_from_list); - - // test patch - let mut target_pod = pod.clone(); - target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); - target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); - let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(target_pod.clone()).unwrap()); - let patch = serde_json::to_value(&diff).unwrap(); - let res = event_cli - .patch(with_name_params.clone(), patch).await; - log::info!("patch result: {:?}", res); - assert!(res.is_ok()); - assert_eq!(target_pod, res.ok().unwrap()); - - // test delete - let res = event_cli - .delete(with_name_params.clone()) - .await; - log::info!("event cli delete res {:?}", res); - assert_eq!(target_pod, res.ok().unwrap()); - let res: APIServerResult = event_cli - .get(with_name_params.clone(), Value::Null) - .await; - log::info!("event cli get res {:?}", res); - assert!(res.is_err()); - assert_eq!(res.err().unwrap().status_code, APIServerStatusCode::NotFound); - } - - #[tokio::test] - #[serial] - async fn test_watch_pod() { - let (app, _) = setup_test_app!(); - - let name = "test-create-pod".to_string(); - let namespace = "ns1".to_string(); - let version = "v1".to_string(); - let plural = "pods".to_string(); - let kind = "Pod".to_string(); - let pod_value: Value = mock_pod(name.clone(), Some(namespace.clone())); - let pod: Pod = serde_json::from_value(pod_value.clone()).unwrap(); - - let with_name_params = APIServerServiceParamsBuilder::new() - .name(name.clone()) - .version(version.clone()) - .namespace(namespace.clone()) - .plural(plural.clone()).build().unwrap(); - - let ri = ResourceCollectionIdentifier { - api_version: APIVersion { group: None, version: version.to_string() }, - kind: kind.to_string(), - namespace: Some(namespace.clone()), - }; - let ri_topic: EventTopic = EventTopic::PubSub(PubSubEventTopic::Watch(ri.clone())); - let topic_str = ri_topic.to_string(); - let got_watch_msg_cnt = Arc::new(AtomicUsize::new(0)); - let got_watch_msg_cnt_cloned = got_watch_msg_cnt.clone(); - - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_cli = APIServerEventClient::new(nats_cli.clone(), None); - - // 在进行watch之前先删除,保证资源不存在 - let res: APIServerResult = event_cli.delete(with_name_params.clone()).await; - log::info!("event cli delete res {:?}", res.is_ok()); - - log::info!("watch event subscribing {}", topic_str); - let mut watch_value_receiver = event_cli.watch::(ri.clone()).await.unwrap(); - - // 等待subscribe订阅成功 - sleep(time::Duration::from_secs(1)).await; - log::info!("watch event subscribed"); - tokio::spawn(async move { - loop { - while let Some(resource) = watch_value_receiver.recv().await { - match resource { - WatchEventMessageResource::Created(pod) => { - log::info!("watched created pod"); - }, - WatchEventMessageResource::Updated(pod) => { - log::info!("watched updated pod"); - }, - WatchEventMessageResource::Deleted(pod) => { - log::info!("watched deleted pod"); - }, - } - got_watch_msg_cnt_cloned.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - if got_watch_msg_cnt_cloned.load(std::sync::atomic::Ordering::SeqCst) >= 3 { - break; - } - } - } - }); - - // 等待watcher启动 - sleep(time::Duration::from_secs(1)).await; - - // do create - let res = event_cli.create_by_resource(pod.clone()).await; - log::info!("event cli create res {:?}", res.is_ok()); - assert_eq!(pod, res.ok().unwrap()); - - // do patch - let mut target_pod = pod.clone(); - target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); - target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); - let diff = json_patch::diff(&serde_json::to_value(pod.clone()).unwrap(), &serde_json::to_value(target_pod.clone()).unwrap()); - let patch = serde_json::to_value(&diff).unwrap(); - let res = event_cli.patch(with_name_params.clone(), patch).await; - log::info!("event cli patch res {:?}", res.is_ok()); - assert!(res.is_ok()); - assert_eq!(target_pod, res.ok().unwrap()); - - // do delete - let res = event_cli.delete(with_name_params.clone()).await; - log::info!("event cli delete res {:?}", res.is_ok()); - assert_eq!(target_pod, res.ok().unwrap()); - - // 等待直到收到3个watch消息才算成功 - let max_wait_secs = 20; - timeout(time::Duration::from_secs(max_wait_secs), async { - loop { - let got_watch_msg_cnt = got_watch_msg_cnt.load(std::sync::atomic::Ordering::SeqCst); - if got_watch_msg_cnt >= 3 { - break; - } - sleep(time::Duration::from_secs(1)).await; - } - }).await.unwrap(); - log::info!("watch test done"); - } - - fn get_url(params: K8sStylePathParams) -> String { - let with_group = params.group.is_some(); - let with_namespace = params.namespace.is_some(); - let with_name = params.name.is_some(); - let prefix = if with_group { - format!("/apis/{}/", params.group.unwrap()) - } else { - "/api/".to_string() - }; - let version_and_namespace = if with_namespace { - format!("{}/namespaces/{}/", params.version, params.namespace.unwrap()) - } else { - format!("{}/", params.version) - }; - let plural_and_name = if with_name { - format!("{}/{}", params.plural, params.name.unwrap()) - } else { - format!("{}", params.plural) - }; - format!("{}{}{}", prefix, version_and_namespace, plural_and_name) - } - - fn mock_pod(name: String, namespace: Option) -> Value { - let pod_yaml = r#" -apiVersion: v1 -kind: Pod -metadata: - name: example-pod - namespace: default - labels: - app: my-app - creationTimestamp: 2024-12-10T08:00:00+08:00 -spec: - nodeName: my-node - hostname: my-hostname - hostAliases: - - ip: "127.0.0.1" - hostnames: - - "my-local-host" - - "another-host" - containers: - - name: example-container - image: example-image:latest - imagePullPolicy: IfNotPresent - command: ["nginx"] - args: ["-g", "daemon off;"] - workingDir: /usr/share/nginx/html - ports: - - name: http - containerPort: 80 - protocol: TCP - - name: https - containerPort: 443 - protocol: TCP - env: - - name: ENV_MODE - value: production - - name: ENV_VERSION - valueFrom: - fieldRef: - fieldPath: metadata.name - resources: - requests: - memory: "128Mi" - cpu: "250m" - limits: - memory: "512Mi" - cpu: "1" - volumeMounts: - - name: config-volume - mountPath: /etc/nginx/conf.d - readOnly: true - - name: data-volume - mountPath: /usr/share/nginx/html - - name: data-host-volume - mountPath: /usr/share/nginx/a.txt - volumeDevices: - - name: device-volume - devicePath: /dev/sdb - securityContext: - runAsUser: 1000 - runAsGroup: 1000 - readOnlyRootFilesystem: true - allowPrivilegeEscalation: true - privileged: true - volumes: - - name: example-volume - configMap: - name: nginx-config - - name: data-volume - emptyDir: {} - - name: device-volume - hostPath: - path: /dev/sdb - type: Directory - - name: device-volume - hostPath: - path: /dev/sdb - type: Directory -status: - phase: Pending - message: begin handle - podIP: 10.42.0.9 - podIPs: - - ip: 10.42.0.9 -"#; - let mut pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML"); - pod.metadata.name = name.to_string(); - pod.metadata.namespace = namespace.map(|s| s.to_string()); - serde_json::to_value(pod).unwrap() - } -} +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use super::*; + use actix_web::{test, web, App, Error, HttpResponse}; + use anyhow::Result; + use env_logger::{Builder, Target}; + use fleet_apiserver::{prepare_app_state, start_server}; + use fleetmod::pod::Pod; + use fleetmod::utils::APIVersion; + use serde_json::{json, Value}; + use serial_test::serial; + use std::sync::atomic::AtomicUsize; + use std::sync::Arc; + use std::time; + use actix_web::web::Data; + use paperclip::actix::OpenApiExt; + use tokio::time::{sleep, timeout}; + use fleet_apiserver::cores::models::{AppState, ServerJsonResponse, ServerResponse, ServerStatusCode}; + use fleet_apiserver::cores::servers; + + const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; + + async fn setup() -> AppState { + let mut builder = Builder::from_default_env(); + builder.target(Target::Stdout); + let _ = builder.try_init(); + + let msg_cli = fleet_apiserver::utils::test::setup_message_cli().await; + log::info!("Setting DATABASE_URL to {}", DATABASE_URL); + let app_state = prepare_app_state(DATABASE_URL, Arc::new(msg_cli)).await.unwrap(); + // 启动watch相关事件监听协程 + let app_state_watch = app_state.clone(); + tokio::spawn(async move { + app_state_watch.watch_daemon.start().await; + }); + // 等待启动 + sleep(time::Duration::from_secs(2)).await; + app_state.clone() + } + + #[tokio::test] + #[serial] + async fn test_setup() { + let _ = setup().await; + // hang up + sleep(time::Duration::from_secs(1000)).await; + } + + fn get_url(version: &str, plural: &str, name: &str) -> String { + format!("/resource/get/{version}/{plural}/{name}") + } + + fn create_url(version: &str, plural: &str) -> String { + format!("/resource/create/{version}/{plural}") + } + + fn list_url(version: &str, plural: &str) -> String { + format!("/resource/get/{version}/{plural}") + } + + fn patch_url(version: &str, plural: &str, name: &str) -> String { + format!("/resource/patch/{version}/{plural}/{name}") + } + + fn delete_url(version: &str, plural: &str, name: &str) -> String { + format!("/resource/delete/{version}/{plural}/{name}") + } + + #[tokio::test] + #[serial] + async fn test_pod_basic() { + let app_state = setup().await; + let app = test::init_service(App::new() + .wrap_api() + .app_data(Data::new(app_state.clone())) + .configure(servers::actix_web::api_server::configure_routes) + .configure(servers::actix_web::cluster_info::configure_routes) + .configure(servers::actix_web::datamgr::configure_routes) + .with_json_spec_at("/api/spec/v2") + .build() + ).await; + + let name = "testpodname".to_string(); + let version = "v1".to_string(); + let plural = "pods".to_string(); + let pod_value = mock_pod(name.clone(), Some("default".to_string())); + let pod: Pod = serde_json::from_value(pod_value.clone()).unwrap(); + + // delete it first + let pod_delete_url = delete_url(version.as_str(), plural.as_str(), name.as_str()); + log::info!("pod_delete_url: {}", pod_delete_url); + let req = test::TestRequest::delete() + .uri(pod_delete_url.as_str()) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("delete res {:?}", res); + + // test create + let pod_create_url = create_url(version.as_str(), plural.as_str()); + let req = test::TestRequest::post() + .uri(pod_create_url.as_str()) + .set_json(&pod_value) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("create res {:?}", res); + let pod_from_create = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(pod, pod_from_create); + + // test get one + let pod_getone_url = get_url(version.as_str(), plural.as_str(), name.as_str()); + let req = test::TestRequest::get() + .uri(pod_getone_url.as_str()) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("getone res {:?}", res); + assert!(res.data.is_some()); + let pod_from_getone: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(pod, pod_from_getone); + + // test list + let pod_list_url = list_url(version.as_str(), plural.as_str()); + let req = test::TestRequest::get() + .uri(pod_list_url.as_str()) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("list res {:?}", res); + assert!(res.data.is_some()); + let pods_from_list: Vec = serde_json::from_value(res.data.unwrap()).unwrap(); + let found = pods_from_list.iter().any(|p| p.metadata.name == name); + assert!(found); + let pod_from_list = pods_from_list + .iter() + .find(|p| p.metadata.name == name) + .unwrap(); + assert_eq!(pod, *pod_from_list); + + // test patch + let pod_patch_url = patch_url(version.as_str(), plural.as_str(), name.as_str()); + let mut target_pod = pod.clone(); + target_pod.metadata.labels = Some(HashMap::from([ + ("app".to_string(), "my-app-patched".to_string()), + ("patch-new-key".to_string(), "patch-new-value".to_string()) + ])); + let diff = json_patch::diff( + &serde_json::to_value(pod.clone()).unwrap(), + &serde_json::to_value(target_pod.clone()).unwrap(), + ); + let patch = serde_json::to_value(&diff).unwrap(); + let req = test::TestRequest::patch() + .uri(pod_patch_url.as_str()) + .set_json(patch) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("patch res {:?}", res); + let pod_patched: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(target_pod, pod_patched); + + // test delete + let pod_delete_url = delete_url(version.as_str(), plural.as_str(), name.as_str()); + let req = test::TestRequest::delete() + .uri(pod_delete_url.as_str()) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("delete res {:?}", res); + let pod_deleted: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(target_pod, pod_deleted); + let req = test::TestRequest::get() + .uri(pod_getone_url.as_str()) + .to_request(); + let res: ServerJsonResponse = test::call_and_read_body_json(&app, req).await; + log::info!("get res {:?}", res); + assert_eq!(res.status_code, ServerStatusCode::NotFound); + + // // test by eventbus + // let event_cli = APIServerEventClient::new(app_state.nats_cli, None); + // let with_name_params = APIServerServiceParams::from(with_name_params.clone()); + // let without_name_params = APIServerServiceParams::from(without_name_params.clone()); + // + // // test create + // let res = event_cli.create_by_resource(pod.clone()).await; + // log::info!("event cli create res {:?}", res); + // assert_eq!(pod, res.ok().unwrap()); + // + // // test get one + // let res = event_cli.get(with_name_params.clone(), Value::Null).await; + // log::info!("event cli getone res {:?}", res); + // assert!(res.is_ok()); + // assert_eq!(pod, res.ok().unwrap()); + // + // // test list + // let res = event_cli + // .get(without_name_params.clone(), Value::Null) + // .await; + // log::info!("event cli list res {:?}", res); + // assert!(res.is_ok()); + // let pods_from_list: Vec = res.ok().unwrap(); + // let found = pods_from_list.iter().any(|p| p.metadata.name == name); + // assert!(found); + // let pod_from_list = pods_from_list + // .iter() + // .find(|p| p.metadata.name == name) + // .unwrap(); + // assert_eq!(pod, *pod_from_list); + // + // // test patch + // let mut target_pod = pod.clone(); + // target_pod + // .metadata + // .labels + // .as_mut() + // .unwrap() + // .insert("app".to_string(), "my-app-patched".to_string()); + // target_pod + // .metadata + // .labels + // .as_mut() + // .unwrap() + // .insert("patch-new-key".to_string(), "patch-new-value".to_string()); + // let diff = json_patch::diff( + // &serde_json::to_value(pod.clone()).unwrap(), + // &serde_json::to_value(target_pod.clone()).unwrap(), + // ); + // let patch = serde_json::to_value(&diff).unwrap(); + // let res = event_cli.patch(with_name_params.clone(), patch).await; + // log::info!("patch result: {:?}", res); + // assert!(res.is_ok()); + // assert_eq!(target_pod, res.ok().unwrap()); + // + // // test delete + // let res = event_cli.delete(with_name_params.clone()).await; + // log::info!("event cli delete res {:?}", res); + // assert_eq!(target_pod, res.ok().unwrap()); + // let res: APIServerResult = event_cli.get(with_name_params.clone(), Value::Null).await; + // log::info!("event cli get res {:?}", res); + // assert!(res.is_err()); + // assert_eq!( + // res.err().unwrap().status_code, + // APIServerStatusCode::NotFound + // ); + } +// +// #[tokio::test] +// #[serial] +// async fn test_watch_pod() { +// let (app, _) = setup_test_app!(); +// +// let name = "test-create-pod".to_string(); +// let namespace = "ns1".to_string(); +// let version = "v1".to_string(); +// let plural = "pods".to_string(); +// let kind = "Pod".to_string(); +// let pod_value: Value = mock_pod(name.clone(), Some(namespace.clone())); +// let pod: Pod = serde_json::from_value(pod_value.clone()).unwrap(); +// +// let with_name_params = APIServerServiceParamsBuilder::new() +// .name(name.clone()) +// .version(version.clone()) +// .namespace(namespace.clone()) +// .plural(plural.clone()) +// .build() +// .unwrap(); +// +// let ri = ResourceCollectionIdentifier { +// api_version: APIVersion { +// group: None, +// version: version.to_string(), +// }, +// kind: kind.to_string(), +// namespace: Some(namespace.clone()), +// }; +// let ri_topic: EventTopic = EventTopic::PubSub(PubSubEventTopic::Watch(ri.clone())); +// let topic_str = ri_topic.to_string(); +// let got_watch_msg_cnt = Arc::new(AtomicUsize::new(0)); +// let got_watch_msg_cnt_cloned = got_watch_msg_cnt.clone(); +// +// let nats_cli = Arc::new(NatsCli::new().await.unwrap()); +// let event_cli = APIServerEventClient::new(nats_cli.clone(), None); +// +// // 在进行watch之前先删除,保证资源不存在 +// let res: APIServerResult = event_cli.delete(with_name_params.clone()).await; +// log::info!("event cli delete res {:?}", res.is_ok()); +// +// log::info!("watch event subscribing {}", topic_str); +// let mut watch_value_receiver = event_cli.watch::(ri.clone()).await.unwrap(); +// +// // 等待subscribe订阅成功 +// sleep(time::Duration::from_secs(1)).await; +// log::info!("watch event subscribed"); +// tokio::spawn(async move { +// loop { +// while let Some(resource) = watch_value_receiver.recv().await { +// match resource { +// WatchEventMessageResource::Created(pod) => { +// log::info!("watched created pod"); +// } +// WatchEventMessageResource::Updated(pod) => { +// log::info!("watched updated pod"); +// } +// WatchEventMessageResource::Deleted(pod) => { +// log::info!("watched deleted pod"); +// } +// } +// got_watch_msg_cnt_cloned.fetch_add(1, std::sync::atomic::Ordering::SeqCst); +// if got_watch_msg_cnt_cloned.load(std::sync::atomic::Ordering::SeqCst) >= 3 { +// break; +// } +// } +// } +// }); +// +// // 等待watcher启动 +// sleep(time::Duration::from_secs(1)).await; +// +// // do create +// let res = event_cli.create_by_resource(pod.clone()).await; +// log::info!("event cli create res {:?}", res.is_ok()); +// assert_eq!(pod, res.ok().unwrap()); +// +// // do patch +// let mut target_pod = pod.clone(); +// target_pod +// .metadata +// .labels +// .as_mut() +// .unwrap() +// .insert("app".to_string(), "my-app-patched".to_string()); +// target_pod +// .metadata +// .labels +// .as_mut() +// .unwrap() +// .insert("patch-new-key".to_string(), "patch-new-value".to_string()); +// let diff = json_patch::diff( +// &serde_json::to_value(pod.clone()).unwrap(), +// &serde_json::to_value(target_pod.clone()).unwrap(), +// ); +// let patch = serde_json::to_value(&diff).unwrap(); +// let res = event_cli.patch(with_name_params.clone(), patch).await; +// log::info!("event cli patch res {:?}", res.is_ok()); +// assert!(res.is_ok()); +// assert_eq!(target_pod, res.ok().unwrap()); +// +// // do delete +// let res = event_cli.delete(with_name_params.clone()).await; +// log::info!("event cli delete res {:?}", res.is_ok()); +// assert_eq!(target_pod, res.ok().unwrap()); +// +// // 等待直到收到3个watch消息才算成功 +// let max_wait_secs = 20; +// timeout(time::Duration::from_secs(max_wait_secs), async { +// loop { +// let got_watch_msg_cnt = got_watch_msg_cnt.load(std::sync::atomic::Ordering::SeqCst); +// if got_watch_msg_cnt >= 3 { +// break; +// } +// sleep(time::Duration::from_secs(1)).await; +// } +// }) +// .await +// .unwrap(); +// log::info!("watch test done"); +// } +// +// fn get_url(params: K8sStylePathParams) -> String { +// let with_group = params.group.is_some(); +// let with_namespace = params.namespace.is_some(); +// let with_name = params.name.is_some(); +// let prefix = if with_group { +// format!("/apis/{}/", params.group.unwrap()) +// } else { +// "/api/".to_string() +// }; +// let version_and_namespace = if with_namespace { +// format!( +// "{}/namespaces/{}/", +// params.version, +// params.namespace.unwrap() +// ) +// } else { +// format!("{}/", params.version) +// }; +// let plural_and_name = if with_name { +// format!("{}/{}", params.plural, params.name.unwrap()) +// } else { +// format!("{}", params.plural) +// }; +// format!("{}{}{}", prefix, version_and_namespace, plural_and_name) +// } +// + fn mock_pod(name: String, namespace: Option) -> Value { + let mut pod: Pod = Pod::default(); + pod.api_version = APIVersion { + group: None, + version: "v1".to_string(), + }; + pod.kind = "Pod".to_string(); + pod.metadata.name = name.to_string(); + pod.metadata.namespace = namespace.map(|s| s.to_string()); + serde_json::to_value(pod).unwrap() + } +}