From 4088c736fa9cc0934f7065dca466a4a6e3a357cc Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 16 May 2023 15:00:27 -0400 Subject: [PATCH 01/60] working deployment of pods --- src/main.rs | 1066 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1066 insertions(+) create mode 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5649e4d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,1066 @@ +// use std::{path::PathBuf, str::FromStr}; + +use std::{collections::BTreeMap, time::Duration}; + +// use k8s_experimentation::Thing; +use k8s_openapi::{ + api::core::v1::{ + ConfigMap, ConfigMapEnvSource, ConfigMapVolumeSource, Container, ContainerPort, + EnvFromSource, EnvVar, HostPathVolumeSource, PersistentVolumeClaim, + PersistentVolumeClaimSpec, PersistentVolumeClaimVolumeSource, Pod, ResourceRequirements, + Service, ServicePort, Volume, VolumeMount, + }, + apimachinery::pkg::api::resource::Quantity, +}; +use serde_json::json; + +use kube::{ + api::{Api, ListParams, PostParams, ResourceExt}, + runtime::wait::{await_condition, conditions::is_pod_running}, + Client, +}; +use std::thread::sleep; + +#[tokio::main] +async fn main() -> Result<(), Box> { + const BITCOIN_NODE_SERVICE_NAME: &str = "bitcoin-node-service"; + const ORCHESTRATOR_SERVICE_NAME: &str = "orchestrator-service"; + + // values from user config + // let user_id = "user-id"; + // let user_project_name = "my-project"; + let namespace = "px-devnet"; //format!("{user_id}-{user_project_name}-devnet"); + let bitcoin_node_image = "quay.io/hirosystems/bitcoind:devnet-v3"; + let bitcoin_node_p2p_port = "18444"; + let bitcoin_node_rpc_port = "18443"; + let stacks_node_image = "quay.io/hirosystems/stacks-node:devnet-v3"; + let stacks_node_p2p_port = "20444"; + let stacks_node_rpc_port = "20443"; + let stacks_miner_secret_key_hex = + "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801"; + let miner_stx_address = "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5"; + let stacks_node_wait_time_for_microblocks: u32 = 50; + let stacks_node_first_attempt_time_ms: u32 = 500; + let stacks_node_subsequent_attempt_time_ms: u32 = 1_000; + let orchestrator_ingestion_port = "20445"; + + let stacks_api_image = "hirosystems/stacks-blockchain-api"; + let chain_coordinator_image = "stacks-network"; + + deploy_bitcoin_node_pod( + &namespace, + &bitcoin_node_image, + &bitcoin_node_p2p_port, + &bitcoin_node_rpc_port, + BITCOIN_NODE_SERVICE_NAME, + ) + .await?; + + deploy_chain_coordinator( + &namespace, + &chain_coordinator_image, + ORCHESTRATOR_SERVICE_NAME, + ) + .await?; + + sleep(Duration::from_secs(10)); + + deploy_stacks_node_pod( + &namespace, + &stacks_node_image, + &stacks_node_p2p_port, + &stacks_node_rpc_port, + &stacks_miner_secret_key_hex, + &stacks_node_wait_time_for_microblocks, + &stacks_node_first_attempt_time_ms, + &stacks_node_subsequent_attempt_time_ms, + &miner_stx_address, + // &bitcoin_node_p2p_port, + // &bitcoin_node_rpc_port, + // &orchestrator_ingestion_port, + // BITCOIN_NODE_SERVICE_NAME, + // ORCHESTRATOR_SERVICE_NAME, + ) + .await?; + + deploy_stacks_api_pod(&namespace, &stacks_api_image).await?; + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + // Watch it phase for a few seconds + let establish = await_condition(pods_api.clone(), "bitcoin-node", is_pod_running()); + let _ = tokio::time::timeout(std::time::Duration::from_secs(15), establish).await?; + + // Verify we can get it + println!("Get Pod bitcoin-node"); + let p1cpy = pods_api.get("bitcoin-node").await?; + if let Some(spec) = &p1cpy.spec { + println!( + "Got bitcoin-node pod with containers: {:?}", + spec.containers + ); + } + + let lp = ListParams::default().fields(&format!("metadata.name={}", "bitcoin-node")); // only want results for our pod + for p in pods_api.list(&lp).await? { + println!("Found Pod: {}", p.name_any()); + } + + // Delete it + // let dp = DeleteParams::default(); + // pods.delete("blog", &dp).await?.map_left(|pdel| { + // assert_eq!(pdel.name_any(), "blog"); + // println!("Deleting blog pod started: {:?}", pdel); + // }); + + Ok(()) +} + +async fn deploy_bitcoin_node_pod( + namespace: &str, + image: &str, + p2p_port: &str, + rpc_port: &str, + service_name: &str, +) -> Result<(), Box> { + // constants for bitcoin pod, services, and config + const POD_NAME: &str = "bitcoin-node"; + const CONTAINER_NAME: &str = "bitcoin-node-container"; + const CONFIGMAP_NAME: &str = "bitcoin-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "bitcoin-conf-volume"; + + // massage user data + let p2p_port = p2p_port.parse::()?; + let rpc_port = rpc_port.parse::()?; + + // deploy config map for bitcoin node + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let bitcoind_conf = format!( + r#" + server=1 + regtest=1 + rpcallowip=0.0.0.0/0 + rpcallowip=::/0 + rpcuser={namespace} + rpcpassword={namespace} + txindex=1 + listen=1 + discover=0 + dns=0 + dnsseed=0 + listenonion=0 + rpcworkqueue=100 + rpcserialversion=1 + disablewallet=0 + fallbackfee=0.00001 + + [regtest] + bind=0.0.0.0:{p2p_port} + rpcbind=0.0.0.0:{rpc_port} + rpcport={rpc_port} + "# + ); + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "bitcoin.conf": bitcoind_conf + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy bitcoin node pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let bitcoin_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": {"name": POD_NAME} + }, + "spec": { + "containers": Some(vec![ Container { + name: CONTAINER_NAME.into(), + image: Some(image.into()), + command: Some(vec![ + "/usr/local/bin/bitcoind".into(), + "-conf=/etc/bitcoin/bitcoin.conf".into(), + "-nodebuglogfile".into(), + "-pid=/run/bitcoind.pid".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ContainerPort { + container_port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ContainerPort { + container_port: 20445, + protocol: Some("TCP".into()), + name: Some("orchestrator".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ VolumeMount { + name: CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/etc/bitcoin".into(), + read_only: Some(true), + ..Default::default() + }]), + ..Default::default() + }]), + "volumes": Some(vec![ + Volume { + name: CONFIGMAP_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: Some(CONFIGMAP_NAME.into()) + , ..Default::default() + }), + ..Default::default() + }]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &bitcoin_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service to communicate with container + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": service_name, + "namespace": namespace + }, + "spec": { + "type": "NodePort", + "ports": Some(vec![ServicePort { + port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + node_port: Some(30000), + ..Default::default() + },ServicePort { + port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + node_port: Some(30001), + ..Default::default() + }]), + "selector": {"name": POD_NAME}, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} + +async fn deploy_stacks_node_pod( + namespace: &str, + image: &str, + p2p_port: &str, + rpc_port: &str, + miner_secret_key_hex: &str, + wait_time_for_microblocks: &u32, + first_attempt_time_ms: &u32, + subsequent_attempt_time_ms: &u32, + miner_coinbase_recipient: &str, + // bitcoin_node_p2p_port: &str, + // bitcoin_node_rpc_port: &str, + // orchestrator_ingestion_port: &str, + // bitcoin_node_service_name: &str, + // orchestrator_service_name: &str, +) -> Result<(), Box> { + // constants for stacks pod, services, and config + const POD_NAME: &str = "stacks-node"; + const CONTAINER_NAME: &str = "stacks-node-container"; + const CONFIGMAP_NAME: &str = "stacks-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "stacks-conf-volume"; + const SERVICE_NAME: &str = "stacks-node-service"; + + // massage user data + let p2p_port = p2p_port.parse::()?; + let rpc_port = rpc_port.parse::()?; + + // deploy config map for stacks node + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let mut stacks_conf = format!( + r#" + [node] + working_dir = "/devnet" + rpc_bind = "0.0.0.0:{rpc_port}" + p2p_bind = "0.0.0.0:{p2p_port}" + miner = true + seed = "{miner_secret_key_hex}" + local_peer_seed = "{miner_secret_key_hex}" + pox_sync_sample_secs = 0 + wait_time_for_blocks = 0 + wait_time_for_microblocks = {wait_time_for_microblocks} + microblock_frequency = 1000 + + [connection_options] + # inv_sync_interval = 10 + # download_interval = 10 + # walk_interval = 10 + disable_block_download = true + disable_inbound_handshakes = true + disable_inbound_walks = true + public_ip_address = "1.1.1.1:1234" + + [miner] + first_attempt_time_ms = {first_attempt_time_ms} + subsequent_attempt_time_ms = {subsequent_attempt_time_ms} + block_reward_recipient = "{miner_coinbase_recipient}" + # microblock_attempt_time_ms = 15000 + "# + ); + + let balance: u64 = 100_000_000_000_000; + stacks_conf.push_str(&format!( + r#" + [[ustx_balance]] + address = "{miner_coinbase_recipient}" + amount = {balance} + "# + )); + + let cluster_domain = "cluster.local"; + + stacks_conf.push_str(&format!( + r#" +# Add orchestrator (docker-host) as an event observer +[[events_observer]] +endpoint = "host.docker.internal:30008" +retry_count = 255 +include_data_events = true +events_keys = ["*"] +"# + )); + + // stacks_conf.push_str(&format!( + // r#" + // # Add stacks-api as an event observer + // [[events_observer]] + // endpoint = "host.docker.internal:{}" + // retry_count = 255 + // include_data_events = false + // events_keys = ["*"] + // "#, + // 30007, + // )); + + stacks_conf.push_str(&format!( + r#" +[burnchain] +chain = "bitcoin" +mode = "krypton" +poll_time_secs = 1 +timeout = 30 +peer_host = "host.docker.internal" +rpc_ssl = false +wallet_name = "devnet" +username = "{namespace}" +password = "{namespace}" +rpc_port = {} +peer_port = {} +"#, + 30008, 30000 + )); + + let pox_2_activation = 112; + let epoch_2_0 = 100; + let epoch_2_05 = 102; + let epoch_2_1 = 106; + stacks_conf.push_str(&format!( + r#"pox_2_activation = {pox_2_activation} + +[[burnchain.epochs]] +epoch_name = "1.0" +start_height = 0 + +[[burnchain.epochs]] +epoch_name = "2.0" +start_height = {epoch_2_0} + +[[burnchain.epochs]] +epoch_name = "2.05" +start_height = {epoch_2_05} + +[[burnchain.epochs]] +epoch_name = "2.1" +start_height = {epoch_2_1} + "# + )); + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "Stacks.toml": stacks_conf + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy stacks node pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let bitcoin_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": {"name": POD_NAME} + }, + "spec": { + "containers": Some(vec![ Container { + name: CONTAINER_NAME.into(), + image: Some(image.into()), + command: Some(vec![ + "stacks-node".into(), + "start".into(), + "--config=/src/stacks-node/Stacks.toml".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ContainerPort { + container_port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ]), + env: Some(vec![ + EnvVar { + name: String::from("STACKS_LOG_PP"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_LOG_DEBUG"), + value: Some(String::from("0")), + ..Default::default() + } + ]), + volume_mounts: Some(vec![ VolumeMount { + name: CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/src/stacks-node".into(), + read_only: Some(true), + ..Default::default() + }]), + ..Default::default() + }]), + "volumes": Some(vec![ + Volume { + name: CONFIGMAP_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: Some(CONFIGMAP_NAME.into()) + , ..Default::default() + }), + ..Default::default() + }]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &bitcoin_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service to communicate with container + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": SERVICE_NAME, + "namespace": namespace + }, + "spec": { + "type": "NodePort", + "ports": Some(vec![ServicePort { + port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + node_port: Some(30002), + ..Default::default() + },ServicePort { + port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + node_port: Some(30003), + ..Default::default() + }]), + "selector": {"name": POD_NAME}, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} + +async fn deploy_stacks_api_pod( + namespace: &str, + image: &str, +) -> Result<(), Box> { + // constants for stacks pod, services, and config + const POD_NAME: &str = "stacks-api"; + const POSTGRES_POD_NAME: &str = "stacks-api-postgres"; + const CONTAINER_NAME: &str = "stacks-api-container"; + const POSTGRES_CONFIGMAP_NAME: &str = "stacks-api-postgres-conf"; + const PVC_NAME: &str = "stacks-api-pvc"; + const STORAGE_CLASS_NAME: &str = "stacks-api-storage-class"; + const POSTGRES_CONFIGMAP_VOLUME_NAME: &str = "stacks-api-postgres-conf-volume"; + const SERVICE_NAME: &str = "stacks-api-service"; + + // deploy config map for stacks api + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": POSTGRES_CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "POSTGRES_PASSWORD": "postgres", + "POSTGRES_DB": "stacks_api", + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy storage class + // { + // let client = Client::try_default().await?; + // let storage_class_api: Api = kube::Api::all(client); + + // let storage_class: StorageClass = serde_json::from_value(json!({ + // "apiVersion": "storage.k8s.io/v1", + // "kind": "StorageClass", + // "metadata": { + // "name": STORAGE_CLASS_NAME, + // "namespace": namespace, + // "labels": { + // "app": "my-app", + // }, + // "annotations": { + // "openebs.io/cas-type": "local", + // "cas.openebs.io/config": "| + // - name: StorageType + // value: hostpath + // - name: BasePath + // value: /var/local-hostpath" + // } + // }, + // "provisioner": "openebs.io/local", + // "volume_binding_modes": "WaitForFirstConsumer" + // }))?; + + // let post_params = PostParams::default(); + // let created_config = storage_class_api + // .create(&post_params, &storage_class) + // .await?; + // let name = created_config.name_any(); + // assert_eq!(storage_class.name_any(), name); + // println!("Created {}", name); + // } + + // deploy persistent volume claim + { + let client = Client::try_default().await?; + let pvc_api: Api = + kube::Api::::namespaced(client, &namespace); + + let mut requests_map: BTreeMap = BTreeMap::new(); + requests_map.insert("storage".to_string(), Quantity("500Mi".to_string())); + let mut limits_map: BTreeMap = BTreeMap::new(); + limits_map.insert("storage".to_string(), Quantity("750Mi".to_string())); + + let pvc: PersistentVolumeClaim = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": PVC_NAME, + "namespace": namespace, + }, + "spec": PersistentVolumeClaimSpec { + storage_class_name: Some(STORAGE_CLASS_NAME.to_string()), + access_modes: Some(vec!["ReadWriteOnce".to_string()]), + resources: Some( ResourceRequirements { + requests: Some(requests_map), + limits: Some(limits_map), + }), + ..Default::default() + }, + }))?; + + let post_params = PostParams::default(); + let created_config = pvc_api.create(&post_params, &pvc).await?; + let name = created_config.name_any(); + assert_eq!(pvc.name_any(), name); + println!("Created {}", name); + } + + // deploy pod with stacks api and postgres containers + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let env: Vec = vec![ + EnvVar { + name: String::from("STACKS_CORE_RPC_HOST"), + value: Some(format!("host.docker.internal",)), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_DB"), + value: Some(String::from("pg")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_RPC_PORT"), + value: Some("30003".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_PORT"), + value: Some("3999".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_HOST"), + value: Some(String::from("0.0.0.0")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_EVENT_PORT"), + value: Some("3700".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_EVENT_HOST"), + value: Some(String::from("0.0.0.0")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_API_ENABLE_FT_METADATA"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("PG_HOST"), + value: Some(format!("host.docker.internal",)), + ..Default::default() + }, + EnvVar { + name: String::from("PG_PORT"), + value: Some(String::from("30006")), + ..Default::default() + }, + EnvVar { + name: String::from("PG_USER"), + value: Some("postgres".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("PG_PASSWORD"), + value: Some("postgres".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("PG_DATABASE"), + value: Some("stacks_api".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CHAIN_ID"), + value: Some(String::from("2147483648")), + ..Default::default() + }, + EnvVar { + name: String::from("V2_POX_MIN_AMOUNT_USTX"), + value: Some(String::from("90000000260")), + ..Default::default() + }, + EnvVar { + name: String::from("NODE_ENV"), + value: Some(String::from("production")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_API_LOG_LEVEL"), + value: Some(String::from("debug")), + ..Default::default() + }, + ]; + + let stacks_api_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": { + "name": POD_NAME + } + }, + "spec": { + "containers": Some(vec![ + Container { + name: CONTAINER_NAME.into(), + image: Some(image.into()), + image_pull_policy: Some("Never".into()), + ports: Some(vec![ + ContainerPort { + container_port: 3999, + protocol: Some(String::from("TCP")), + name: Some("api".into()), + ..Default::default() + }, + ContainerPort { + container_port: 3700, + protocol: Some(String::from("TCP")), + name: Some("eventport".into()), + ..Default::default() + }, + ]), + env: Some(env), + ..Default::default() + }, + Container { + name: POSTGRES_POD_NAME.into(), + image: Some("postgres:14".to_string()), + ports: Some(vec![ + ContainerPort { + container_port: 5432, + protocol: Some(String::from("TCP")), + name: Some("postgres".into()), + ..Default::default() + }, + ]), + env_from: Some(vec![ + EnvFromSource { + config_map_ref: Some( ConfigMapEnvSource{name: Some(POSTGRES_CONFIGMAP_NAME.to_string()), optional: Some(false)}), + ..Default::default() + } + ]), + volume_mounts: Some(vec![ VolumeMount { + name: POSTGRES_CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/var/lib/postgresql/data".into(), + ..Default::default() + }]), + ..Default::default() + }]), + "volumes": Some(vec![ + Volume { + name: POSTGRES_CONFIGMAP_VOLUME_NAME.into(), + persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { + claim_name: PVC_NAME.into() + , ..Default::default() + }), + ..Default::default() + }]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &stacks_api_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service to communicate with container + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": SERVICE_NAME, + "namespace": namespace, + }, + "spec": { + "type": "NodePort", + "ports": Some(vec![ServicePort { + port: 3999, + protocol: Some("TCP".into()), + name: Some("api".into()), + node_port: Some(30005), + ..Default::default() + }, + ServicePort { + port: 5432, + protocol: Some("TCP".into()), + name: Some("postgres".into()), + node_port: Some(30006), + ..Default::default() + }, + ServicePort { + port: 3700, + protocol: Some("TCP".into()), + name: Some("eventport".into()), + node_port: Some(30007), + ..Default::default() + }]), + "selector": { + "name": POD_NAME + }, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} + +async fn deploy_chain_coordinator( + namespace: &str, + image: &str, + orchestrator_service_name: &str, +) -> Result<(), Box> { + const POD_NAME: &str = "chain-coordinator"; + const CONTAINER_NAME: &str = "chain-coordinator-container"; + const CONFIGMAP_NAME: &str = "chain-coordinator-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; + + // // deploy config map for chain coordinator + // { + // let client = Client::try_default().await?; + // let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + // let manifest_path = PathBuf::from("../stx-px/Clarinet.toml"); + // let manifest = fs::read_to_string(manifest_path)?; + + // let deployment_plan_path = PathBuf::from("../stx-px/deployments/default.devnet-plan.yaml"); + // let deployment_plan = fs::read_to_string(deployment_plan_path)?; + + // let config_map: ConfigMap = serde_json::from_value(json!({ + // "apiVersion": "v1", + // "kind": "ConfigMap", + // "metadata": { + // "name": CONFIGMAP_NAME, + // "namespace": namespace + // }, + // "data": { + // "Clarinet.toml": manifest, + // "deployment-plan.yaml": deployment_plan, + // }, + // }))?; + + // let post_params = PostParams::default(); + // let created_config = config_map_api.create(&post_params, &config_map).await?; + // let name = created_config.name_any(); + // assert_eq!(config_map.name_any(), name); + // println!("Created {}", name); + // } + + // deploy pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let project_path = String::from("/foo2"); + + let pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": {"name": POD_NAME} + }, + "spec": { + "containers": Some(vec![ Container { + name: CONTAINER_NAME.into(), + image: Some(image.into()), + image_pull_policy: Some("Never".into()), + command: Some(vec![ + "./stacks-network".into(), + // "--help".into(), + "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), + "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), + "--project-root-path=/etc/stacks-network/project/".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: 20445, + protocol: Some("TCP".into()), + name: Some("orchestrator".into()), + ..Default::default() + }, + ContainerPort { + container_port: 20446, + protocol: Some("TCP".into()), + name: Some("orch-control".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ + // VolumeMount { + // name: CONFIGMAP_VOLUME_NAME.into(), + // mount_path: "/etc/stacks-network".into(), + // read_only: Some(true), + // ..Default::default() + // }, + VolumeMount { + name: "project".into(), + mount_path: "/etc/stacks-network/project".into(), + read_only: Some(false), + ..Default::default() + } + ]), + ..Default::default() + }]), + "volumes": Some(vec![ + // Volume { + // name: CONFIGMAP_VOLUME_NAME.into(), + // config_map: Some(ConfigMapVolumeSource { + // name: Some(CONFIGMAP_NAME.into()) + // , ..Default::default() + // }), + // ..Default::default() + // }, + Volume { + name: "project".into(), + host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), + ..Default::default() + } + ]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service to communicate with container + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": orchestrator_service_name, + "namespace": namespace + }, + "spec": { + "type": "NodePort", + "ports": Some(vec![ServicePort { + port: 20445, + protocol: Some("TCP".into()), + name: Some("orchestrator".into()), + node_port: Some(30008), + ..Default::default() + },ServicePort { + port: 20446, + protocol: Some("TCP".into()), + name: Some("orch-control".into()), + node_port: Some(30009), + ..Default::default() + }]), + "selector": {"name": POD_NAME}, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} From a8e34fad0fd75c3e7c91de14135d2f9df99f7a2e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 16 May 2023 16:29:36 -0400 Subject: [PATCH 02/60] use internal ports --- src/main.rs | 900 ++++++++++++++++++++++++++-------------------------- 1 file changed, 455 insertions(+), 445 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5649e4d..6d0f7c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::{collections::BTreeMap, time::Duration}; use k8s_openapi::{ api::core::v1::{ ConfigMap, ConfigMapEnvSource, ConfigMapVolumeSource, Container, ContainerPort, - EnvFromSource, EnvVar, HostPathVolumeSource, PersistentVolumeClaim, + EnvFromSource, EnvVar, HostPathVolumeSource, Namespace, PersistentVolumeClaim, PersistentVolumeClaimSpec, PersistentVolumeClaimVolumeSource, Pod, ResourceRequirements, Service, ServicePort, Volume, VolumeMount, }, @@ -21,86 +21,71 @@ use kube::{ }; use std::thread::sleep; +const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-service"; +const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; +// const CHAIN_COORDINATOR_SERVICE_NAME: &str = "orchestrator-service"; + +const BITCOIND_P2P_PORT: &str = "18444"; +const BITCOIND_RPC_PORT: &str = "18443"; +const STACKS_NODE_P2P_PORT: &str = "20444"; +const STACKS_NODE_RPC_PORT: &str = "20443"; +const CHAIN_COORDINATOR_INGESTION_PORT: &str = "20445"; +const CHAIN_COORDINATOR_CONTROL_PORT: &str = "20446"; + #[tokio::main] async fn main() -> Result<(), Box> { - const BITCOIN_NODE_SERVICE_NAME: &str = "bitcoin-node-service"; - const ORCHESTRATOR_SERVICE_NAME: &str = "orchestrator-service"; - // values from user config // let user_id = "user-id"; // let user_project_name = "my-project"; let namespace = "px-devnet"; //format!("{user_id}-{user_project_name}-devnet"); - let bitcoin_node_image = "quay.io/hirosystems/bitcoind:devnet-v3"; - let bitcoin_node_p2p_port = "18444"; - let bitcoin_node_rpc_port = "18443"; - let stacks_node_image = "quay.io/hirosystems/stacks-node:devnet-v3"; - let stacks_node_p2p_port = "20444"; - let stacks_node_rpc_port = "20443"; let stacks_miner_secret_key_hex = "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801"; let miner_stx_address = "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5"; let stacks_node_wait_time_for_microblocks: u32 = 50; let stacks_node_first_attempt_time_ms: u32 = 500; let stacks_node_subsequent_attempt_time_ms: u32 = 1_000; - let orchestrator_ingestion_port = "20445"; - - let stacks_api_image = "hirosystems/stacks-blockchain-api"; - let chain_coordinator_image = "stacks-network"; + let pox_2_activation = 112; + let epoch_2_0 = 100; + let epoch_2_05 = 102; + let epoch_2_1 = 106; - deploy_bitcoin_node_pod( - &namespace, - &bitcoin_node_image, - &bitcoin_node_p2p_port, - &bitcoin_node_rpc_port, - BITCOIN_NODE_SERVICE_NAME, - ) - .await?; + create_namespace(&namespace).await?; + deploy_bitcoin_node_pod(&namespace).await?; - deploy_chain_coordinator( - &namespace, - &chain_coordinator_image, - ORCHESTRATOR_SERVICE_NAME, - ) - .await?; + // deploy_chain_coordinator(&namespace).await?; sleep(Duration::from_secs(10)); deploy_stacks_node_pod( &namespace, - &stacks_node_image, - &stacks_node_p2p_port, - &stacks_node_rpc_port, &stacks_miner_secret_key_hex, &stacks_node_wait_time_for_microblocks, &stacks_node_first_attempt_time_ms, &stacks_node_subsequent_attempt_time_ms, &miner_stx_address, - // &bitcoin_node_p2p_port, - // &bitcoin_node_rpc_port, - // &orchestrator_ingestion_port, - // BITCOIN_NODE_SERVICE_NAME, - // ORCHESTRATOR_SERVICE_NAME, + pox_2_activation, + epoch_2_0, + epoch_2_05, + epoch_2_1, ) .await?; - deploy_stacks_api_pod(&namespace, &stacks_api_image).await?; + deploy_stacks_api_pod(&namespace).await?; + let client = Client::try_default().await?; let pods_api: Api = Api::namespaced(client, &namespace); // Watch it phase for a few seconds - let establish = await_condition(pods_api.clone(), "bitcoin-node", is_pod_running()); + let establish = await_condition(pods_api.clone(), "bitcoind", is_pod_running()); let _ = tokio::time::timeout(std::time::Duration::from_secs(15), establish).await?; // Verify we can get it - println!("Get Pod bitcoin-node"); - let p1cpy = pods_api.get("bitcoin-node").await?; + println!("Get Pod bitcoind"); + let p1cpy = pods_api.get("bitcoind").await?; if let Some(spec) = &p1cpy.spec { - println!( - "Got bitcoin-node pod with containers: {:?}", - spec.containers - ); + println!("Got bitcoind pod with containers: {:?}", spec.containers); } - let lp = ListParams::default().fields(&format!("metadata.name={}", "bitcoin-node")); // only want results for our pod + let lp = ListParams::default().fields(&format!("metadata.name={}", "bitcoind")); // only want results for our pod for p in pods_api.list(&lp).await? { println!("Found Pod: {}", p.name_any()); } @@ -115,24 +100,48 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn deploy_bitcoin_node_pod( - namespace: &str, - image: &str, - p2p_port: &str, - rpc_port: &str, - service_name: &str, -) -> Result<(), Box> { - // constants for bitcoin pod, services, and config - const POD_NAME: &str = "bitcoin-node"; - const CONTAINER_NAME: &str = "bitcoin-node-container"; - const CONFIGMAP_NAME: &str = "bitcoin-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "bitcoin-conf-volume"; +async fn create_namespace(namespace: &str) -> Result<(), Box> { + let client = Client::try_default().await?; + let namespace_api: Api = kube::Api::all(client); + + let namespace: Namespace = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": namespace, + "labels": { + "name": namespace + } + } + }))?; + let post_params = PostParams::default(); + let created_namespace = namespace_api.create(&post_params, &namespace).await?; + let name = created_namespace.name_any(); + assert_eq!(namespace.name_any(), name); + println!("Created {}", name); + Ok(()) +} + +async fn deploy_bitcoin_node_pod(namespace: &str) -> Result<(), Box> { + const POD_NAME: &str = "bitcoind-chain-coordinator"; + const BITCOIND_CONTAINER_NAME: &str = "bitcoind-container"; + const BITCOIND_CONFIGMAP_NAME: &str = "bitcoind-conf"; + const BITCOIND_CONFIGMAP_VOLUME_NAME: &str = "bitcoind-conf-volume"; + const BITCOIND_IMAGE: &str = "quay.io/hirosystems/bitcoind:devnet-v3"; + + const CHAIN_COORDINATOR_CONTAINER_NAME: &str = "chain-coordinator-container"; + const CHAIN_COORDINATOR_CONFIGMAP_NAME: &str = "chain-coordinator-conf"; + const CHAIN_COORDINATOR_CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; + const CHAIN_COORDINATOR_IMAGE: &str = "stacks-network"; - // massage user data - let p2p_port = p2p_port.parse::()?; - let rpc_port = rpc_port.parse::()?; + let bitcoind_p2p_port = BITCOIND_P2P_PORT.parse::()?; + let bitcoind_rpc_port = BITCOIND_RPC_PORT.parse::()?; + let coordinator_ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; + let coordinator_control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; - // deploy config map for bitcoin node + let project_path = String::from("/foo2"); + + // deploy configmap for bitcoin node { let client = Client::try_default().await?; let config_map_api: Api = kube::Api::::namespaced(client, &namespace); @@ -157,16 +166,16 @@ async fn deploy_bitcoin_node_pod( fallbackfee=0.00001 [regtest] - bind=0.0.0.0:{p2p_port} - rpcbind=0.0.0.0:{rpc_port} - rpcport={rpc_port} + bind=0.0.0.0:{bitcoind_p2p_port} + rpcbind=0.0.0.0:{bitcoind_rpc_port} + rpcport={bitcoind_rpc_port} "# ); let config_map: ConfigMap = serde_json::from_value(json!({ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { - "name": CONFIGMAP_NAME, + "name": BITCOIND_CONFIGMAP_NAME, "namespace": namespace }, "data": { @@ -182,7 +191,7 @@ async fn deploy_bitcoin_node_pod( println!("Created {}", name); } - // deploy bitcoin node pod + // deploy pod { let client = Client::try_default().await?; let pods_api: Api = Api::namespaced(client, &namespace); @@ -196,52 +205,94 @@ async fn deploy_bitcoin_node_pod( "labels": {"name": POD_NAME} }, "spec": { - "containers": Some(vec![ Container { - name: CONTAINER_NAME.into(), - image: Some(image.into()), - command: Some(vec![ - "/usr/local/bin/bitcoind".into(), - "-conf=/etc/bitcoin/bitcoin.conf".into(), - "-nodebuglogfile".into(), - "-pid=/run/bitcoind.pid".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ContainerPort { - container_port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ContainerPort { - container_port: 20445, - protocol: Some("TCP".into()), - name: Some("orchestrator".into()), + "containers": Some(vec![ + Container { + name: BITCOIND_CONTAINER_NAME.into(), + image: Some(BITCOIND_IMAGE.into()), + command: Some(vec![ + "/usr/local/bin/bitcoind".into(), + "-conf=/etc/bitcoin/bitcoin.conf".into(), + "-nodebuglogfile".into(), + "-pid=/run/bitcoind.pid".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: bitcoind_p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ContainerPort { + container_port: bitcoind_rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ContainerPort { + container_port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("orchestrator".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ VolumeMount { + name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/etc/bitcoin".into(), + read_only: Some(true), ..Default::default() - }, - ]), - volume_mounts: Some(vec![ VolumeMount { - name: CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/etc/bitcoin".into(), - read_only: Some(true), + }]), ..Default::default() - }]), - ..Default::default() - }]), + }, + Container { + name: CHAIN_COORDINATOR_CONTAINER_NAME.into(), + image: Some(CHAIN_COORDINATOR_IMAGE.into()), + image_pull_policy: Some("Never".into()), + command: Some(vec![ + "./stacks-network".into(), + "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), + "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), + "--project-root-path=/etc/stacks-network/project/".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("coordinator-in".into()), + ..Default::default() + }, + ContainerPort { + container_port: coordinator_control_port, + protocol: Some("TCP".into()), + name: Some("coordinator-con".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ + VolumeMount { + name: "project".into(), + mount_path: "/etc/stacks-network/project".into(), + read_only: Some(false), + ..Default::default() + } + ]), + ..Default::default() + } + ]), "volumes": Some(vec![ Volume { - name: CONFIGMAP_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: Some(CONFIGMAP_NAME.into()) - , ..Default::default() - }), - ..Default::default() - }]) + name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: Some(BITCOIND_CONFIGMAP_NAME.into()) + , ..Default::default() + }), + ..Default::default() + }, + Volume { + name: "project".into(), + host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), + ..Default::default() + } + ]) }}))?; let pp = PostParams::default(); @@ -250,36 +301,48 @@ async fn deploy_bitcoin_node_pod( println!("created pod {}", name); } - // deploy service to communicate with container + // deploy service { let client = Client::try_default().await?; let service_api: Api = Api::namespaced(client, &namespace); let mut selector = BTreeMap::::new(); - selector.insert("name".into(), CONTAINER_NAME.into()); + selector.insert("name".into(), BITCOIND_CONTAINER_NAME.into()); let service: Service = serde_json::from_value(json!({ "apiVersion": "v1", "kind": "Service", "metadata": { - "name": service_name, + "name": BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, "namespace": namespace }, "spec": { - "type": "NodePort", - "ports": Some(vec![ServicePort { - port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - node_port: Some(30000), - ..Default::default() - },ServicePort { - port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - node_port: Some(30001), - ..Default::default() - }]), + "ports": Some(vec![ + ServicePort { + port: bitcoind_p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ServicePort { + port: bitcoind_rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ServicePort { + port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("coordinator-in".into()), + ..Default::default() + }, + ServicePort { + port: coordinator_control_port, + protocol: Some("TCP".into()), + name: Some("coordinator-con".into()), + ..Default::default() + } + ]), "selector": {"name": POD_NAME}, } }))?; @@ -292,145 +355,260 @@ async fn deploy_bitcoin_node_pod( Ok(()) } +// async fn deploy_chain_coordinator(namespace: &str) -> Result<(), Box> { +// const POD_NAME: &str = "chain-coordinator"; +// const CONTAINER_NAME: &str = "chain-coordinator-container"; + +// let ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; +// let control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; + +// // deploy pod +// { +// let client = Client::try_default().await?; +// let pods_api: Api = Api::namespaced(client, &namespace); + +// let project_path = String::from("/foo2"); + +// let pod: Pod = serde_json::from_value(json!({ +// "apiVersion": "v1", +// "kind": "Pod", +// "metadata": { +// "name": POD_NAME, +// "namespace": namespace, +// "labels": {"name": POD_NAME} +// }, +// "spec": { +// "containers": Some(vec![ Container { +// name: CONTAINER_NAME.into(), +// image: Some(CHAIN_COORDINATOR_IMAGE.into()), +// image_pull_policy: Some("Never".into()), +// command: Some(vec![ +// "./stacks-network".into(), +// "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), +// "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), +// "--project-root-path=/etc/stacks-network/project/".into(), +// ]), +// ports: Some(vec![ +// ContainerPort { +// container_port: ingestion_port, +// protocol: Some("TCP".into()), +// name: Some("coordinator-in".into()), +// ..Default::default() +// }, +// ContainerPort { +// container_port: control_port, +// protocol: Some("TCP".into()), +// name: Some("coordinator-con".into()), +// ..Default::default() +// }, +// ]), +// volume_mounts: Some(vec![ +// VolumeMount { +// name: "project".into(), +// mount_path: "/etc/stacks-network/project".into(), +// read_only: Some(false), +// ..Default::default() +// } +// ]), +// ..Default::default() +// }]), +// "volumes": Some(vec![ +// Volume { +// name: "project".into(), +// host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), +// ..Default::default() +// } +// ]) +// }}))?; + +// let pp = PostParams::default(); +// let response = pods_api.create(&pp, &pod).await?; +// let name = response.name_any(); +// println!("created pod {}", name); +// } + +// // deploy service +// { +// let client = Client::try_default().await?; +// let service_api: Api = Api::namespaced(client, &namespace); + +// let mut selector = BTreeMap::::new(); +// selector.insert("name".into(), CONTAINER_NAME.into()); + +// let service: Service = serde_json::from_value(json!({ +// "apiVersion": "v1", +// "kind": "Service", +// "metadata": { +// "name": CHAIN_COORDINATOR_SERVICE_NAME, +// "namespace": namespace +// }, +// "spec": { +// "ports": Some(vec![ServicePort { +// port: 20445, +// protocol: Some("TCP".into()), +// name: Some("coordinator-in".into()), +// ..Default::default() +// },ServicePort { +// port: 20446, +// protocol: Some("TCP".into()), +// name: Some("coordinator-con".into()), +// ..Default::default() +// }]), +// "selector": {"name": POD_NAME}, +// } +// }))?; + +// let pp = PostParams::default(); +// let response = service_api.create(&pp, &service).await?; +// let name = response.name_any(); +// println!("created service {}", name); +// } +// Ok(()) +// } + async fn deploy_stacks_node_pod( namespace: &str, - image: &str, - p2p_port: &str, - rpc_port: &str, miner_secret_key_hex: &str, wait_time_for_microblocks: &u32, first_attempt_time_ms: &u32, subsequent_attempt_time_ms: &u32, miner_coinbase_recipient: &str, - // bitcoin_node_p2p_port: &str, - // bitcoin_node_rpc_port: &str, - // orchestrator_ingestion_port: &str, - // bitcoin_node_service_name: &str, - // orchestrator_service_name: &str, + pox_2_activation: i32, + epoch_2_0: i32, + epoch_2_05: i32, + epoch_2_1: i32, ) -> Result<(), Box> { - // constants for stacks pod, services, and config const POD_NAME: &str = "stacks-node"; const CONTAINER_NAME: &str = "stacks-node-container"; - const CONFIGMAP_NAME: &str = "stacks-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "stacks-conf-volume"; - const SERVICE_NAME: &str = "stacks-node-service"; + const CONFIGMAP_NAME: &str = "stacks-node-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "stacks-node-conf-volume"; + const STACKS_NODE_IMAGE: &str = "quay.io/hirosystems/stacks-node:devnet-v3"; - // massage user data - let p2p_port = p2p_port.parse::()?; - let rpc_port = rpc_port.parse::()?; + let p2p_port = STACKS_NODE_P2P_PORT.parse::()?; + let rpc_port = STACKS_NODE_RPC_PORT.parse::()?; - // deploy config map for stacks node + // deploy configmap { let client = Client::try_default().await?; let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let mut stacks_conf = format!( - r#" - [node] - working_dir = "/devnet" - rpc_bind = "0.0.0.0:{rpc_port}" - p2p_bind = "0.0.0.0:{p2p_port}" - miner = true - seed = "{miner_secret_key_hex}" - local_peer_seed = "{miner_secret_key_hex}" - pox_sync_sample_secs = 0 - wait_time_for_blocks = 0 - wait_time_for_microblocks = {wait_time_for_microblocks} - microblock_frequency = 1000 - - [connection_options] - # inv_sync_interval = 10 - # download_interval = 10 - # walk_interval = 10 - disable_block_download = true - disable_inbound_handshakes = true - disable_inbound_walks = true - public_ip_address = "1.1.1.1:1234" - - [miner] - first_attempt_time_ms = {first_attempt_time_ms} - subsequent_attempt_time_ms = {subsequent_attempt_time_ms} - block_reward_recipient = "{miner_coinbase_recipient}" - # microblock_attempt_time_ms = 15000 + let stacks_conf = { + let mut stacks_conf = format!( + r#" + [node] + working_dir = "/devnet" + rpc_bind = "0.0.0.0:{rpc_port}" + p2p_bind = "0.0.0.0:{p2p_port}" + miner = true + seed = "{miner_secret_key_hex}" + local_peer_seed = "{miner_secret_key_hex}" + pox_sync_sample_secs = 0 + wait_time_for_blocks = 0 + wait_time_for_microblocks = {wait_time_for_microblocks} + microblock_frequency = 1000 + + [connection_options] + # inv_sync_interval = 10 + # download_interval = 10 + # walk_interval = 10 + disable_block_download = true + disable_inbound_handshakes = true + disable_inbound_walks = true + public_ip_address = "1.1.1.1:1234" + + [miner] + first_attempt_time_ms = {first_attempt_time_ms} + subsequent_attempt_time_ms = {subsequent_attempt_time_ms} + block_reward_recipient = "{miner_coinbase_recipient}" + # microblock_attempt_time_ms = 15000 + "# + ); + + let balance: u64 = 100_000_000_000_000; + stacks_conf.push_str(&format!( + r#" + [[ustx_balance]] + address = "{miner_coinbase_recipient}" + amount = {balance} "# - ); - - let balance: u64 = 100_000_000_000_000; - stacks_conf.push_str(&format!( - r#" - [[ustx_balance]] - address = "{miner_coinbase_recipient}" - amount = {balance} - "# - )); - - let cluster_domain = "cluster.local"; + )); + + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let bitcoind_chain_coordinator_host = format!( + "{}.{}", + &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host + ); + + stacks_conf.push_str(&format!( + r#" + # Add orchestrator (docker-host) as an event observer + [[events_observer]] + endpoint = "{}:{}" + retry_count = 255 + include_data_events = true + events_keys = ["*"] + "#, + bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT + )); + + // stacks_conf.push_str(&format!( + // r#" + // # Add stacks-api as an event observer + // [[events_observer]] + // endpoint = "host.docker.internal:{}" + // retry_count = 255 + // include_data_events = false + // events_keys = ["*"] + // "#, + // 30007, + // )); + + stacks_conf.push_str(&format!( + r#" + [burnchain] + chain = "bitcoin" + mode = "krypton" + poll_time_secs = 1 + timeout = 30 + peer_host = "{}" + rpc_ssl = false + wallet_name = "devnet" + username = "{}" + password = "{}" + rpc_port = {} + peer_port = {} + "#, + bitcoind_chain_coordinator_host, + namespace, + namespace, + CHAIN_COORDINATOR_INGESTION_PORT, /* TODO: this is supposed to be the coordinator ingestion port (consider making coorinator/bitcoind same pod) */ + BITCOIND_P2P_PORT + )); + + stacks_conf.push_str(&format!( + r#" + pox_2_activation = {} + + [[burnchain.epochs]] + epoch_name = "1.0" + start_height = 0 + + [[burnchain.epochs]] + epoch_name = "2.0" + start_height = {} + + [[burnchain.epochs]] + epoch_name = "2.05" + start_height = {} + + [[burnchain.epochs]] + epoch_name = "2.1" + start_height = {} + "#, + pox_2_activation, epoch_2_0, epoch_2_05, epoch_2_1 + )); + stacks_conf + }; - stacks_conf.push_str(&format!( - r#" -# Add orchestrator (docker-host) as an event observer -[[events_observer]] -endpoint = "host.docker.internal:30008" -retry_count = 255 -include_data_events = true -events_keys = ["*"] -"# - )); - - // stacks_conf.push_str(&format!( - // r#" - // # Add stacks-api as an event observer - // [[events_observer]] - // endpoint = "host.docker.internal:{}" - // retry_count = 255 - // include_data_events = false - // events_keys = ["*"] - // "#, - // 30007, - // )); - - stacks_conf.push_str(&format!( - r#" -[burnchain] -chain = "bitcoin" -mode = "krypton" -poll_time_secs = 1 -timeout = 30 -peer_host = "host.docker.internal" -rpc_ssl = false -wallet_name = "devnet" -username = "{namespace}" -password = "{namespace}" -rpc_port = {} -peer_port = {} -"#, - 30008, 30000 - )); - - let pox_2_activation = 112; - let epoch_2_0 = 100; - let epoch_2_05 = 102; - let epoch_2_1 = 106; - stacks_conf.push_str(&format!( - r#"pox_2_activation = {pox_2_activation} - -[[burnchain.epochs]] -epoch_name = "1.0" -start_height = 0 - -[[burnchain.epochs]] -epoch_name = "2.0" -start_height = {epoch_2_0} - -[[burnchain.epochs]] -epoch_name = "2.05" -start_height = {epoch_2_05} - -[[burnchain.epochs]] -epoch_name = "2.1" -start_height = {epoch_2_1} - "# - )); let config_map: ConfigMap = serde_json::from_value(json!({ "apiVersion": "v1", "kind": "ConfigMap", @@ -451,7 +629,7 @@ start_height = {epoch_2_1} println!("Created {}", name); } - // deploy stacks node pod + // deploy pod { let client = Client::try_default().await?; let pods_api: Api = Api::namespaced(client, &namespace); @@ -467,7 +645,7 @@ start_height = {epoch_2_1} "spec": { "containers": Some(vec![ Container { name: CONTAINER_NAME.into(), - image: Some(image.into()), + image: Some(STACKS_NODE_IMAGE.into()), command: Some(vec![ "stacks-node".into(), "start".into(), @@ -529,7 +707,7 @@ start_height = {epoch_2_1} println!("created pod {}", name); } - // deploy service to communicate with container + // deploy service { let client = Client::try_default().await?; let service_api: Api = Api::namespaced(client, &namespace); @@ -541,22 +719,19 @@ start_height = {epoch_2_1} "apiVersion": "v1", "kind": "Service", "metadata": { - "name": SERVICE_NAME, + "name": STACKS_NODE_SERVICE_NAME, "namespace": namespace }, "spec": { - "type": "NodePort", "ports": Some(vec![ServicePort { port: p2p_port, protocol: Some("TCP".into()), name: Some("p2p".into()), - node_port: Some(30002), ..Default::default() },ServicePort { port: rpc_port, protocol: Some("TCP".into()), name: Some("rpc".into()), - node_port: Some(30003), ..Default::default() }]), "selector": {"name": POD_NAME}, @@ -571,21 +746,20 @@ start_height = {epoch_2_1} Ok(()) } -async fn deploy_stacks_api_pod( - namespace: &str, - image: &str, -) -> Result<(), Box> { +async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box> { // constants for stacks pod, services, and config const POD_NAME: &str = "stacks-api"; - const POSTGRES_POD_NAME: &str = "stacks-api-postgres"; - const CONTAINER_NAME: &str = "stacks-api-container"; - const POSTGRES_CONFIGMAP_NAME: &str = "stacks-api-postgres-conf"; + const POSTGRES_CONTAINER_NAME: &str = "stacks-api-postgres"; + const API_CONTAINER_NAME: &str = "stacks-api-container"; + const CONFIGMAP_NAME: &str = "stacks-api-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "stacks-api-conf-volume"; const PVC_NAME: &str = "stacks-api-pvc"; const STORAGE_CLASS_NAME: &str = "stacks-api-storage-class"; - const POSTGRES_CONFIGMAP_VOLUME_NAME: &str = "stacks-api-postgres-conf-volume"; const SERVICE_NAME: &str = "stacks-api-service"; + const STACKS_API_IMAGE: &str = "hirosystems/stacks-blockchain-api"; + const POSTGRES_IMAGE: &str = "postgres:14"; - // deploy config map for stacks api + // deploy configmap { let client = Client::try_default().await?; let config_map_api: Api = kube::Api::::namespaced(client, &namespace); @@ -594,7 +768,7 @@ async fn deploy_stacks_api_pod( "apiVersion": "v1", "kind": "ConfigMap", "metadata": { - "name": POSTGRES_CONFIGMAP_NAME, + "name": CONFIGMAP_NAME, "namespace": namespace }, "data": { @@ -683,15 +857,18 @@ async fn deploy_stacks_api_pod( println!("Created {}", name); } - // deploy pod with stacks api and postgres containers + // deploy pod { let client = Client::try_default().await?; let pods_api: Api = Api::namespaced(client, &namespace); + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); + let env: Vec = vec![ EnvVar { name: String::from("STACKS_CORE_RPC_HOST"), - value: Some(format!("host.docker.internal",)), + value: Some(format!("{}", stacks_node_host)), ..Default::default() }, EnvVar { @@ -701,7 +878,7 @@ async fn deploy_stacks_api_pod( }, EnvVar { name: String::from("STACKS_CORE_RPC_PORT"), - value: Some("30003".to_string()), + value: Some(STACKS_NODE_RPC_PORT.to_string()), ..Default::default() }, EnvVar { @@ -731,12 +908,12 @@ async fn deploy_stacks_api_pod( }, EnvVar { name: String::from("PG_HOST"), - value: Some(format!("host.docker.internal",)), + value: Some(format!("0.0.0.0",)), ..Default::default() }, EnvVar { name: String::from("PG_PORT"), - value: Some(String::from("30006")), + value: Some(String::from("5432")), ..Default::default() }, EnvVar { @@ -789,8 +966,8 @@ async fn deploy_stacks_api_pod( "spec": { "containers": Some(vec![ Container { - name: CONTAINER_NAME.into(), - image: Some(image.into()), + name: API_CONTAINER_NAME.into(), + image: Some(STACKS_API_IMAGE.into()), image_pull_policy: Some("Never".into()), ports: Some(vec![ ContainerPort { @@ -810,8 +987,8 @@ async fn deploy_stacks_api_pod( ..Default::default() }, Container { - name: POSTGRES_POD_NAME.into(), - image: Some("postgres:14".to_string()), + name: POSTGRES_CONTAINER_NAME.into(), + image: Some(POSTGRES_IMAGE.to_string()), ports: Some(vec![ ContainerPort { container_port: 5432, @@ -822,12 +999,12 @@ async fn deploy_stacks_api_pod( ]), env_from: Some(vec![ EnvFromSource { - config_map_ref: Some( ConfigMapEnvSource{name: Some(POSTGRES_CONFIGMAP_NAME.to_string()), optional: Some(false)}), + config_map_ref: Some( ConfigMapEnvSource{name: Some(CONFIGMAP_NAME.to_string()), optional: Some(false)}), ..Default::default() } ]), volume_mounts: Some(vec![ VolumeMount { - name: POSTGRES_CONFIGMAP_VOLUME_NAME.into(), + name: CONFIGMAP_VOLUME_NAME.into(), mount_path: "/var/lib/postgresql/data".into(), ..Default::default() }]), @@ -835,7 +1012,7 @@ async fn deploy_stacks_api_pod( }]), "volumes": Some(vec![ Volume { - name: POSTGRES_CONFIGMAP_VOLUME_NAME.into(), + name: CONFIGMAP_VOLUME_NAME.into(), persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { claim_name: PVC_NAME.into() , ..Default::default() @@ -850,13 +1027,13 @@ async fn deploy_stacks_api_pod( println!("created pod {}", name); } - // deploy service to communicate with container + // deploy service { let client = Client::try_default().await?; let service_api: Api = Api::namespaced(client, &namespace); let mut selector = BTreeMap::::new(); - selector.insert("name".into(), CONTAINER_NAME.into()); + selector.insert("name".into(), API_CONTAINER_NAME.into()); let service: Service = serde_json::from_value(json!({ "apiVersion": "v1", @@ -866,26 +1043,22 @@ async fn deploy_stacks_api_pod( "namespace": namespace, }, "spec": { - "type": "NodePort", "ports": Some(vec![ServicePort { port: 3999, protocol: Some("TCP".into()), name: Some("api".into()), - node_port: Some(30005), ..Default::default() }, ServicePort { port: 5432, protocol: Some("TCP".into()), name: Some("postgres".into()), - node_port: Some(30006), ..Default::default() }, ServicePort { port: 3700, protocol: Some("TCP".into()), name: Some("eventport".into()), - node_port: Some(30007), ..Default::default() }]), "selector": { @@ -901,166 +1074,3 @@ async fn deploy_stacks_api_pod( } Ok(()) } - -async fn deploy_chain_coordinator( - namespace: &str, - image: &str, - orchestrator_service_name: &str, -) -> Result<(), Box> { - const POD_NAME: &str = "chain-coordinator"; - const CONTAINER_NAME: &str = "chain-coordinator-container"; - const CONFIGMAP_NAME: &str = "chain-coordinator-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; - - // // deploy config map for chain coordinator - // { - // let client = Client::try_default().await?; - // let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - // let manifest_path = PathBuf::from("../stx-px/Clarinet.toml"); - // let manifest = fs::read_to_string(manifest_path)?; - - // let deployment_plan_path = PathBuf::from("../stx-px/deployments/default.devnet-plan.yaml"); - // let deployment_plan = fs::read_to_string(deployment_plan_path)?; - - // let config_map: ConfigMap = serde_json::from_value(json!({ - // "apiVersion": "v1", - // "kind": "ConfigMap", - // "metadata": { - // "name": CONFIGMAP_NAME, - // "namespace": namespace - // }, - // "data": { - // "Clarinet.toml": manifest, - // "deployment-plan.yaml": deployment_plan, - // }, - // }))?; - - // let post_params = PostParams::default(); - // let created_config = config_map_api.create(&post_params, &config_map).await?; - // let name = created_config.name_any(); - // assert_eq!(config_map.name_any(), name); - // println!("Created {}", name); - // } - - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let project_path = String::from("/foo2"); - - let pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": {"name": POD_NAME} - }, - "spec": { - "containers": Some(vec![ Container { - name: CONTAINER_NAME.into(), - image: Some(image.into()), - image_pull_policy: Some("Never".into()), - command: Some(vec![ - "./stacks-network".into(), - // "--help".into(), - "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), - "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), - "--project-root-path=/etc/stacks-network/project/".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: 20445, - protocol: Some("TCP".into()), - name: Some("orchestrator".into()), - ..Default::default() - }, - ContainerPort { - container_port: 20446, - protocol: Some("TCP".into()), - name: Some("orch-control".into()), - ..Default::default() - }, - ]), - volume_mounts: Some(vec![ - // VolumeMount { - // name: CONFIGMAP_VOLUME_NAME.into(), - // mount_path: "/etc/stacks-network".into(), - // read_only: Some(true), - // ..Default::default() - // }, - VolumeMount { - name: "project".into(), - mount_path: "/etc/stacks-network/project".into(), - read_only: Some(false), - ..Default::default() - } - ]), - ..Default::default() - }]), - "volumes": Some(vec![ - // Volume { - // name: CONFIGMAP_VOLUME_NAME.into(), - // config_map: Some(ConfigMapVolumeSource { - // name: Some(CONFIGMAP_NAME.into()) - // , ..Default::default() - // }), - // ..Default::default() - // }, - Volume { - name: "project".into(), - host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), - ..Default::default() - } - ]) - }}))?; - - let pp = PostParams::default(); - let response = pods_api.create(&pp, &pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - } - - // deploy service to communicate with container - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": orchestrator_service_name, - "namespace": namespace - }, - "spec": { - "type": "NodePort", - "ports": Some(vec![ServicePort { - port: 20445, - protocol: Some("TCP".into()), - name: Some("orchestrator".into()), - node_port: Some(30008), - ..Default::default() - },ServicePort { - port: 20446, - protocol: Some("TCP".into()), - name: Some("orch-control".into()), - node_port: Some(30009), - ..Default::default() - }]), - "selector": {"name": POD_NAME}, - } - }))?; - - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } - Ok(()) -} From f881e217d68cec4e6aef05af68af51034be9306a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 16 May 2023 23:26:41 -0400 Subject: [PATCH 03/60] cleanup, use config struct, create server --- src/lib.rs | 917 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1094 ++------------------------------------------------- 2 files changed, 947 insertions(+), 1064 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b2069ef --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,917 @@ +use std::{collections::BTreeMap, time::Duration}; + +// use k8s_experimentation::Thing; +use k8s_openapi::{ + api::core::v1::{ + ConfigMap, ConfigMapEnvSource, ConfigMapVolumeSource, Container, ContainerPort, + EnvFromSource, EnvVar, HostPathVolumeSource, Namespace, PersistentVolumeClaim, + PersistentVolumeClaimSpec, PersistentVolumeClaimVolumeSource, Pod, ResourceRequirements, + Service, ServicePort, Volume, VolumeMount, + }, + apimachinery::pkg::api::resource::Quantity, +}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use kube::{ + api::{Api, PostParams, ResourceExt}, + Client, +}; +use std::thread::sleep; + +#[derive(Serialize, Deserialize, Debug)] +pub struct StacksDevnetConfig { + namespace: String, + label: String, + network_id: u32, + stacks_node_wait_time_for_microblocks: u32, + stacks_node_first_attempt_time_ms: u32, + stacks_node_subsequent_attempt_time_ms: u32, + bitcoin_node_username: String, + bitcoin_node_password: String, + miner_mnemonic: String, + miner_derivation_path: String, + miner_coinbase_recipient: String, + faucet_mnemonic: String, + faucet_derivation_path: String, + bitcoin_controller_block_time: u32, + bitcoin_controller_automining_disabled: bool, + disable_bitcoin_explorer: bool, + disable_stacks_explorer: bool, + disable_stacks_api: bool, + epoch_2_0: u32, + epoch_2_05: u32, + epoch_2_1: u32, + epoch_2_2: u32, + pox_2_activation: u32, + pox_2_unlock_height: u32, + // to remove and compute + stacks_miner_secret_key_hex: String, + miner_stx_address: String, +} + +const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-service"; +const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; +// const CHAIN_COORDINATOR_SERVICE_NAME: &str = "orchestrator-service"; + +const BITCOIND_P2P_PORT: &str = "18444"; +const BITCOIND_RPC_PORT: &str = "18443"; +const STACKS_NODE_P2P_PORT: &str = "20444"; +const STACKS_NODE_RPC_PORT: &str = "20443"; +const CHAIN_COORDINATOR_INGESTION_PORT: &str = "20445"; +const CHAIN_COORDINATOR_CONTROL_PORT: &str = "20446"; + +pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box> { + let namespace = &config.namespace; + + create_namespace(&namespace).await?; + deploy_bitcoin_node_pod(&config).await?; + + // deploy_chain_coordinator(&namespace).await?; + + sleep(Duration::from_secs(10)); + + deploy_stacks_node_pod(&config).await?; + + if !config.disable_stacks_api { + deploy_stacks_api_pod(&namespace).await?; + } + Ok(()) +} + +async fn create_namespace(namespace: &str) -> Result<(), Box> { + let client = Client::try_default().await?; + let namespace_api: Api = kube::Api::all(client); + + let namespace: Namespace = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": namespace, + "labels": { + "name": namespace + } + } + }))?; + let post_params = PostParams::default(); + let created_namespace = namespace_api.create(&post_params, &namespace).await?; + let name = created_namespace.name_any(); + assert_eq!(namespace.name_any(), name); + println!("Created {}", name); + Ok(()) +} + +async fn deploy_bitcoin_node_pod( + config: &StacksDevnetConfig, +) -> Result<(), Box> { + const POD_NAME: &str = "bitcoind-chain-coordinator"; + const BITCOIND_CONTAINER_NAME: &str = "bitcoind-container"; + const BITCOIND_CONFIGMAP_NAME: &str = "bitcoind-conf"; + const BITCOIND_CONFIGMAP_VOLUME_NAME: &str = "bitcoind-conf-volume"; + const BITCOIND_IMAGE: &str = "quay.io/hirosystems/bitcoind:devnet-v3"; + + const CHAIN_COORDINATOR_CONTAINER_NAME: &str = "chain-coordinator-container"; + const CHAIN_COORDINATOR_CONFIGMAP_NAME: &str = "chain-coordinator-conf"; + const CHAIN_COORDINATOR_CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; + const CHAIN_COORDINATOR_IMAGE: &str = "stacks-network"; + + let bitcoind_p2p_port = BITCOIND_P2P_PORT.parse::()?; + let bitcoind_rpc_port = BITCOIND_RPC_PORT.parse::()?; + let coordinator_ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; + let coordinator_control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; + + let project_path = String::from("/foo2"); + let namespace = &config.namespace; + + // deploy configmap for bitcoin node + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let bitcoind_conf = format!( + r#" + server=1 + regtest=1 + rpcallowip=0.0.0.0/0 + rpcallowip=::/0 + rpcuser={} + rpcpassword={} + txindex=1 + listen=1 + discover=0 + dns=0 + dnsseed=0 + listenonion=0 + rpcworkqueue=100 + rpcserialversion=1 + disablewallet=0 + fallbackfee=0.00001 + + [regtest] + bind=0.0.0.0:{} + rpcbind=0.0.0.0:{} + rpcport={} + "#, + config.bitcoin_node_username, + config.bitcoin_node_password, + bitcoind_p2p_port, + bitcoind_rpc_port, + bitcoind_rpc_port + ); + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": BITCOIND_CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "bitcoin.conf": bitcoind_conf + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let bitcoin_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": {"name": POD_NAME} + }, + "spec": { + "containers": Some(vec![ + Container { + name: BITCOIND_CONTAINER_NAME.into(), + image: Some(BITCOIND_IMAGE.into()), + command: Some(vec![ + "/usr/local/bin/bitcoind".into(), + "-conf=/etc/bitcoin/bitcoin.conf".into(), + "-nodebuglogfile".into(), + "-pid=/run/bitcoind.pid".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: bitcoind_p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ContainerPort { + container_port: bitcoind_rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ContainerPort { + container_port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("orchestrator".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ VolumeMount { + name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/etc/bitcoin".into(), + read_only: Some(true), + ..Default::default() + }]), + ..Default::default() + }, + Container { + name: CHAIN_COORDINATOR_CONTAINER_NAME.into(), + image: Some(CHAIN_COORDINATOR_IMAGE.into()), + image_pull_policy: Some("Never".into()), + command: Some(vec![ + "./stacks-network".into(), + "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), + "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), + "--project-root-path=/etc/stacks-network/project/".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("coordinator-in".into()), + ..Default::default() + }, + ContainerPort { + container_port: coordinator_control_port, + protocol: Some("TCP".into()), + name: Some("coordinator-con".into()), + ..Default::default() + }, + ]), + volume_mounts: Some(vec![ + VolumeMount { + name: "project".into(), + mount_path: "/etc/stacks-network/project".into(), + read_only: Some(false), + ..Default::default() + } + ]), + ..Default::default() + } + ]), + "volumes": Some(vec![ + Volume { + name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: Some(BITCOIND_CONFIGMAP_NAME.into()) + , ..Default::default() + }), + ..Default::default() + }, + Volume { + name: "project".into(), + host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), + ..Default::default() + } + ]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &bitcoin_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), BITCOIND_CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, + "namespace": namespace + }, + "spec": { + "ports": Some(vec![ + ServicePort { + port: bitcoind_p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ServicePort { + port: bitcoind_rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ServicePort { + port: coordinator_ingestion_port, + protocol: Some("TCP".into()), + name: Some("coordinator-in".into()), + ..Default::default() + }, + ServicePort { + port: coordinator_control_port, + protocol: Some("TCP".into()), + name: Some("coordinator-con".into()), + ..Default::default() + } + ]), + "selector": {"name": POD_NAME}, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} + +async fn deploy_stacks_node_pod( + config: &StacksDevnetConfig, +) -> Result<(), Box> { + const POD_NAME: &str = "stacks-node"; + const CONTAINER_NAME: &str = "stacks-node-container"; + const CONFIGMAP_NAME: &str = "stacks-node-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "stacks-node-conf-volume"; + const STACKS_NODE_IMAGE: &str = "quay.io/hirosystems/stacks-node:devnet-v3"; + + let p2p_port = STACKS_NODE_P2P_PORT.parse::()?; + let rpc_port = STACKS_NODE_RPC_PORT.parse::()?; + let namespace = &config.namespace; + + // deploy configmap + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + let stacks_conf = { + let mut stacks_conf = format!( + r#" + [node] + working_dir = "/devnet" + rpc_bind = "0.0.0.0:{}" + p2p_bind = "0.0.0.0:{}" + miner = true + seed = "{}" + local_peer_seed = "{}" + pox_sync_sample_secs = 0 + wait_time_for_blocks = 0 + wait_time_for_microblocks = {} + microblock_frequency = 1000 + + [connection_options] + # inv_sync_interval = 10 + # download_interval = 10 + # walk_interval = 10 + disable_block_download = true + disable_inbound_handshakes = true + disable_inbound_walks = true + public_ip_address = "1.1.1.1:1234" + + [miner] + first_attempt_time_ms = {} + subsequent_attempt_time_ms = {} + block_reward_recipient = "{}" + # microblock_attempt_time_ms = 15000 + "#, + rpc_port, + p2p_port, + config.stacks_miner_secret_key_hex, + config.stacks_miner_secret_key_hex, + config.stacks_node_wait_time_for_microblocks, + config.stacks_node_first_attempt_time_ms, + config.stacks_node_subsequent_attempt_time_ms, + config.miner_coinbase_recipient + ); + + let balance: u64 = 100_000_000_000_000; + stacks_conf.push_str(&format!( + r#" + [[ustx_balance]] + address = "{}" + amount = {} + "#, + config.miner_coinbase_recipient, balance + )); + + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let bitcoind_chain_coordinator_host = format!( + "{}.{}", + &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host + ); + + stacks_conf.push_str(&format!( + r#" + # Add orchestrator (docker-host) as an event observer + [[events_observer]] + endpoint = "{}:{}" + retry_count = 255 + include_data_events = true + events_keys = ["*"] + "#, + bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT + )); + + // stacks_conf.push_str(&format!( + // r#" + // # Add stacks-api as an event observer + // [[events_observer]] + // endpoint = "host.docker.internal:{}" + // retry_count = 255 + // include_data_events = false + // events_keys = ["*"] + // "#, + // 30007, + // )); + + stacks_conf.push_str(&format!( + r#" + [burnchain] + chain = "bitcoin" + mode = "krypton" + poll_time_secs = 1 + timeout = 30 + peer_host = "{}" + rpc_ssl = false + wallet_name = "devnet" + username = "{}" + password = "{}" + rpc_port = {} + peer_port = {} + "#, + bitcoind_chain_coordinator_host, + config.bitcoin_node_username, + config.bitcoin_node_password, + CHAIN_COORDINATOR_INGESTION_PORT, + BITCOIND_P2P_PORT + )); + + stacks_conf.push_str(&format!( + r#" + pox_2_activation = {} + + [[burnchain.epochs]] + epoch_name = "1.0" + start_height = 0 + + [[burnchain.epochs]] + epoch_name = "2.0" + start_height = {} + + [[burnchain.epochs]] + epoch_name = "2.05" + start_height = {} + + [[burnchain.epochs]] + epoch_name = "2.1" + start_height = {} + "#, + config.pox_2_activation, config.epoch_2_0, config.epoch_2_05, config.epoch_2_1 + )); + stacks_conf + }; + + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "Stacks.toml": stacks_conf + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let bitcoin_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": {"name": POD_NAME} + }, + "spec": { + "containers": Some(vec![ Container { + name: CONTAINER_NAME.into(), + image: Some(STACKS_NODE_IMAGE.into()), + command: Some(vec![ + "stacks-node".into(), + "start".into(), + "--config=/src/stacks-node/Stacks.toml".into(), + ]), + ports: Some(vec![ + ContainerPort { + container_port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + }, + ContainerPort { + container_port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }, + ]), + env: Some(vec![ + EnvVar { + name: String::from("STACKS_LOG_PP"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_LOG_DEBUG"), + value: Some(String::from("0")), + ..Default::default() + } + ]), + volume_mounts: Some(vec![ VolumeMount { + name: CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/src/stacks-node".into(), + read_only: Some(true), + ..Default::default() + }]), + ..Default::default() + }]), + "volumes": Some(vec![ + Volume { + name: CONFIGMAP_VOLUME_NAME.into(), + config_map: Some(ConfigMapVolumeSource { + name: Some(CONFIGMAP_NAME.into()) + , ..Default::default() + }), + ..Default::default() + }]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &bitcoin_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": STACKS_NODE_SERVICE_NAME, + "namespace": namespace + }, + "spec": { + "ports": Some(vec![ServicePort { + port: p2p_port, + protocol: Some("TCP".into()), + name: Some("p2p".into()), + ..Default::default() + },ServicePort { + port: rpc_port, + protocol: Some("TCP".into()), + name: Some("rpc".into()), + ..Default::default() + }]), + "selector": {"name": POD_NAME}, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} + +async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box> { + // constants for stacks pod, services, and config + const POD_NAME: &str = "stacks-api"; + const POSTGRES_CONTAINER_NAME: &str = "stacks-api-postgres"; + const API_CONTAINER_NAME: &str = "stacks-api-container"; + const CONFIGMAP_NAME: &str = "stacks-api-conf"; + const CONFIGMAP_VOLUME_NAME: &str = "stacks-api-conf-volume"; + const PVC_NAME: &str = "stacks-api-pvc"; + const STORAGE_CLASS_NAME: &str = "stacks-api-storage-class"; + const SERVICE_NAME: &str = "stacks-api-service"; + const STACKS_API_IMAGE: &str = "hirosystems/stacks-blockchain-api"; + const POSTGRES_IMAGE: &str = "postgres:14"; + + // deploy configmap + { + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let config_map: ConfigMap = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": CONFIGMAP_NAME, + "namespace": namespace + }, + "data": { + "POSTGRES_PASSWORD": "postgres", + "POSTGRES_DB": "stacks_api", + + }, + }))?; + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &config_map).await?; + let name = created_config.name_any(); + assert_eq!(config_map.name_any(), name); + println!("Created {}", name); + } + + // deploy persistent volume claim + { + let client = Client::try_default().await?; + let pvc_api: Api = + kube::Api::::namespaced(client, &namespace); + + let mut requests_map: BTreeMap = BTreeMap::new(); + requests_map.insert("storage".to_string(), Quantity("500Mi".to_string())); + let mut limits_map: BTreeMap = BTreeMap::new(); + limits_map.insert("storage".to_string(), Quantity("750Mi".to_string())); + + let pvc: PersistentVolumeClaim = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": PVC_NAME, + "namespace": namespace, + }, + "spec": PersistentVolumeClaimSpec { + storage_class_name: Some(STORAGE_CLASS_NAME.to_string()), + access_modes: Some(vec!["ReadWriteOnce".to_string()]), + resources: Some( ResourceRequirements { + requests: Some(requests_map), + limits: Some(limits_map), + }), + ..Default::default() + }, + }))?; + + let post_params = PostParams::default(); + let created_config = pvc_api.create(&post_params, &pvc).await?; + let name = created_config.name_any(); + assert_eq!(pvc.name_any(), name); + println!("Created {}", name); + } + + // deploy pod + { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); + + let env: Vec = vec![ + EnvVar { + name: String::from("STACKS_CORE_RPC_HOST"), + value: Some(format!("{}", stacks_node_host)), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_DB"), + value: Some(String::from("pg")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_RPC_PORT"), + value: Some(STACKS_NODE_RPC_PORT.to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_PORT"), + value: Some("3999".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_BLOCKCHAIN_API_HOST"), + value: Some(String::from("0.0.0.0")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_EVENT_PORT"), + value: Some("3700".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CORE_EVENT_HOST"), + value: Some(String::from("0.0.0.0")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_API_ENABLE_FT_METADATA"), + value: Some(String::from("1")), + ..Default::default() + }, + EnvVar { + name: String::from("PG_HOST"), + value: Some(format!("0.0.0.0",)), + ..Default::default() + }, + EnvVar { + name: String::from("PG_PORT"), + value: Some(String::from("5432")), + ..Default::default() + }, + EnvVar { + name: String::from("PG_USER"), + value: Some("postgres".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("PG_PASSWORD"), + value: Some("postgres".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("PG_DATABASE"), + value: Some("stacks_api".to_string()), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_CHAIN_ID"), + value: Some(String::from("2147483648")), + ..Default::default() + }, + EnvVar { + name: String::from("V2_POX_MIN_AMOUNT_USTX"), + value: Some(String::from("90000000260")), + ..Default::default() + }, + EnvVar { + name: String::from("NODE_ENV"), + value: Some(String::from("production")), + ..Default::default() + }, + EnvVar { + name: String::from("STACKS_API_LOG_LEVEL"), + value: Some(String::from("debug")), + ..Default::default() + }, + ]; + + let stacks_api_pod: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": POD_NAME, + "namespace": namespace, + "labels": { + "name": POD_NAME + } + }, + "spec": { + "containers": Some(vec![ + Container { + name: API_CONTAINER_NAME.into(), + image: Some(STACKS_API_IMAGE.into()), + image_pull_policy: Some("Never".into()), + ports: Some(vec![ + ContainerPort { + container_port: 3999, + protocol: Some(String::from("TCP")), + name: Some("api".into()), + ..Default::default() + }, + ContainerPort { + container_port: 3700, + protocol: Some(String::from("TCP")), + name: Some("eventport".into()), + ..Default::default() + }, + ]), + env: Some(env), + ..Default::default() + }, + Container { + name: POSTGRES_CONTAINER_NAME.into(), + image: Some(POSTGRES_IMAGE.to_string()), + ports: Some(vec![ + ContainerPort { + container_port: 5432, + protocol: Some(String::from("TCP")), + name: Some("postgres".into()), + ..Default::default() + }, + ]), + env_from: Some(vec![ + EnvFromSource { + config_map_ref: Some( ConfigMapEnvSource{name: Some(CONFIGMAP_NAME.to_string()), optional: Some(false)}), + ..Default::default() + } + ]), + volume_mounts: Some(vec![ VolumeMount { + name: CONFIGMAP_VOLUME_NAME.into(), + mount_path: "/var/lib/postgresql/data".into(), + ..Default::default() + }]), + ..Default::default() + }]), + "volumes": Some(vec![ + Volume { + name: CONFIGMAP_VOLUME_NAME.into(), + persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { + claim_name: PVC_NAME.into() + , ..Default::default() + }), + ..Default::default() + }]) + }}))?; + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &stacks_api_pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + } + + // deploy service + { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let mut selector = BTreeMap::::new(); + selector.insert("name".into(), API_CONTAINER_NAME.into()); + + let service: Service = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": SERVICE_NAME, + "namespace": namespace, + }, + "spec": { + "ports": Some(vec![ServicePort { + port: 3999, + protocol: Some("TCP".into()), + name: Some("api".into()), + ..Default::default() + }, + ServicePort { + port: 5432, + protocol: Some("TCP".into()), + name: Some("postgres".into()), + ..Default::default() + }, + ServicePort { + port: 3700, + protocol: Some("TCP".into()), + name: Some("eventport".into()), + ..Default::default() + }]), + "selector": { + "name": POD_NAME + }, + } + }))?; + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 6d0f7c4..4222e01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,1076 +1,42 @@ -// use std::{path::PathBuf, str::FromStr}; - -use std::{collections::BTreeMap, time::Duration}; - -// use k8s_experimentation::Thing; -use k8s_openapi::{ - api::core::v1::{ - ConfigMap, ConfigMapEnvSource, ConfigMapVolumeSource, Container, ContainerPort, - EnvFromSource, EnvVar, HostPathVolumeSource, Namespace, PersistentVolumeClaim, - PersistentVolumeClaimSpec, PersistentVolumeClaimVolumeSource, Pod, ResourceRequirements, - Service, ServicePort, Volume, VolumeMount, - }, - apimachinery::pkg::api::resource::Quantity, -}; -use serde_json::json; - -use kube::{ - api::{Api, ListParams, PostParams, ResourceExt}, - runtime::wait::{await_condition, conditions::is_pod_running}, - Client, -}; -use std::thread::sleep; - -const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-service"; -const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; -// const CHAIN_COORDINATOR_SERVICE_NAME: &str = "orchestrator-service"; - -const BITCOIND_P2P_PORT: &str = "18444"; -const BITCOIND_RPC_PORT: &str = "18443"; -const STACKS_NODE_P2P_PORT: &str = "20444"; -const STACKS_NODE_RPC_PORT: &str = "20443"; -const CHAIN_COORDINATOR_INGESTION_PORT: &str = "20445"; -const CHAIN_COORDINATOR_CONTROL_PORT: &str = "20446"; +use k8s_experimentation::{deploy_devnet, StacksDevnetConfig}; +use tiny_http::{Method, Response, Server}; #[tokio::main] async fn main() -> Result<(), Box> { - // values from user config - // let user_id = "user-id"; - // let user_project_name = "my-project"; - let namespace = "px-devnet"; //format!("{user_id}-{user_project_name}-devnet"); - let stacks_miner_secret_key_hex = - "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801"; - let miner_stx_address = "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5"; - let stacks_node_wait_time_for_microblocks: u32 = 50; - let stacks_node_first_attempt_time_ms: u32 = 500; - let stacks_node_subsequent_attempt_time_ms: u32 = 1_000; - let pox_2_activation = 112; - let epoch_2_0 = 100; - let epoch_2_05 = 102; - let epoch_2_1 = 106; - - create_namespace(&namespace).await?; - deploy_bitcoin_node_pod(&namespace).await?; - - // deploy_chain_coordinator(&namespace).await?; - - sleep(Duration::from_secs(10)); - - deploy_stacks_node_pod( - &namespace, - &stacks_miner_secret_key_hex, - &stacks_node_wait_time_for_microblocks, - &stacks_node_first_attempt_time_ms, - &stacks_node_subsequent_attempt_time_ms, - &miner_stx_address, - pox_2_activation, - epoch_2_0, - epoch_2_05, - epoch_2_1, - ) - .await?; - - deploy_stacks_api_pod(&namespace).await?; - - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - // Watch it phase for a few seconds - let establish = await_condition(pods_api.clone(), "bitcoind", is_pod_running()); - let _ = tokio::time::timeout(std::time::Duration::from_secs(15), establish).await?; - - // Verify we can get it - println!("Get Pod bitcoind"); - let p1cpy = pods_api.get("bitcoind").await?; - if let Some(spec) = &p1cpy.spec { - println!("Got bitcoind pod with containers: {:?}", spec.containers); - } - - let lp = ListParams::default().fields(&format!("metadata.name={}", "bitcoind")); // only want results for our pod - for p in pods_api.list(&lp).await? { - println!("Found Pod: {}", p.name_any()); - } - - // Delete it - // let dp = DeleteParams::default(); - // pods.delete("blog", &dp).await?.map_left(|pdel| { - // assert_eq!(pdel.name_any(), "blog"); - // println!("Deleting blog pod started: {:?}", pdel); - // }); - - Ok(()) -} - -async fn create_namespace(namespace: &str) -> Result<(), Box> { - let client = Client::try_default().await?; - let namespace_api: Api = kube::Api::all(client); - - let namespace: Namespace = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "name": namespace, - "labels": { - "name": namespace + const HOST: &str = "127.0.0.1"; + const PORT: &str = "8477"; + let endpoint: String = HOST.to_owned() + ":" + PORT; + + let server = Server::http(endpoint).unwrap(); + loop { + // blocks until the next request is received + let mut request = match server.recv() { + Ok(rq) => rq, + Err(e) => { + println!("error: {}", e); + break; } - } - }))?; - let post_params = PostParams::default(); - let created_namespace = namespace_api.create(&post_params, &namespace).await?; - let name = created_namespace.name_any(); - assert_eq!(namespace.name_any(), name); - println!("Created {}", name); - Ok(()) -} - -async fn deploy_bitcoin_node_pod(namespace: &str) -> Result<(), Box> { - const POD_NAME: &str = "bitcoind-chain-coordinator"; - const BITCOIND_CONTAINER_NAME: &str = "bitcoind-container"; - const BITCOIND_CONFIGMAP_NAME: &str = "bitcoind-conf"; - const BITCOIND_CONFIGMAP_VOLUME_NAME: &str = "bitcoind-conf-volume"; - const BITCOIND_IMAGE: &str = "quay.io/hirosystems/bitcoind:devnet-v3"; - - const CHAIN_COORDINATOR_CONTAINER_NAME: &str = "chain-coordinator-container"; - const CHAIN_COORDINATOR_CONFIGMAP_NAME: &str = "chain-coordinator-conf"; - const CHAIN_COORDINATOR_CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; - const CHAIN_COORDINATOR_IMAGE: &str = "stacks-network"; - - let bitcoind_p2p_port = BITCOIND_P2P_PORT.parse::()?; - let bitcoind_rpc_port = BITCOIND_RPC_PORT.parse::()?; - let coordinator_ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; - let coordinator_control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; - - let project_path = String::from("/foo2"); - - // deploy configmap for bitcoin node - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let bitcoind_conf = format!( - r#" - server=1 - regtest=1 - rpcallowip=0.0.0.0/0 - rpcallowip=::/0 - rpcuser={namespace} - rpcpassword={namespace} - txindex=1 - listen=1 - discover=0 - dns=0 - dnsseed=0 - listenonion=0 - rpcworkqueue=100 - rpcserialversion=1 - disablewallet=0 - fallbackfee=0.00001 - - [regtest] - bind=0.0.0.0:{bitcoind_p2p_port} - rpcbind=0.0.0.0:{bitcoind_rpc_port} - rpcport={bitcoind_rpc_port} - "# - ); - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": BITCOIND_CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "bitcoin.conf": bitcoind_conf - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } - - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let bitcoin_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": {"name": POD_NAME} - }, - "spec": { - "containers": Some(vec![ - Container { - name: BITCOIND_CONTAINER_NAME.into(), - image: Some(BITCOIND_IMAGE.into()), - command: Some(vec![ - "/usr/local/bin/bitcoind".into(), - "-conf=/etc/bitcoin/bitcoin.conf".into(), - "-nodebuglogfile".into(), - "-pid=/run/bitcoind.pid".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: bitcoind_p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ContainerPort { - container_port: bitcoind_rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ContainerPort { - container_port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("orchestrator".into()), - ..Default::default() - }, - ]), - volume_mounts: Some(vec![ VolumeMount { - name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/etc/bitcoin".into(), - read_only: Some(true), - ..Default::default() - }]), - ..Default::default() - }, - Container { - name: CHAIN_COORDINATOR_CONTAINER_NAME.into(), - image: Some(CHAIN_COORDINATOR_IMAGE.into()), - image_pull_policy: Some("Never".into()), - command: Some(vec![ - "./stacks-network".into(), - "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), - "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), - "--project-root-path=/etc/stacks-network/project/".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("coordinator-in".into()), - ..Default::default() - }, - ContainerPort { - container_port: coordinator_control_port, - protocol: Some("TCP".into()), - name: Some("coordinator-con".into()), - ..Default::default() - }, - ]), - volume_mounts: Some(vec![ - VolumeMount { - name: "project".into(), - mount_path: "/etc/stacks-network/project".into(), - read_only: Some(false), - ..Default::default() - } - ]), - ..Default::default() - } - ]), - "volumes": Some(vec![ - Volume { - name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: Some(BITCOIND_CONFIGMAP_NAME.into()) - , ..Default::default() - }), - ..Default::default() - }, - Volume { - name: "project".into(), - host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), - ..Default::default() - } - ]) - }}))?; - - let pp = PostParams::default(); - let response = pods_api.create(&pp, &bitcoin_pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - } - - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), BITCOIND_CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, - "namespace": namespace - }, - "spec": { - "ports": Some(vec![ - ServicePort { - port: bitcoind_p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ServicePort { - port: bitcoind_rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ServicePort { - port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("coordinator-in".into()), - ..Default::default() - }, - ServicePort { - port: coordinator_control_port, - protocol: Some("TCP".into()), - name: Some("coordinator-con".into()), - ..Default::default() - } - ]), - "selector": {"name": POD_NAME}, - } - }))?; - - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } - Ok(()) -} - -// async fn deploy_chain_coordinator(namespace: &str) -> Result<(), Box> { -// const POD_NAME: &str = "chain-coordinator"; -// const CONTAINER_NAME: &str = "chain-coordinator-container"; - -// let ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; -// let control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; - -// // deploy pod -// { -// let client = Client::try_default().await?; -// let pods_api: Api = Api::namespaced(client, &namespace); - -// let project_path = String::from("/foo2"); - -// let pod: Pod = serde_json::from_value(json!({ -// "apiVersion": "v1", -// "kind": "Pod", -// "metadata": { -// "name": POD_NAME, -// "namespace": namespace, -// "labels": {"name": POD_NAME} -// }, -// "spec": { -// "containers": Some(vec![ Container { -// name: CONTAINER_NAME.into(), -// image: Some(CHAIN_COORDINATOR_IMAGE.into()), -// image_pull_policy: Some("Never".into()), -// command: Some(vec![ -// "./stacks-network".into(), -// "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), -// "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), -// "--project-root-path=/etc/stacks-network/project/".into(), -// ]), -// ports: Some(vec![ -// ContainerPort { -// container_port: ingestion_port, -// protocol: Some("TCP".into()), -// name: Some("coordinator-in".into()), -// ..Default::default() -// }, -// ContainerPort { -// container_port: control_port, -// protocol: Some("TCP".into()), -// name: Some("coordinator-con".into()), -// ..Default::default() -// }, -// ]), -// volume_mounts: Some(vec![ -// VolumeMount { -// name: "project".into(), -// mount_path: "/etc/stacks-network/project".into(), -// read_only: Some(false), -// ..Default::default() -// } -// ]), -// ..Default::default() -// }]), -// "volumes": Some(vec![ -// Volume { -// name: "project".into(), -// host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), -// ..Default::default() -// } -// ]) -// }}))?; - -// let pp = PostParams::default(); -// let response = pods_api.create(&pp, &pod).await?; -// let name = response.name_any(); -// println!("created pod {}", name); -// } - -// // deploy service -// { -// let client = Client::try_default().await?; -// let service_api: Api = Api::namespaced(client, &namespace); - -// let mut selector = BTreeMap::::new(); -// selector.insert("name".into(), CONTAINER_NAME.into()); - -// let service: Service = serde_json::from_value(json!({ -// "apiVersion": "v1", -// "kind": "Service", -// "metadata": { -// "name": CHAIN_COORDINATOR_SERVICE_NAME, -// "namespace": namespace -// }, -// "spec": { -// "ports": Some(vec![ServicePort { -// port: 20445, -// protocol: Some("TCP".into()), -// name: Some("coordinator-in".into()), -// ..Default::default() -// },ServicePort { -// port: 20446, -// protocol: Some("TCP".into()), -// name: Some("coordinator-con".into()), -// ..Default::default() -// }]), -// "selector": {"name": POD_NAME}, -// } -// }))?; - -// let pp = PostParams::default(); -// let response = service_api.create(&pp, &service).await?; -// let name = response.name_any(); -// println!("created service {}", name); -// } -// Ok(()) -// } - -async fn deploy_stacks_node_pod( - namespace: &str, - miner_secret_key_hex: &str, - wait_time_for_microblocks: &u32, - first_attempt_time_ms: &u32, - subsequent_attempt_time_ms: &u32, - miner_coinbase_recipient: &str, - pox_2_activation: i32, - epoch_2_0: i32, - epoch_2_05: i32, - epoch_2_1: i32, -) -> Result<(), Box> { - const POD_NAME: &str = "stacks-node"; - const CONTAINER_NAME: &str = "stacks-node-container"; - const CONFIGMAP_NAME: &str = "stacks-node-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "stacks-node-conf-volume"; - const STACKS_NODE_IMAGE: &str = "quay.io/hirosystems/stacks-node:devnet-v3"; - - let p2p_port = STACKS_NODE_P2P_PORT.parse::()?; - let rpc_port = STACKS_NODE_RPC_PORT.parse::()?; - - // deploy configmap - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - let stacks_conf = { - let mut stacks_conf = format!( - r#" - [node] - working_dir = "/devnet" - rpc_bind = "0.0.0.0:{rpc_port}" - p2p_bind = "0.0.0.0:{p2p_port}" - miner = true - seed = "{miner_secret_key_hex}" - local_peer_seed = "{miner_secret_key_hex}" - pox_sync_sample_secs = 0 - wait_time_for_blocks = 0 - wait_time_for_microblocks = {wait_time_for_microblocks} - microblock_frequency = 1000 - - [connection_options] - # inv_sync_interval = 10 - # download_interval = 10 - # walk_interval = 10 - disable_block_download = true - disable_inbound_handshakes = true - disable_inbound_walks = true - public_ip_address = "1.1.1.1:1234" - - [miner] - first_attempt_time_ms = {first_attempt_time_ms} - subsequent_attempt_time_ms = {subsequent_attempt_time_ms} - block_reward_recipient = "{miner_coinbase_recipient}" - # microblock_attempt_time_ms = 15000 - "# - ); - - let balance: u64 = 100_000_000_000_000; - stacks_conf.push_str(&format!( - r#" - [[ustx_balance]] - address = "{miner_coinbase_recipient}" - amount = {balance} - "# - )); - - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let bitcoind_chain_coordinator_host = format!( - "{}.{}", - &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host - ); - - stacks_conf.push_str(&format!( - r#" - # Add orchestrator (docker-host) as an event observer - [[events_observer]] - endpoint = "{}:{}" - retry_count = 255 - include_data_events = true - events_keys = ["*"] - "#, - bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT - )); - - // stacks_conf.push_str(&format!( - // r#" - // # Add stacks-api as an event observer - // [[events_observer]] - // endpoint = "host.docker.internal:{}" - // retry_count = 255 - // include_data_events = false - // events_keys = ["*"] - // "#, - // 30007, - // )); - - stacks_conf.push_str(&format!( - r#" - [burnchain] - chain = "bitcoin" - mode = "krypton" - poll_time_secs = 1 - timeout = 30 - peer_host = "{}" - rpc_ssl = false - wallet_name = "devnet" - username = "{}" - password = "{}" - rpc_port = {} - peer_port = {} - "#, - bitcoind_chain_coordinator_host, - namespace, - namespace, - CHAIN_COORDINATOR_INGESTION_PORT, /* TODO: this is supposed to be the coordinator ingestion port (consider making coorinator/bitcoind same pod) */ - BITCOIND_P2P_PORT - )); - - stacks_conf.push_str(&format!( - r#" - pox_2_activation = {} - - [[burnchain.epochs]] - epoch_name = "1.0" - start_height = 0 - - [[burnchain.epochs]] - epoch_name = "2.0" - start_height = {} - - [[burnchain.epochs]] - epoch_name = "2.05" - start_height = {} - - [[burnchain.epochs]] - epoch_name = "2.1" - start_height = {} - "#, - pox_2_activation, epoch_2_0, epoch_2_05, epoch_2_1 - )); - stacks_conf }; - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "Stacks.toml": stacks_conf - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } - - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let bitcoin_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": {"name": POD_NAME} - }, - "spec": { - "containers": Some(vec![ Container { - name: CONTAINER_NAME.into(), - image: Some(STACKS_NODE_IMAGE.into()), - command: Some(vec![ - "stacks-node".into(), - "start".into(), - "--config=/src/stacks-node/Stacks.toml".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ContainerPort { - container_port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ]), - env: Some(vec![ - EnvVar { - name: String::from("STACKS_LOG_PP"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_LOG_DEBUG"), - value: Some(String::from("0")), - ..Default::default() + match request.method() { + Method::Post => { + let url = request.url(); + println!("{}", url); + match url { + "/api/v1/networks" => { + let mut content = String::new(); + request.as_reader().read_to_string(&mut content).unwrap(); + let config: StacksDevnetConfig = serde_json::from_str(&content)?; + deploy_devnet(config).await?; + request.respond(Response::empty(200))? } - ]), - volume_mounts: Some(vec![ VolumeMount { - name: CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/src/stacks-node".into(), - read_only: Some(true), - ..Default::default() - }]), - ..Default::default() - }]), - "volumes": Some(vec![ - Volume { - name: CONFIGMAP_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: Some(CONFIGMAP_NAME.into()) - , ..Default::default() - }), - ..Default::default() - }]) - }}))?; - - let pp = PostParams::default(); - let response = pods_api.create(&pp, &bitcoin_pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - } - - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": STACKS_NODE_SERVICE_NAME, - "namespace": namespace - }, - "spec": { - "ports": Some(vec![ServicePort { - port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - },ServicePort { - port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }]), - "selector": {"name": POD_NAME}, - } - }))?; - - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } - Ok(()) -} - -async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box> { - // constants for stacks pod, services, and config - const POD_NAME: &str = "stacks-api"; - const POSTGRES_CONTAINER_NAME: &str = "stacks-api-postgres"; - const API_CONTAINER_NAME: &str = "stacks-api-container"; - const CONFIGMAP_NAME: &str = "stacks-api-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "stacks-api-conf-volume"; - const PVC_NAME: &str = "stacks-api-pvc"; - const STORAGE_CLASS_NAME: &str = "stacks-api-storage-class"; - const SERVICE_NAME: &str = "stacks-api-service"; - const STACKS_API_IMAGE: &str = "hirosystems/stacks-blockchain-api"; - const POSTGRES_IMAGE: &str = "postgres:14"; - - // deploy configmap - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_DB": "stacks_api", - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } - - // deploy storage class - // { - // let client = Client::try_default().await?; - // let storage_class_api: Api = kube::Api::all(client); - - // let storage_class: StorageClass = serde_json::from_value(json!({ - // "apiVersion": "storage.k8s.io/v1", - // "kind": "StorageClass", - // "metadata": { - // "name": STORAGE_CLASS_NAME, - // "namespace": namespace, - // "labels": { - // "app": "my-app", - // }, - // "annotations": { - // "openebs.io/cas-type": "local", - // "cas.openebs.io/config": "| - // - name: StorageType - // value: hostpath - // - name: BasePath - // value: /var/local-hostpath" - // } - // }, - // "provisioner": "openebs.io/local", - // "volume_binding_modes": "WaitForFirstConsumer" - // }))?; - - // let post_params = PostParams::default(); - // let created_config = storage_class_api - // .create(&post_params, &storage_class) - // .await?; - // let name = created_config.name_any(); - // assert_eq!(storage_class.name_any(), name); - // println!("Created {}", name); - // } - - // deploy persistent volume claim - { - let client = Client::try_default().await?; - let pvc_api: Api = - kube::Api::::namespaced(client, &namespace); - - let mut requests_map: BTreeMap = BTreeMap::new(); - requests_map.insert("storage".to_string(), Quantity("500Mi".to_string())); - let mut limits_map: BTreeMap = BTreeMap::new(); - limits_map.insert("storage".to_string(), Quantity("750Mi".to_string())); - - let pvc: PersistentVolumeClaim = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "name": PVC_NAME, - "namespace": namespace, - }, - "spec": PersistentVolumeClaimSpec { - storage_class_name: Some(STORAGE_CLASS_NAME.to_string()), - access_modes: Some(vec!["ReadWriteOnce".to_string()]), - resources: Some( ResourceRequirements { - requests: Some(requests_map), - limits: Some(limits_map), - }), - ..Default::default() - }, - }))?; - - let post_params = PostParams::default(); - let created_config = pvc_api.create(&post_params, &pvc).await?; - let name = created_config.name_any(); - assert_eq!(pvc.name_any(), name); - println!("Created {}", name); - } - - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); - - let env: Vec = vec![ - EnvVar { - name: String::from("STACKS_CORE_RPC_HOST"), - value: Some(format!("{}", stacks_node_host)), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_DB"), - value: Some(String::from("pg")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_RPC_PORT"), - value: Some(STACKS_NODE_RPC_PORT.to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_PORT"), - value: Some("3999".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_HOST"), - value: Some(String::from("0.0.0.0")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_EVENT_PORT"), - value: Some("3700".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_EVENT_HOST"), - value: Some(String::from("0.0.0.0")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_API_ENABLE_FT_METADATA"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("PG_HOST"), - value: Some(format!("0.0.0.0",)), - ..Default::default() - }, - EnvVar { - name: String::from("PG_PORT"), - value: Some(String::from("5432")), - ..Default::default() - }, - EnvVar { - name: String::from("PG_USER"), - value: Some("postgres".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("PG_PASSWORD"), - value: Some("postgres".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("PG_DATABASE"), - value: Some("stacks_api".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CHAIN_ID"), - value: Some(String::from("2147483648")), - ..Default::default() - }, - EnvVar { - name: String::from("V2_POX_MIN_AMOUNT_USTX"), - value: Some(String::from("90000000260")), - ..Default::default() - }, - EnvVar { - name: String::from("NODE_ENV"), - value: Some(String::from("production")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_API_LOG_LEVEL"), - value: Some(String::from("debug")), - ..Default::default() - }, - ]; - - let stacks_api_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": { - "name": POD_NAME + _ => request.respond(Response::empty(404))?, + } } - }, - "spec": { - "containers": Some(vec![ - Container { - name: API_CONTAINER_NAME.into(), - image: Some(STACKS_API_IMAGE.into()), - image_pull_policy: Some("Never".into()), - ports: Some(vec![ - ContainerPort { - container_port: 3999, - protocol: Some(String::from("TCP")), - name: Some("api".into()), - ..Default::default() - }, - ContainerPort { - container_port: 3700, - protocol: Some(String::from("TCP")), - name: Some("eventport".into()), - ..Default::default() - }, - ]), - env: Some(env), - ..Default::default() - }, - Container { - name: POSTGRES_CONTAINER_NAME.into(), - image: Some(POSTGRES_IMAGE.to_string()), - ports: Some(vec![ - ContainerPort { - container_port: 5432, - protocol: Some(String::from("TCP")), - name: Some("postgres".into()), - ..Default::default() - }, - ]), - env_from: Some(vec![ - EnvFromSource { - config_map_ref: Some( ConfigMapEnvSource{name: Some(CONFIGMAP_NAME.to_string()), optional: Some(false)}), - ..Default::default() - } - ]), - volume_mounts: Some(vec![ VolumeMount { - name: CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/var/lib/postgresql/data".into(), - ..Default::default() - }]), - ..Default::default() - }]), - "volumes": Some(vec![ - Volume { - name: CONFIGMAP_VOLUME_NAME.into(), - persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { - claim_name: PVC_NAME.into() - , ..Default::default() - }), - ..Default::default() - }]) - }}))?; - - let pp = PostParams::default(); - let response = pods_api.create(&pp, &stacks_api_pod).await?; - let name = response.name_any(); - println!("created pod {}", name); + //let not_implemented = Response::new( StatusCode::from(501), request.headers(), "not implemented", None, None ); + _ => request.respond(Response::empty(501))?, + } } - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), API_CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": SERVICE_NAME, - "namespace": namespace, - }, - "spec": { - "ports": Some(vec![ServicePort { - port: 3999, - protocol: Some("TCP".into()), - name: Some("api".into()), - ..Default::default() - }, - ServicePort { - port: 5432, - protocol: Some("TCP".into()), - name: Some("postgres".into()), - ..Default::default() - }, - ServicePort { - port: 3700, - protocol: Some("TCP".into()), - name: Some("eventport".into()), - ..Default::default() - }]), - "selector": { - "name": POD_NAME - }, - } - }))?; - - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } Ok(()) } From 46995ab6da3e76e67cab4d5b4b5fbbb3aa100db2 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 17 May 2023 14:09:26 -0400 Subject: [PATCH 04/60] add templates for pods/configmaps/services --- ...tcoind-chain-coordinator-pod.template.yaml | 57 +++++++++++++++++++ ...nd-chain-coordinator-service.template.yaml | 26 +++++++++ templates/bitcoind-configmap.template.yaml | 7 +++ templates/namespace.template.yaml | 6 ++ templates/stacks-api-configmap.template.yaml | 7 +++ templates/stacks-api-pod.template.yaml | 41 +++++++++++++ ...tacks-api-postgres-configmap.template.yaml | 7 +++ templates/stacks-api-pvc.template.yaml | 15 +++++ templates/stacks-api-service.template.yaml | 22 +++++++ templates/stacks-node-configmap.template.yaml | 7 +++ templates/stacks-node-service.template.yaml | 15 +++++ 11 files changed, 210 insertions(+) create mode 100644 templates/bitcoind-chain-coordinator-pod.template.yaml create mode 100644 templates/bitcoind-chain-coordinator-service.template.yaml create mode 100644 templates/bitcoind-configmap.template.yaml create mode 100644 templates/namespace.template.yaml create mode 100644 templates/stacks-api-configmap.template.yaml create mode 100644 templates/stacks-api-pod.template.yaml create mode 100644 templates/stacks-api-postgres-configmap.template.yaml create mode 100644 templates/stacks-api-pvc.template.yaml create mode 100644 templates/stacks-api-service.template.yaml create mode 100644 templates/stacks-node-configmap.template.yaml create mode 100644 templates/stacks-node-service.template.yaml diff --git a/templates/bitcoind-chain-coordinator-pod.template.yaml b/templates/bitcoind-chain-coordinator-pod.template.yaml new file mode 100644 index 0000000..c919370 --- /dev/null +++ b/templates/bitcoind-chain-coordinator-pod.template.yaml @@ -0,0 +1,57 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + name: bitcoind-chain-coordinator + name: bitcoind-chain-coordinator + namespace: "{namespace}" +spec: + containers: + - command: + - /usr/local/bin/bitcoind + - -conf=/etc/bitcoin/bitcoin.conf + - -nodebuglogfile + - -pid=/run/bitcoind.pid + image: quay.io/hirosystems/bitcoind:devnet-v3 + imagePullPolicy: IfNotPresent + name: bitcoind-container + ports: + - containerPort: 18444 + name: p2p + protocol: TCP + - containerPort: 18443 + name: rpc + protocol: TCP + - containerPort: 20445 + name: orchestrator + protocol: TCP + volumeMounts: + - mountPath: /etc/bitcoin + name: bitcoind-conf-volume + readOnly: true + - command: + - ./stacks-network + - --manifest-path=/etc/stacks-network/project/Clarinet.toml + - --deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml + - --project-root-path=/etc/stacks-network/project/ + image: stacks-network + imagePullPolicy: Never + name: chain-coordinator-container + ports: + - containerPort: 20445 + name: coordinator-in + protocol: TCP + - containerPort: 20446 + name: coordinator-con + protocol: TCP + volumeMounts: + - mountPath: /etc/stacks-network/project + name: project + volumes: + - configMap: + name: bitcoind-conf + name: bitcoind-conf-volume + - hostPath: + path: /foo2 + type: Directory + name: project \ No newline at end of file diff --git a/templates/bitcoind-chain-coordinator-service.template.yaml b/templates/bitcoind-chain-coordinator-service.template.yaml new file mode 100644 index 0000000..3e41c78 --- /dev/null +++ b/templates/bitcoind-chain-coordinator-service.template.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Service +metadata: + name: bitcoind-chain-coordinator-service + namespace: "{namespace}" +spec: + ports: + - name: p2p + port: 18444 + protocol: TCP + targetPort: 18444 + - name: rpc + port: 18443 + protocol: TCP + targetPort: 18443 + - name: coordinator-in + port: 20445 + protocol: TCP + targetPort: 20445 + - name: coordinator-con + port: 20446 + protocol: TCP + targetPort: 20446 + selector: + name: bitcoind-chain-coordinator + type: ClusterIP diff --git a/templates/bitcoind-configmap.template.yaml b/templates/bitcoind-configmap.template.yaml new file mode 100644 index 0000000..e3a0b84 --- /dev/null +++ b/templates/bitcoind-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + bitcoin.conf: "{data}" +kind: ConfigMap +metadata: + name: bitcoind-conf + namespace: "{namespace}" diff --git a/templates/namespace.template.yaml b/templates/namespace.template.yaml new file mode 100644 index 0000000..21f2c71 --- /dev/null +++ b/templates/namespace.template.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "{namespace}" + labels: + name: "{namespace}" \ No newline at end of file diff --git a/templates/stacks-api-configmap.template.yaml b/templates/stacks-api-configmap.template.yaml new file mode 100644 index 0000000..12d6395 --- /dev/null +++ b/templates/stacks-api-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: stacks-api-conf + namespace: "{namespace}" diff --git a/templates/stacks-api-pod.template.yaml b/templates/stacks-api-pod.template.yaml new file mode 100644 index 0000000..3d27862 --- /dev/null +++ b/templates/stacks-api-pod.template.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + name: stacks-api + name: stacks-api + namespace: "{namespace}" +spec: + containers: + - name: stacks-api-container + envFrom: + - configMapRef: + name: stacks-api-conf + optional: false + image: hirosystems/stacks-blockchain-api + imagePullPolicy: Never + ports: + - containerPort: 3999 + name: api + protocol: TCP + - containerPort: 3700 + name: eventport + protocol: TCP + - name: stacks-api-postgres + envFrom: + - configMapRef: + name: stacks-api-postgres-conf + optional: false + image: postgres:14 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5432 + name: postgres + protocol: TCP + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: stacks-api-conf-volume + volumes: + - name: stacks-api-conf-volume + persistentVolumeClaim: + claimName: stacks-api-pvc \ No newline at end of file diff --git a/templates/stacks-api-postgres-configmap.template.yaml b/templates/stacks-api-postgres-configmap.template.yaml new file mode 100644 index 0000000..319203c --- /dev/null +++ b/templates/stacks-api-postgres-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: stacks-api-postgres-conf + namespace: "{namespace}" diff --git a/templates/stacks-api-pvc.template.yaml b/templates/stacks-api-pvc.template.yaml new file mode 100644 index 0000000..d87702d --- /dev/null +++ b/templates/stacks-api-pvc.template.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: stacks-api-pvc + namespace: "{namespace}" +spec: + accessModes: + - ReadWriteOnce + resources: + limits: + storage: 750Mi + requests: + storage: 500Mi + storageClassName: stacks-api-storage-class + volumeMode: Filesystem diff --git a/templates/stacks-api-service.template.yaml b/templates/stacks-api-service.template.yaml new file mode 100644 index 0000000..a0ac97d --- /dev/null +++ b/templates/stacks-api-service.template.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: stacks-api-service + namespace: "{namespace}" +spec: + ports: + - name: api + port: 3999 + protocol: TCP + targetPort: 3999 + - name: postgres + port: 5432 + protocol: TCP + targetPort: 5432 + - name: eventport + port: 3700 + protocol: TCP + targetPort: 3700 + selector: + name: stacks-api + type: ClusterIP diff --git a/templates/stacks-node-configmap.template.yaml b/templates/stacks-node-configmap.template.yaml new file mode 100644 index 0000000..5ca101a --- /dev/null +++ b/templates/stacks-node-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + Stacks.toml: "{data}" +kind: ConfigMap +metadata: + name: stacks-node-conf + namespace: "{namespace}" diff --git a/templates/stacks-node-service.template.yaml b/templates/stacks-node-service.template.yaml new file mode 100644 index 0000000..190b98c --- /dev/null +++ b/templates/stacks-node-service.template.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: stacks-node-service + namespace: "{namespace}" +spec: + ports: + - name: p2p + port: 20444 + protocol: TCP + - name: rpc + port: 20443 + protocol: TCP + selector: + name: stacks-node From 262af782a5e032d5f802889e8a673410ce2218f7 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 17 May 2023 14:09:41 -0400 Subject: [PATCH 05/60] use template yaml rather than json for deployments --- src/lib.rs | 921 +++++++++++++---------------------------------------- 1 file changed, 213 insertions(+), 708 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b2069ef..98a62b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,6 @@ -use std::{collections::BTreeMap, time::Duration}; - -// use k8s_experimentation::Thing; -use k8s_openapi::{ - api::core::v1::{ - ConfigMap, ConfigMapEnvSource, ConfigMapVolumeSource, Container, ContainerPort, - EnvFromSource, EnvVar, HostPathVolumeSource, Namespace, PersistentVolumeClaim, - PersistentVolumeClaimSpec, PersistentVolumeClaimVolumeSource, Pod, ResourceRequirements, - Service, ServicePort, Volume, VolumeMount, - }, - apimachinery::pkg::api::resource::Quantity, -}; +use k8s_openapi::api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}; use serde::{Deserialize, Serialize}; -use serde_json::json; +use std::{collections::BTreeMap, fs, time::Duration}; use kube::{ api::{Api, PostParams, ResourceExt}, @@ -45,31 +34,28 @@ pub struct StacksDevnetConfig { epoch_2_2: u32, pox_2_activation: u32, pox_2_unlock_height: u32, + accounts: Vec<(String, u64)>, // to remove and compute stacks_miner_secret_key_hex: String, miner_stx_address: String, } -const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-service"; +const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; -// const CHAIN_COORDINATOR_SERVICE_NAME: &str = "orchestrator-service"; const BITCOIND_P2P_PORT: &str = "18444"; const BITCOIND_RPC_PORT: &str = "18443"; const STACKS_NODE_P2P_PORT: &str = "20444"; const STACKS_NODE_RPC_PORT: &str = "20443"; const CHAIN_COORDINATOR_INGESTION_PORT: &str = "20445"; -const CHAIN_COORDINATOR_CONTROL_PORT: &str = "20446"; pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box> { let namespace = &config.namespace; - create_namespace(&namespace).await?; + deploy_namespace(&namespace).await?; deploy_bitcoin_node_pod(&config).await?; - // deploy_chain_coordinator(&namespace).await?; - - sleep(Duration::from_secs(10)); + sleep(Duration::from_secs(5)); deploy_stacks_node_pod(&config).await?; @@ -79,20 +65,16 @@ pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box Result<(), Box> { +async fn deploy_namespace(namespace_str: &str) -> Result<(), Box> { let client = Client::try_default().await?; let namespace_api: Api = kube::Api::all(client); - let namespace: Namespace = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "name": namespace, - "labels": { - "name": namespace - } - } - }))?; + let file = fs::File::open("./templates/namespace.template.yaml")?; + let mut namespace: Namespace = serde_yaml::from_reader(file)?; + + namespace.metadata.name = Some(namespace_str.to_owned()); + namespace.metadata.labels = Some(BTreeMap::from([("name".into(), namespace_str.to_owned())])); + let post_params = PostParams::default(); let created_namespace = namespace_api.create(&post_params, &namespace).await?; let name = created_namespace.name_any(); @@ -101,35 +83,74 @@ async fn create_namespace(namespace: &str) -> Result<(), Box Result<(), Box> { + let client = Client::try_default().await?; + let pods_api: Api = Api::namespaced(client, &namespace); + + let file = fs::File::open(format!("./templates/{}", template))?; + let mut pod: Pod = serde_yaml::from_reader(file)?; + pod.metadata.namespace = Some(namespace.to_owned()); + + let pp = PostParams::default(); + let response = pods_api.create(&pp, &pod).await?; + let name = response.name_any(); + println!("created pod {}", name); + Ok(()) +} + +async fn deploy_service(template: &str, namespace: &str) -> Result<(), Box> { + let client = Client::try_default().await?; + let service_api: Api = Api::namespaced(client, &namespace); + + let file = fs::File::open(format!("./templates/{}", template))?; + let mut service: Service = serde_yaml::from_reader(file)?; + service.metadata.namespace = Some(namespace.to_owned()); + + let pp = PostParams::default(); + let response = service_api.create(&pp, &service).await?; + let name = response.name_any(); + println!("created service {}", name); + Ok(()) +} + +async fn deploy_configmap( + template: &str, + namespace: &str, + configmap_data: Option>, ) -> Result<(), Box> { - const POD_NAME: &str = "bitcoind-chain-coordinator"; - const BITCOIND_CONTAINER_NAME: &str = "bitcoind-container"; - const BITCOIND_CONFIGMAP_NAME: &str = "bitcoind-conf"; - const BITCOIND_CONFIGMAP_VOLUME_NAME: &str = "bitcoind-conf-volume"; - const BITCOIND_IMAGE: &str = "quay.io/hirosystems/bitcoind:devnet-v3"; + let client = Client::try_default().await?; + let config_map_api: Api = kube::Api::::namespaced(client, &namespace); + + let file = fs::File::open(format!("./templates/{}", template))?; + let mut configmap: ConfigMap = serde_yaml::from_reader(file)?; - const CHAIN_COORDINATOR_CONTAINER_NAME: &str = "chain-coordinator-container"; - const CHAIN_COORDINATOR_CONFIGMAP_NAME: &str = "chain-coordinator-conf"; - const CHAIN_COORDINATOR_CONFIGMAP_VOLUME_NAME: &str = "chain-coordinator-conf-volume"; - const CHAIN_COORDINATOR_IMAGE: &str = "stacks-network"; + configmap.metadata.namespace = Some(namespace.to_owned()); + if let Some(configmap_data) = configmap_data { + let mut map = BTreeMap::new(); + for (key, value) in configmap_data { + map.insert(key.into(), value.into()); + } + configmap.data = Some(map); + } + + let post_params = PostParams::default(); + let created_config = config_map_api.create(&post_params, &configmap).await?; + let name = created_config.name_any(); + assert_eq!(configmap.name_any(), name); + println!("Created {}", name); + Ok(()) +} +async fn deploy_bitcoin_node_pod( + config: &StacksDevnetConfig, +) -> Result<(), Box> { let bitcoind_p2p_port = BITCOIND_P2P_PORT.parse::()?; let bitcoind_rpc_port = BITCOIND_RPC_PORT.parse::()?; - let coordinator_ingestion_port = CHAIN_COORDINATOR_INGESTION_PORT.parse::()?; - let coordinator_control_port = CHAIN_COORDINATOR_CONTROL_PORT.parse::()?; - let project_path = String::from("/foo2"); let namespace = &config.namespace; - // deploy configmap for bitcoin node - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let bitcoind_conf = format!( - r#" + let bitcoind_conf = format!( + r#" server=1 regtest=1 rpcallowip=0.0.0.0/0 @@ -152,216 +173,41 @@ async fn deploy_bitcoin_node_pod( rpcbind=0.0.0.0:{} rpcport={} "#, - config.bitcoin_node_username, - config.bitcoin_node_password, - bitcoind_p2p_port, - bitcoind_rpc_port, - bitcoind_rpc_port - ); - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": BITCOIND_CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "bitcoin.conf": bitcoind_conf - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } - - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let bitcoin_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": {"name": POD_NAME} - }, - "spec": { - "containers": Some(vec![ - Container { - name: BITCOIND_CONTAINER_NAME.into(), - image: Some(BITCOIND_IMAGE.into()), - command: Some(vec![ - "/usr/local/bin/bitcoind".into(), - "-conf=/etc/bitcoin/bitcoin.conf".into(), - "-nodebuglogfile".into(), - "-pid=/run/bitcoind.pid".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: bitcoind_p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ContainerPort { - container_port: bitcoind_rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ContainerPort { - container_port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("orchestrator".into()), - ..Default::default() - }, - ]), - volume_mounts: Some(vec![ VolumeMount { - name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/etc/bitcoin".into(), - read_only: Some(true), - ..Default::default() - }]), - ..Default::default() - }, - Container { - name: CHAIN_COORDINATOR_CONTAINER_NAME.into(), - image: Some(CHAIN_COORDINATOR_IMAGE.into()), - image_pull_policy: Some("Never".into()), - command: Some(vec![ - "./stacks-network".into(), - "--manifest-path=/etc/stacks-network/project/Clarinet.toml".into(), - "--deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml".into(), - "--project-root-path=/etc/stacks-network/project/".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("coordinator-in".into()), - ..Default::default() - }, - ContainerPort { - container_port: coordinator_control_port, - protocol: Some("TCP".into()), - name: Some("coordinator-con".into()), - ..Default::default() - }, - ]), - volume_mounts: Some(vec![ - VolumeMount { - name: "project".into(), - mount_path: "/etc/stacks-network/project".into(), - read_only: Some(false), - ..Default::default() - } - ]), - ..Default::default() - } - ]), - "volumes": Some(vec![ - Volume { - name: BITCOIND_CONFIGMAP_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: Some(BITCOIND_CONFIGMAP_NAME.into()) - , ..Default::default() - }), - ..Default::default() - }, - Volume { - name: "project".into(), - host_path: Some(HostPathVolumeSource { path: project_path, type_: Some("Directory".into())}), - ..Default::default() - } - ]) - }}))?; + config.bitcoin_node_username, + config.bitcoin_node_password, + bitcoind_p2p_port, + bitcoind_rpc_port, + bitcoind_rpc_port + ); + + deploy_configmap( + "bitcoind-configmap.template.yaml", + &namespace, + Some(vec![("bitcoin.conf", &bitcoind_conf)]), + ) + .await?; + + deploy_pod("bitcoind-chain-coordinator-pod.template.yaml", &namespace).await?; + + deploy_service( + "bitcoind-chain-coordinator-service.template.yaml", + namespace, + ) + .await?; - let pp = PostParams::default(); - let response = pods_api.create(&pp, &bitcoin_pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - } - - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), BITCOIND_CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, - "namespace": namespace - }, - "spec": { - "ports": Some(vec![ - ServicePort { - port: bitcoind_p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ServicePort { - port: bitcoind_rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ServicePort { - port: coordinator_ingestion_port, - protocol: Some("TCP".into()), - name: Some("coordinator-in".into()), - ..Default::default() - }, - ServicePort { - port: coordinator_control_port, - protocol: Some("TCP".into()), - name: Some("coordinator-con".into()), - ..Default::default() - } - ]), - "selector": {"name": POD_NAME}, - } - }))?; - - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } Ok(()) } async fn deploy_stacks_node_pod( config: &StacksDevnetConfig, ) -> Result<(), Box> { - const POD_NAME: &str = "stacks-node"; - const CONTAINER_NAME: &str = "stacks-node-container"; - const CONFIGMAP_NAME: &str = "stacks-node-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "stacks-node-conf-volume"; - const STACKS_NODE_IMAGE: &str = "quay.io/hirosystems/stacks-node:devnet-v3"; - let p2p_port = STACKS_NODE_P2P_PORT.parse::()?; let rpc_port = STACKS_NODE_RPC_PORT.parse::()?; let namespace = &config.namespace; - // deploy configmap - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - let stacks_conf = { - let mut stacks_conf = format!( - r#" + let stacks_conf = { + let mut stacks_conf = format!( + r#" [node] working_dir = "/devnet" rpc_bind = "0.0.0.0:{}" @@ -389,34 +235,45 @@ async fn deploy_stacks_node_pod( block_reward_recipient = "{}" # microblock_attempt_time_ms = 15000 "#, - rpc_port, - p2p_port, - config.stacks_miner_secret_key_hex, - config.stacks_miner_secret_key_hex, - config.stacks_node_wait_time_for_microblocks, - config.stacks_node_first_attempt_time_ms, - config.stacks_node_subsequent_attempt_time_ms, - config.miner_coinbase_recipient - ); - - let balance: u64 = 100_000_000_000_000; + rpc_port, + p2p_port, + config.stacks_miner_secret_key_hex, + config.stacks_miner_secret_key_hex, + config.stacks_node_wait_time_for_microblocks, + config.stacks_node_first_attempt_time_ms, + config.stacks_node_subsequent_attempt_time_ms, + config.miner_coinbase_recipient + ); + + for (address, balance) in config.accounts.iter() { stacks_conf.push_str(&format!( r#" + [[ustx_balance]] + address = "{}" + amount = {} + "#, + address, balance + )); + } + + let balance: u64 = 100_000_000_000_000; + stacks_conf.push_str(&format!( + r#" [[ustx_balance]] address = "{}" amount = {} "#, - config.miner_coinbase_recipient, balance - )); + config.miner_coinbase_recipient, balance + )); - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let bitcoind_chain_coordinator_host = format!( - "{}.{}", - &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host - ); + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let bitcoind_chain_coordinator_host = format!( + "{}.{}", + &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host + ); - stacks_conf.push_str(&format!( - r#" + stacks_conf.push_str(&format!( + r#" # Add orchestrator (docker-host) as an event observer [[events_observer]] endpoint = "{}:{}" @@ -424,23 +281,23 @@ async fn deploy_stacks_node_pod( include_data_events = true events_keys = ["*"] "#, - bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT - )); - - // stacks_conf.push_str(&format!( - // r#" - // # Add stacks-api as an event observer - // [[events_observer]] - // endpoint = "host.docker.internal:{}" - // retry_count = 255 - // include_data_events = false - // events_keys = ["*"] - // "#, - // 30007, - // )); - - stacks_conf.push_str(&format!( - r#" + bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT + )); + + // stacks_conf.push_str(&format!( + // r#" + // # Add stacks-api as an event observer + // [[events_observer]] + // endpoint = "host.docker.internal:{}" + // retry_count = 255 + // include_data_events = false + // events_keys = ["*"] + // "#, + // 30007, + // )); + + stacks_conf.push_str(&format!( + r#" [burnchain] chain = "bitcoin" mode = "krypton" @@ -454,15 +311,15 @@ async fn deploy_stacks_node_pod( rpc_port = {} peer_port = {} "#, - bitcoind_chain_coordinator_host, - config.bitcoin_node_username, - config.bitcoin_node_password, - CHAIN_COORDINATOR_INGESTION_PORT, - BITCOIND_P2P_PORT - )); + bitcoind_chain_coordinator_host, + config.bitcoin_node_username, + config.bitcoin_node_password, + CHAIN_COORDINATOR_INGESTION_PORT, + BITCOIND_P2P_PORT + )); - stacks_conf.push_str(&format!( - r#" + stacks_conf.push_str(&format!( + r#" pox_2_activation = {} [[burnchain.epochs]] @@ -481,437 +338,85 @@ async fn deploy_stacks_node_pod( epoch_name = "2.1" start_height = {} "#, - config.pox_2_activation, config.epoch_2_0, config.epoch_2_05, config.epoch_2_1 - )); - stacks_conf - }; - - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "Stacks.toml": stacks_conf - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } + config.pox_2_activation, config.epoch_2_0, config.epoch_2_05, config.epoch_2_1 + )); + stacks_conf + }; - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let bitcoin_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": {"name": POD_NAME} - }, - "spec": { - "containers": Some(vec![ Container { - name: CONTAINER_NAME.into(), - image: Some(STACKS_NODE_IMAGE.into()), - command: Some(vec![ - "stacks-node".into(), - "start".into(), - "--config=/src/stacks-node/Stacks.toml".into(), - ]), - ports: Some(vec![ - ContainerPort { - container_port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - }, - ContainerPort { - container_port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }, - ]), - env: Some(vec![ - EnvVar { - name: String::from("STACKS_LOG_PP"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_LOG_DEBUG"), - value: Some(String::from("0")), - ..Default::default() - } - ]), - volume_mounts: Some(vec![ VolumeMount { - name: CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/src/stacks-node".into(), - read_only: Some(true), - ..Default::default() - }]), - ..Default::default() - }]), - "volumes": Some(vec![ - Volume { - name: CONFIGMAP_VOLUME_NAME.into(), - config_map: Some(ConfigMapVolumeSource { - name: Some(CONFIGMAP_NAME.into()) - , ..Default::default() - }), - ..Default::default() - }]) - }}))?; + deploy_configmap( + "stacks-node-configmap.template.yaml", + &namespace, + Some(vec![("Stacks.toml", &stacks_conf)]), + ) + .await?; - let pp = PostParams::default(); - let response = pods_api.create(&pp, &bitcoin_pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - } + deploy_pod("stacks-node-pod.template.yaml", &namespace).await?; - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": STACKS_NODE_SERVICE_NAME, - "namespace": namespace - }, - "spec": { - "ports": Some(vec![ServicePort { - port: p2p_port, - protocol: Some("TCP".into()), - name: Some("p2p".into()), - ..Default::default() - },ServicePort { - port: rpc_port, - protocol: Some("TCP".into()), - name: Some("rpc".into()), - ..Default::default() - }]), - "selector": {"name": POD_NAME}, - } - }))?; + deploy_service("stacks-node-service.template.yaml", namespace).await?; - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } Ok(()) } async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box> { - // constants for stacks pod, services, and config - const POD_NAME: &str = "stacks-api"; - const POSTGRES_CONTAINER_NAME: &str = "stacks-api-postgres"; - const API_CONTAINER_NAME: &str = "stacks-api-container"; - const CONFIGMAP_NAME: &str = "stacks-api-conf"; - const CONFIGMAP_VOLUME_NAME: &str = "stacks-api-conf-volume"; - const PVC_NAME: &str = "stacks-api-pvc"; - const STORAGE_CLASS_NAME: &str = "stacks-api-storage-class"; - const SERVICE_NAME: &str = "stacks-api-service"; - const STACKS_API_IMAGE: &str = "hirosystems/stacks-blockchain-api"; - const POSTGRES_IMAGE: &str = "postgres:14"; - - // deploy configmap - { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let config_map: ConfigMap = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": CONFIGMAP_NAME, - "namespace": namespace - }, - "data": { - "POSTGRES_PASSWORD": "postgres", - "POSTGRES_DB": "stacks_api", - - }, - }))?; - - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &config_map).await?; - let name = created_config.name_any(); - assert_eq!(config_map.name_any(), name); - println!("Created {}", name); - } + // configmap env vars for pg conatainer + let stacks_api_pg_env = Vec::from([ + ("POSTGRES_PASSWORD", "postgres"), + ("POSTGRES_DB", "stacks_api"), + ]); + deploy_configmap( + "stacks-api-postgres-configmap.template.yaml", + &namespace, + Some(stacks_api_pg_env), + ) + .await?; + + // configmap env vars for api conatainer + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); + let stacks_api_env = Vec::from([ + ("STACKS_CORE_RPC_HOST", &stacks_node_host[..]), + ("STACKS_BLOCKCHAIN_API_DB", "pg"), + ("STACKS_CORE_RPC_PORT", STACKS_NODE_RPC_PORT), + ("STACKS_BLOCKCHAIN_API_PORT", "3999"), + ("STACKS_BLOCKCHAIN_API_HOST", "0.0.0.0"), + ("STACKS_CORE_EVENT_PORT", "3700"), + ("STACKS_CORE_EVENT_HOST", "0.0.0.0"), + ("STACKS_API_ENABLE_FT_METADATA", "1"), + ("PG_HOST", "0.0.0.0"), + ("PG_PORT", "5432"), + ("PG_USER", "postgres"), + ("PG_PASSWORD", "postgres"), + ("PG_DATABASE", "stacks_api"), + ("STACKS_CHAIN_ID", "2147483648"), + ("V2_POX_MIN_AMOUNT_USTX", "90000000260"), + ("NODE_ENV", "production"), + ("STACKS_API_LOG_LEVEL", "debug"), + ]); + deploy_configmap( + "stacks-api-configmap.template.yaml", + &namespace, + Some(stacks_api_env), + ) + .await?; // deploy persistent volume claim { let client = Client::try_default().await?; - let pvc_api: Api = - kube::Api::::namespaced(client, &namespace); - - let mut requests_map: BTreeMap = BTreeMap::new(); - requests_map.insert("storage".to_string(), Quantity("500Mi".to_string())); - let mut limits_map: BTreeMap = BTreeMap::new(); - limits_map.insert("storage".to_string(), Quantity("750Mi".to_string())); - - let pvc: PersistentVolumeClaim = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "name": PVC_NAME, - "namespace": namespace, - }, - "spec": PersistentVolumeClaimSpec { - storage_class_name: Some(STORAGE_CLASS_NAME.to_string()), - access_modes: Some(vec!["ReadWriteOnce".to_string()]), - resources: Some( ResourceRequirements { - requests: Some(requests_map), - limits: Some(limits_map), - }), - ..Default::default() - }, - }))?; - - let post_params = PostParams::default(); - let created_config = pvc_api.create(&post_params, &pvc).await?; - let name = created_config.name_any(); - assert_eq!(pvc.name_any(), name); - println!("Created {}", name); - } + let pvc_api: Api = Api::namespaced(client, &namespace); - // deploy pod - { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); - - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); - - let env: Vec = vec![ - EnvVar { - name: String::from("STACKS_CORE_RPC_HOST"), - value: Some(format!("{}", stacks_node_host)), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_DB"), - value: Some(String::from("pg")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_RPC_PORT"), - value: Some(STACKS_NODE_RPC_PORT.to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_PORT"), - value: Some("3999".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_BLOCKCHAIN_API_HOST"), - value: Some(String::from("0.0.0.0")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_EVENT_PORT"), - value: Some("3700".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CORE_EVENT_HOST"), - value: Some(String::from("0.0.0.0")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_API_ENABLE_FT_METADATA"), - value: Some(String::from("1")), - ..Default::default() - }, - EnvVar { - name: String::from("PG_HOST"), - value: Some(format!("0.0.0.0",)), - ..Default::default() - }, - EnvVar { - name: String::from("PG_PORT"), - value: Some(String::from("5432")), - ..Default::default() - }, - EnvVar { - name: String::from("PG_USER"), - value: Some("postgres".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("PG_PASSWORD"), - value: Some("postgres".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("PG_DATABASE"), - value: Some("stacks_api".to_string()), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_CHAIN_ID"), - value: Some(String::from("2147483648")), - ..Default::default() - }, - EnvVar { - name: String::from("V2_POX_MIN_AMOUNT_USTX"), - value: Some(String::from("90000000260")), - ..Default::default() - }, - EnvVar { - name: String::from("NODE_ENV"), - value: Some(String::from("production")), - ..Default::default() - }, - EnvVar { - name: String::from("STACKS_API_LOG_LEVEL"), - value: Some(String::from("debug")), - ..Default::default() - }, - ]; - - let stacks_api_pod: Pod = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": POD_NAME, - "namespace": namespace, - "labels": { - "name": POD_NAME - } - }, - "spec": { - "containers": Some(vec![ - Container { - name: API_CONTAINER_NAME.into(), - image: Some(STACKS_API_IMAGE.into()), - image_pull_policy: Some("Never".into()), - ports: Some(vec![ - ContainerPort { - container_port: 3999, - protocol: Some(String::from("TCP")), - name: Some("api".into()), - ..Default::default() - }, - ContainerPort { - container_port: 3700, - protocol: Some(String::from("TCP")), - name: Some("eventport".into()), - ..Default::default() - }, - ]), - env: Some(env), - ..Default::default() - }, - Container { - name: POSTGRES_CONTAINER_NAME.into(), - image: Some(POSTGRES_IMAGE.to_string()), - ports: Some(vec![ - ContainerPort { - container_port: 5432, - protocol: Some(String::from("TCP")), - name: Some("postgres".into()), - ..Default::default() - }, - ]), - env_from: Some(vec![ - EnvFromSource { - config_map_ref: Some( ConfigMapEnvSource{name: Some(CONFIGMAP_NAME.to_string()), optional: Some(false)}), - ..Default::default() - } - ]), - volume_mounts: Some(vec![ VolumeMount { - name: CONFIGMAP_VOLUME_NAME.into(), - mount_path: "/var/lib/postgresql/data".into(), - ..Default::default() - }]), - ..Default::default() - }]), - "volumes": Some(vec![ - Volume { - name: CONFIGMAP_VOLUME_NAME.into(), - persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { - claim_name: PVC_NAME.into() - , ..Default::default() - }), - ..Default::default() - }]) - }}))?; + let file = fs::File::open("./templates/stacks-api-pvc.template.yaml")?; + let mut pvc: PersistentVolumeClaim = serde_yaml::from_reader(file)?; + pvc.metadata.namespace = Some(namespace.to_owned()); let pp = PostParams::default(); - let response = pods_api.create(&pp, &stacks_api_pod).await?; + let response = pvc_api.create(&pp, &pvc).await?; let name = response.name_any(); println!("created pod {}", name); } - // deploy service - { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let mut selector = BTreeMap::::new(); - selector.insert("name".into(), API_CONTAINER_NAME.into()); - - let service: Service = serde_json::from_value(json!({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": SERVICE_NAME, - "namespace": namespace, - }, - "spec": { - "ports": Some(vec![ServicePort { - port: 3999, - protocol: Some("TCP".into()), - name: Some("api".into()), - ..Default::default() - }, - ServicePort { - port: 5432, - protocol: Some("TCP".into()), - name: Some("postgres".into()), - ..Default::default() - }, - ServicePort { - port: 3700, - protocol: Some("TCP".into()), - name: Some("eventport".into()), - ..Default::default() - }]), - "selector": { - "name": POD_NAME - }, - } - }))?; + deploy_pod("stacks-api-pod.template.yaml", &namespace).await?; + + deploy_service("stacks-api-service.template.yaml", &namespace).await?; - let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - } Ok(()) } From e69c272e2057414f9f5e7849c3b971e564f306b4 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 18 May 2023 10:00:25 -0400 Subject: [PATCH 06/60] add missed template yaml --- templates/stacks-node-pod.template.yaml | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 templates/stacks-node-pod.template.yaml diff --git a/templates/stacks-node-pod.template.yaml b/templates/stacks-node-pod.template.yaml new file mode 100644 index 0000000..e49706f --- /dev/null +++ b/templates/stacks-node-pod.template.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + name: stacks-node + name: stacks-node + namespace: "{namespace}" +spec: + containers: + - command: + - stacks-node + - start + - --config=/src/stacks-node/Stacks.toml + env: + - name: STACKS_LOG_PP + value: "1" + - name: BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE + value: "1" + - name: STACKS_LOG_DEBUG + value: "0" + image: quay.io/hirosystems/stacks-node:devnet-v3 + imagePullPolicy: IfNotPresent + name: stacks-node-container + ports: + - containerPort: 20444 + name: p2p + protocol: TCP + - containerPort: 20443 + name: rpc + protocol: TCP + volumeMounts: + - mountPath: /src/stacks-node + name: stacks-node-conf-volume + readOnly: true + volumes: + - configMap: + name: stacks-node-conf + name: stacks-node-conf-volume \ No newline at end of file From 2407988d25c9a2a4dfb0a621db904caa5ab7736c Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 18 May 2023 10:01:58 -0400 Subject: [PATCH 07/60] add route to delete deployment --- Cargo.lock | 1698 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 19 + src/lib.rs | 60 +- src/main.rs | 46 +- 4 files changed, 1807 insertions(+), 16 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c598e81 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1698 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "num-integer", + "num-traits", + "serde", + "winapi", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.15", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-openssl" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" +dependencies = [ + "http", + "hyper", + "linked_hash_set", + "once_cell", + "openssl", + "openssl-sys", + "parking_lot", + "tokio", + "tokio-openssl", + "tower-layer", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "k8s-experimentation" +version = "0.1.0" +dependencies = [ + "futures", + "k8s-openapi", + "kube", + "serde", + "serde_json", + "serde_qs", + "serde_yaml", + "tiny_http", + "tokio", + "toml", + "url", +] + +[[package]] +name = "k8s-openapi" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd990069640f9db34b3b0f7a1afc62a05ffaa3be9b66aa3c313f58346df7f788" +dependencies = [ + "base64 0.21.0", + "bytes", + "chrono", + "http", + "percent-encoding", + "serde", + "serde-value", + "serde_json", + "url", +] + +[[package]] +name = "kube" +version = "0.82.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7d3d52dd5c871991679102e80dfb192faaaa09fecdbccdd8c55af264ce7a8f" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.82.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "544339f1665488243f79080441cacb09c997746fd763342303e66eebb9d3ba13" +dependencies = [ + "base64 0.20.0", + "bytes", + "chrono", + "dirs-next", + "either", + "futures", + "http", + "http-body", + "hyper", + "hyper-openssl", + "hyper-timeout", + "jsonpath_lib", + "k8s-openapi", + "kube-core", + "openssl", + "pem", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.82.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25983d07f414dfffba08c5951fe110f649113416b1d8e22f7c89c750eb2555a7" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "once_cell", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-runtime" +version = "0.82.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "125331201e3073707ac79c294c89021faa76c84da3a566a3749a2a93d295c98a" +dependencies = [ + "ahash", + "async-trait", + "backoff", + "derivative", + "futures", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "libc" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl" +version = "0.10.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "openssl-sys" +version = "0.9.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "base64 0.20.0", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fe4c18d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "k8s-experimentation" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +kube = { version="0.82.2", features = ["client", "runtime"] } +k8s-openapi = { version = "0.18.0", features = ["v1_25"] } +futures = "0.3.28" +tokio = { version = "1.27.0", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.96" +serde_yaml = "0.9.21" +toml = "0.7.3" +tiny_http = "0.12.0" +url = "2.3.1" +serde_qs = "0.12.0" diff --git a/src/lib.rs b/src/lib.rs index 98a62b1..07fedec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ -use k8s_openapi::api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}; -use serde::{Deserialize, Serialize}; +use futures::try_join; +use k8s_openapi::{ + api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, + NamespaceResourceScope, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, fs, time::Duration}; use kube::{ - api::{Api, PostParams, ResourceExt}, + api::{Api, DeleteParams, PostParams, ResourceExt}, Client, }; use std::thread::sleep; @@ -65,6 +69,24 @@ pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box Result<(), Box> { + try_join!( + delete_namespace(namespace), + delete_resource::(namespace, "bitcoind-chain-coordinator"), + delete_resource::(namespace, "stacks-node"), + delete_resource::(namespace, "stacks-api"), + delete_resource::(namespace, "bitcoind-conf"), + delete_resource::(namespace, "stacks-node-conf"), + delete_resource::(namespace, "stacks-api-conf"), + delete_resource::(namespace, "stacks-api-postgres-conf"), + delete_resource::(namespace, "bitcoind-chain-coordinator-service"), + delete_resource::(namespace, "stacks-node-service"), + delete_resource::(namespace, "stacks-api-service"), + delete_resource::(namespace, "stacks-api-pvc"), + )?; + Ok(()) +} + async fn deploy_namespace(namespace_str: &str) -> Result<(), Box> { let client = Client::try_default().await?; let namespace_api: Api = kube::Api::all(client); @@ -420,3 +442,35 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box>( + namespace: &str, + resource_name: &str, +) -> Result<(), Box> +where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, +{ + let client = Client::try_default().await?; + let api: Api = Api::namespaced(client, &namespace); + let dp = DeleteParams::default(); + api.delete(resource_name, &dp).await?.map_left(|del| { + assert_eq!(del.name_any(), resource_name); + println!("Deleting resource started: {:?}", del); + }); + Ok(()) +} + +async fn delete_namespace(namespace_str: &str) -> Result<(), Box> { + let client = Client::try_default().await?; + let api: Api = kube::Api::all(client); + + let dp = DeleteParams::default(); + api.delete(namespace_str, &dp).await?.map_left(|del| { + assert_eq!(del.name_any(), namespace_str); + println!("Deleting resource started: {:?}", del); + }); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 4222e01..c48f4eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,14 @@ -use k8s_experimentation::{deploy_devnet, StacksDevnetConfig}; +use std::str::FromStr; + +use k8s_experimentation::{delete_devnet, deploy_devnet, StacksDevnetConfig}; +use serde::Deserialize; use tiny_http::{Method, Response, Server}; +use url::Url; + +#[derive(Deserialize, Debug)] +struct DeleteRequest { + network: String, +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -7,7 +16,7 @@ async fn main() -> Result<(), Box> { const PORT: &str = "8477"; let endpoint: String = HOST.to_owned() + ":" + PORT; - let server = Server::http(endpoint).unwrap(); + let server = Server::http(&endpoint).unwrap(); loop { // blocks until the next request is received let mut request = match server.recv() { @@ -18,21 +27,32 @@ async fn main() -> Result<(), Box> { } }; + let url = request.url(); + let full_url = format!("http://{}{}", &endpoint, url); + let url = Url::from_str(&full_url)?; match request.method() { - Method::Post => { - let url = request.url(); - println!("{}", url); - match url { - "/api/v1/networks" => { - let mut content = String::new(); - request.as_reader().read_to_string(&mut content).unwrap(); - let config: StacksDevnetConfig = serde_json::from_str(&content)?; - deploy_devnet(config).await?; + Method::Post => match url.path() { + "/api/v1/networks" => { + let mut content = String::new(); + request.as_reader().read_to_string(&mut content).unwrap(); + let config: StacksDevnetConfig = serde_json::from_str(&content)?; + deploy_devnet(config).await?; + request.respond(Response::empty(200))? + } + _ => request.respond(Response::empty(404))?, + }, + Method::Delete => match url.path() { + "/api/v1/network" => { + if let Some(query) = url.query() { + let delete_request: DeleteRequest = serde_qs::from_str(query)?; + delete_devnet(&delete_request.network).await?; request.respond(Response::empty(200))? + } else { + request.respond(Response::empty(400))?; } - _ => request.respond(Response::empty(404))?, } - } + _ => request.respond(Response::empty(404))?, + }, //let not_implemented = Response::new( StatusCode::from(501), request.headers(), "not implemented", None, None ); _ => request.respond(Response::empty(501))?, } From abf2e17e9ea204f8fbf117c2152ce71a6b64545b Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 19 May 2023 17:01:19 -0400 Subject: [PATCH 08/60] make services nodeport type --- templates/bitcoind-chain-coordinator-service.template.yaml | 6 +++++- templates/stacks-api-service.template.yaml | 5 ++++- templates/stacks-node-service.template.yaml | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/templates/bitcoind-chain-coordinator-service.template.yaml b/templates/bitcoind-chain-coordinator-service.template.yaml index 3e41c78..feee3aa 100644 --- a/templates/bitcoind-chain-coordinator-service.template.yaml +++ b/templates/bitcoind-chain-coordinator-service.template.yaml @@ -9,18 +9,22 @@ spec: port: 18444 protocol: TCP targetPort: 18444 + nodePort: 30000 - name: rpc port: 18443 protocol: TCP targetPort: 18443 + nodePort: 30001 - name: coordinator-in port: 20445 protocol: TCP targetPort: 20445 + nodePort: 30005 - name: coordinator-con port: 20446 protocol: TCP targetPort: 20446 + nodePort: 30006 selector: name: bitcoind-chain-coordinator - type: ClusterIP + type: NodePort diff --git a/templates/stacks-api-service.template.yaml b/templates/stacks-api-service.template.yaml index a0ac97d..25d6c3c 100644 --- a/templates/stacks-api-service.template.yaml +++ b/templates/stacks-api-service.template.yaml @@ -9,14 +9,17 @@ spec: port: 3999 protocol: TCP targetPort: 3999 + nodePort: 30007 - name: postgres port: 5432 protocol: TCP targetPort: 5432 + nodePort: 30008 - name: eventport port: 3700 protocol: TCP targetPort: 3700 + nodePort: 30009 selector: name: stacks-api - type: ClusterIP + type: NodePort diff --git a/templates/stacks-node-service.template.yaml b/templates/stacks-node-service.template.yaml index 190b98c..75d9290 100644 --- a/templates/stacks-node-service.template.yaml +++ b/templates/stacks-node-service.template.yaml @@ -8,8 +8,11 @@ spec: - name: p2p port: 20444 protocol: TCP + nodePort: 30002 - name: rpc port: 20443 protocol: TCP + nodePort: 30003 selector: name: stacks-node + type: NodePort From 81ef8c4d7352a82d446b9486bba2c471af808dca Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 19 May 2023 17:01:54 -0400 Subject: [PATCH 09/60] rename var --- templates/stacks-api-pvc.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/stacks-api-pvc.template.yaml b/templates/stacks-api-pvc.template.yaml index d87702d..cb90749 100644 --- a/templates/stacks-api-pvc.template.yaml +++ b/templates/stacks-api-pvc.template.yaml @@ -11,5 +11,5 @@ spec: storage: 750Mi requests: storage: 500Mi - storageClassName: stacks-api-storage-class + storageClassName: devnet-storage-class volumeMode: Filesystem From 1f6d98d10cb19b3e470548a9d3192d1e89d7ae4d Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 19 May 2023 17:02:22 -0400 Subject: [PATCH 10/60] fix error handling on deletion --- src/lib.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 07fedec..be3a60b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,20 +70,23 @@ pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box Result<(), Box> { - try_join!( - delete_namespace(namespace), - delete_resource::(namespace, "bitcoind-chain-coordinator"), - delete_resource::(namespace, "stacks-node"), - delete_resource::(namespace, "stacks-api"), - delete_resource::(namespace, "bitcoind-conf"), - delete_resource::(namespace, "stacks-node-conf"), - delete_resource::(namespace, "stacks-api-conf"), - delete_resource::(namespace, "stacks-api-postgres-conf"), - delete_resource::(namespace, "bitcoind-chain-coordinator-service"), - delete_resource::(namespace, "stacks-node-service"), - delete_resource::(namespace, "stacks-api-service"), - delete_resource::(namespace, "stacks-api-pvc"), - )?; + let _ = delete_namespace(namespace).await; + let _ = delete_resource::(namespace, "bitcoind-chain-coordinator").await; + let _ = delete_resource::(namespace, "stacks-node").await; + let _ = delete_resource::(namespace, "stacks-api").await; + let _ = delete_resource::(namespace, "bitcoind-conf").await; + let _ = delete_resource::(namespace, "stacks-node-conf").await; + let _ = delete_resource::(namespace, "stacks-api-conf").await; + let _ = delete_resource::(namespace, "stacks-api-postgres-conf").await; + let _ = delete_resource::(namespace, "deployment-plan-conf").await; + let _ = delete_resource::(namespace, "devnet-conf").await; + let _ = delete_resource::(namespace, "project-dir-conf").await; + let _ = delete_resource::(namespace, "namespace-conf").await; + let _ = delete_resource::(namespace, "project-manifest-conf").await; + let _ = delete_resource::(namespace, "bitcoind-chain-coordinator-service").await; + let _ = delete_resource::(namespace, "stacks-node-service").await; + let _ = delete_resource::(namespace, "stacks-api-service").await; + let _ = delete_resource::(namespace, "stacks-api-pvc").await; Ok(()) } From fc24c8ab932a5f03f9c566e18c774f3818242e84 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 19 May 2023 17:02:46 -0400 Subject: [PATCH 11/60] deploy configmaps for chain coordinator --- src/lib.rs | 45 ++++++++++++++++++- ...tcoind-chain-coordinator-pod.template.yaml | 35 +++++++++++---- ...rd-deployment-plan-configmap.template.yaml | 7 +++ ...chain-coord-devnet-configmap.template.yaml | 7 +++ ...in-coord-namespace-configmap.template.yaml | 7 +++ ...-coord-project-dir-configmap.template.yaml | 7 +++ ...d-project-manifest-configmap.template.yaml | 7 +++ 7 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 templates/chain-coord-deployment-plan-configmap.template.yaml create mode 100644 templates/chain-coord-devnet-configmap.template.yaml create mode 100644 templates/chain-coord-namespace-configmap.template.yaml create mode 100644 templates/chain-coord-project-dir-configmap.template.yaml create mode 100644 templates/chain-coord-project-manifest-configmap.template.yaml diff --git a/src/lib.rs b/src/lib.rs index be3a60b..aa849e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -use futures::try_join; use k8s_openapi::{ api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, NamespaceResourceScope, @@ -39,6 +38,11 @@ pub struct StacksDevnetConfig { pox_2_activation: u32, pox_2_unlock_height: u32, accounts: Vec<(String, u64)>, + // needed for chain coordinator + project_manifest: String, + devnet_config: String, + deployment_plan: String, + contracts: Vec<(String, String)>, // to remove and compute stacks_miner_secret_key_hex: String, miner_stx_address: String, @@ -212,6 +216,45 @@ async fn deploy_bitcoin_node_pod( ) .await?; + deploy_configmap( + "chain-coord-namespace-configmap.template.yaml", + &namespace, + Some(vec![("NAMESPACE", &namespace)]), + ) + .await?; + + deploy_configmap( + "chain-coord-project-manifest-configmap.template.yaml", + &namespace, + Some(vec![("Clarinet.toml", &config.project_manifest)]), + ) + .await?; + + deploy_configmap( + "chain-coord-devnet-configmap.template.yaml", + &namespace, + Some(vec![("Devnet.toml", &config.devnet_config)]), + ) + .await?; + + deploy_configmap( + "chain-coord-deployment-plan-configmap.template.yaml", + &namespace, + Some(vec![("default.devnet-plan.yaml", &config.deployment_plan)]), + ) + .await?; + + let mut contracts: Vec<(&str, &str)> = vec![]; + for (contract_name, contract_source) in &config.contracts { + contracts.push((contract_name, contract_source)); + } + deploy_configmap( + "chain-coord-project-dir-configmap.template.yaml", + &namespace, + Some(contracts), + ) + .await?; + deploy_pod("bitcoind-chain-coordinator-pod.template.yaml", &namespace).await?; deploy_service( diff --git a/templates/bitcoind-chain-coordinator-pod.template.yaml b/templates/bitcoind-chain-coordinator-pod.template.yaml index c919370..d5057b4 100644 --- a/templates/bitcoind-chain-coordinator-pod.template.yaml +++ b/templates/bitcoind-chain-coordinator-pod.template.yaml @@ -22,18 +22,22 @@ spec: - containerPort: 18443 name: rpc protocol: TCP - - containerPort: 20445 - name: orchestrator - protocol: TCP volumeMounts: - mountPath: /etc/bitcoin name: bitcoind-conf-volume readOnly: true - command: - ./stacks-network + - --namespace=$(NAMESPACE) - --manifest-path=/etc/stacks-network/project/Clarinet.toml - --deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml - --project-root-path=/etc/stacks-network/project/ + env: + - name: NAMESPACE + valueFrom: + configMapKeyRef: + name: namespace-conf + key: NAMESPACE image: stacks-network imagePullPolicy: Never name: chain-coordinator-container @@ -46,12 +50,27 @@ spec: protocol: TCP volumeMounts: - mountPath: /etc/stacks-network/project - name: project + name: project-manifest-conf-volume + - mountPath: /etc/stacks-network/project/settings + name: devnet-conf-volume + - mountPath: /etc/stacks-network/project/deployments + name: deployment-plan-conf-volume + - mountPath: /etc/stacks-network/project/contracts + name: project-dir-conf-volume volumes: - configMap: name: bitcoind-conf name: bitcoind-conf-volume - - hostPath: - path: /foo2 - type: Directory - name: project \ No newline at end of file + - configMap: + name: project-manifest-conf + name: project-manifest-conf-volume + - configMap: + name: devnet-conf + name: devnet-conf-volume + - configMap: + name: deployment-plan-conf + name: deployment-plan-conf-volume + - configMap: + name: project-dir-conf + name: project-dir-conf-volume + # when you create a network, we'll pass an augmented deployment plan that will include the contract source code. \ No newline at end of file diff --git a/templates/chain-coord-deployment-plan-configmap.template.yaml b/templates/chain-coord-deployment-plan-configmap.template.yaml new file mode 100644 index 0000000..6905c73 --- /dev/null +++ b/templates/chain-coord-deployment-plan-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: deployment-plan-conf + namespace: "{namespace}" diff --git a/templates/chain-coord-devnet-configmap.template.yaml b/templates/chain-coord-devnet-configmap.template.yaml new file mode 100644 index 0000000..0e58907 --- /dev/null +++ b/templates/chain-coord-devnet-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: devnet-conf + namespace: "{namespace}" diff --git a/templates/chain-coord-namespace-configmap.template.yaml b/templates/chain-coord-namespace-configmap.template.yaml new file mode 100644 index 0000000..15f97bc --- /dev/null +++ b/templates/chain-coord-namespace-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: namespace-conf + namespace: "{namespace}" diff --git a/templates/chain-coord-project-dir-configmap.template.yaml b/templates/chain-coord-project-dir-configmap.template.yaml new file mode 100644 index 0000000..c62df2c --- /dev/null +++ b/templates/chain-coord-project-dir-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: project-dir-conf + namespace: "{namespace}" diff --git a/templates/chain-coord-project-manifest-configmap.template.yaml b/templates/chain-coord-project-manifest-configmap.template.yaml new file mode 100644 index 0000000..1a70f6b --- /dev/null +++ b/templates/chain-coord-project-manifest-configmap.template.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: project-manifest-conf + namespace: "{namespace}" From 02f506297ea6c5ac2643f8d38dbd5b65a6a9a194 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 19 May 2023 17:03:09 -0400 Subject: [PATCH 12/60] yaml for cluster config --- templates/initial-config/kind.yaml | 30 +++++++++++++++++++++ templates/initial-config/storage-class.yaml | 14 ++++++++++ 2 files changed, 44 insertions(+) create mode 100644 templates/initial-config/kind.yaml create mode 100644 templates/initial-config/storage-class.yaml diff --git a/templates/initial-config/kind.yaml b/templates/initial-config/kind.yaml new file mode 100644 index 0000000..cdad75c --- /dev/null +++ b/templates/initial-config/kind.yaml @@ -0,0 +1,30 @@ +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +nodes: +- role: control-plane + extraMounts: + - hostPath: /Users/micaiahreid/work/stx-px + containerPath: /foo2 + extraPortMappings: + - containerPort: 30000 + hostPort: 18444 + - containerPort: 30001 + hostPort: 18443 + - containerPort: 30002 + hostPort: 20444 + - containerPort: 30003 + hostPort: 20443 + - containerPort: 30005 + hostPort: 20445 + - containerPort: 30006 + hostPort: 20446 + - containerPort: 30007 + hostPort: 3999 + - containerPort: 30008 + hostPort: 5432 + - containerPort: 30009 + hostPort: 3700 +- role: worker + extraMounts: + - hostPath: /Users/micaiahreid/work/stx-px + containerPath: /foo2 \ No newline at end of file diff --git a/templates/initial-config/storage-class.yaml b/templates/initial-config/storage-class.yaml new file mode 100644 index 0000000..b7a6d1c --- /dev/null +++ b/templates/initial-config/storage-class.yaml @@ -0,0 +1,14 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: devnet-storage-class + annotations: + openebs.io/cas-type: local + cas.openebs.io/config: | + - name: StorageType + value: hostpath + - name: BasePath + value: /var/local-hostpath +provisioner: openebs.io/local +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer \ No newline at end of file From 915e59418e3f3a2df164d3c3c8b7e49b9faf5264 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:10:14 -0400 Subject: [PATCH 13/60] add user/password to devnet config --- src/lib.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index aa849e0..e4a87ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -230,10 +230,21 @@ async fn deploy_bitcoin_node_pod( ) .await?; + let mut devnet_config = config.devnet_config.clone(); + //devnet_config.push_str("\n[devnet]"); + devnet_config.push_str(&format!( + "\nbitcoin_node_username = \"{}\"", + &config.bitcoin_node_username + )); + devnet_config.push_str(&format!( + "\nbitcoin_node_password = \"{}\"", + &config.bitcoin_node_password + )); + println!("{}", devnet_config); deploy_configmap( "chain-coord-devnet-configmap.template.yaml", &namespace, - Some(vec![("Devnet.toml", &config.devnet_config)]), + Some(vec![("Devnet.toml", &devnet_config)]), ) .await?; From 9dd2936287f12767048b4f4c08885aee67dc693f Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:11:10 -0400 Subject: [PATCH 14/60] remove mounts from kind.yaml --- templates/initial-config/kind.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/templates/initial-config/kind.yaml b/templates/initial-config/kind.yaml index cdad75c..fb1c46c 100644 --- a/templates/initial-config/kind.yaml +++ b/templates/initial-config/kind.yaml @@ -2,9 +2,6 @@ apiVersion: kind.x-k8s.io/v1alpha4 kind: Cluster nodes: - role: control-plane - extraMounts: - - hostPath: /Users/micaiahreid/work/stx-px - containerPath: /foo2 extraPortMappings: - containerPort: 30000 hostPort: 18444 @@ -23,8 +20,4 @@ nodes: - containerPort: 30008 hostPort: 5432 - containerPort: 30009 - hostPort: 3700 -- role: worker - extraMounts: - - hostPath: /Users/micaiahreid/work/stx-px - containerPath: /foo2 \ No newline at end of file + hostPort: 3700 \ No newline at end of file From fbe70bf8d063bb14ad27264bd65e4e7bfb2357e0 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:12:18 -0400 Subject: [PATCH 15/60] helpful scripts --- scripts/get-logs.sh | 5 +++++ scripts/kind-redeploy.sh | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100755 scripts/get-logs.sh create mode 100755 scripts/kind-redeploy.sh diff --git a/scripts/get-logs.sh b/scripts/get-logs.sh new file mode 100755 index 0000000..1008040 --- /dev/null +++ b/scripts/get-logs.sh @@ -0,0 +1,5 @@ +kubectl logs stacks-node --namespace $1 > ./logs/stacks-node.txt & \ +kubectl logs stacks-api --namespace $1 -c stacks-api-container > ./logs/stacks-api.txt & \ +kubectl logs stacks-api --namespace $1 -c stacks-api-postgres > ./logs/stacks-api-postgres.txt & \ +kubectl logs bitcoind-chain-coordinator --namespace $1 -c bitcoind-container > ./logs/bitcoin-node.txt & \ +kubectl logs bitcoind-chain-coordinator --namespace $1 -c chain-coordinator-container > ./logs/chain-coordinator.txt \ No newline at end of file diff --git a/scripts/kind-redeploy.sh b/scripts/kind-redeploy.sh new file mode 100755 index 0000000..b369d4c --- /dev/null +++ b/scripts/kind-redeploy.sh @@ -0,0 +1,6 @@ +kind create cluster --config=./templates/initial-config/kind.yaml && \ +docker pull hirosystems/stacks-blockchain-api:latest --platform=linux/amd64 && \ +kind load docker-image hirosystems/stacks-blockchain-api && \ +kind load docker-image stacks-network && \ +kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ +kubectl apply -f ../templates/initial-config/storage-class.yaml \ No newline at end of file From 7e3d621ac567f6bc44ee7f72a69d935ba2423b4c Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:12:26 -0400 Subject: [PATCH 16/60] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0ed7d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/logs \ No newline at end of file From 3e4e78bcafa534f8440e3a7dbd2e6b0b7ae3a0d6 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:46:32 -0400 Subject: [PATCH 17/60] rename file --- scripts/{kind-redeploy.sh => kind-deploy.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{kind-redeploy.sh => kind-deploy.sh} (100%) diff --git a/scripts/kind-redeploy.sh b/scripts/kind-deploy.sh similarity index 100% rename from scripts/kind-redeploy.sh rename to scripts/kind-deploy.sh From 472a28e62d08f1a6c7b93436cd767b66980fa294 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:46:40 -0400 Subject: [PATCH 18/60] provide example json --- examples/new-network.example.json | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/new-network.example.json diff --git a/examples/new-network.example.json b/examples/new-network.example.json new file mode 100644 index 0000000..f42c98a --- /dev/null +++ b/examples/new-network.example.json @@ -0,0 +1,43 @@ +{ + "namespace": "test-namespace", + "label": "test-label", + "network_id": 1, + "stacks_node_wait_time_for_microblocks": 50, + "stacks_node_first_attempt_time_ms": 500, + "stacks_node_subsequent_attempt_time_ms": 1000, + "bitcoin_node_username": "test-username", + "bitcoin_node_password": "test-password", + "miner_mnemonic": "", + "miner_derivation_path": "", + "miner_coinbase_recipient": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", + "faucet_mnemonic": "", + "faucet_derivation_path": "", + "bitcoin_controller_block_time": 0, + "bitcoin_controller_automining_disabled": true, + "disable_bitcoin_explorer": true, + "disable_stacks_explorer": true, + "disable_stacks_api": false, + "epoch_2_0": 100, + "epoch_2_05": 102, + "epoch_2_1": 106, + "epoch_2_2": 120, + "pox_2_activation": 112, + "pox_2_unlock_height": 112, + "accounts": [ + ["ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", 100000000000000], + ["STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", 100000000000000], + ["ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5", 100000000000000], + ["ST39PM053V14N2GN61QPX52Q44954B6739M7R1KZM", 100000000000000] + ], + "project_manifest": "[project]\nname = 'px'\ndescription = ''\nauthors = []\ntelemetry = true\ncache_dir = './.cache'\nrequirements = []\n[contracts.px]\npath = 'contracts/px.clar'\nclarity_version = 2\nepoch = 2.1\n[repl.analysis]\npasses = ['check_checker']\n\n[repl.analysis.check_checker]\nstrict = true\ntrusted_sender = false\ntrusted_caller = false\ncallee_filter = false\n", + "devnet_config": "[network]\nname = 'devnet'\n\n[accounts.deployer]\nmnemonic = 'twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw'\nbalance = 100_000_000_000_000\n[accounts.wallet_1]\nmnemonic = 'sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild'\nbalance = 100_000_000_000_000\n# secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801\n# stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5\n# btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC\n[devnet]\nminer_mnemonic = 'sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild'\n", + "deployment_plan": "---\nid: 0\nname: Devnet deployment\nnetwork: devnet\nstacks-node: 'http://localhost:20443'\nbitcoin-node: 'http://px-devnet:px-devnet@localhost:18443'\nplan:\n batches:\n - id: 0\n transactions:\n - contract-publish:\n contract-name: px\n expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM\n cost: 18060\n path: contracts/px.clar\n anchor-block-only: true\n clarity-version: 2\n epoch: '2.1'", + "contracts": [ + [ + "px.clar", + "\n;; title: px\n;; version:\n;; summary:\n;; description: Allows users to pay to update data in a matrix. \n;; Each matrix value must be a hexadecimal value from 0x000000 to 0xffffff, representing a color to be displayed on a grid in a web page. \n;; Each matrix key corresponds to the location of the grid, which is 100x100 cells.\n\n;; traits\n;;\n\n;; token definitions\n;; \n\n;; constants\n;;\n(define-constant MAX_LOC u100)\n(define-constant MAX_VAL 0xffffff)\n(define-constant MIN_VAL 0x000000)\n(define-constant ALL_LOCS (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20 u21 u22 u23 u24 u25 u26 u27 u28 u29 u30 u31 u32 u33 u34 u35 u36 u37 u38 u39 u40 u41 u42 u43 u44 u45 u46 u47 u48 u49 u50 u51 u52 u53 u54 u55 u56 u57 u58 u59 u60 u61 u62 u63 u64 u65 u66 u67 u68 u69 u70 u71 u72 u73 u74 u75 u76 u77 u78 u79 u80 u81 u82 u83 u84 u85 u86 u87 u88 u89 u90 u91 u92 u93 u94 u95 u96 u97 u98 u99))\n;; data vars\n;;\n\n;; data maps\n;;\n(define-map pixels uint (buff 3))\n\n;; public functions\n;;\n(define-public (set-value-at (loc uint) (value (buff 3))) \n (begin \n (if (>= loc MAX_LOC)\n (err \"Location out of bounds.\")\n (if (> value MAX_VAL)\n (err \"Value must be less than 0xffffff.\")\n (if (< value MIN_VAL)\n (err \"Value must be greater than 0x000000.\")\n (ok (map-set pixels loc value))\n )\n )\n )\n )\n)\n;; read only functions\n;;\n\n(define-read-only (get-value-at (loc uint))\n (if (>= loc MAX_LOC)\n (err \"Out of bounds.\")\n (ok (default-to 0xffffff (map-get? pixels loc)))\n )\n)\n\n(define-read-only (get-all) \n (map get-value-at ALL_LOCS)\n)\n\n(define-read-only (genesis-time (height uint))\n (get-block-info? time height)\n)\n;; private functions\n;;\n" + ] + ], + "stacks_miner_secret_key_hex": "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801", + "miner_stx_address": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5" +} From c363f9d9a388ff5391ed037de00c3201f8cc43be Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:53:33 -0400 Subject: [PATCH 19/60] initial pass at readme --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd6a932 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Stacks Devnet API +Spins up a server that provides an API to deploy, delete, control, and make requests to Stacks Devnets running on Kubernetes. + +## Installation +Running this tool requires having Kubernetes installed. Some [kind](https://kind.sigs.k8s.io/) configuration scripts are included in this repo. Using kind is not a requirement for using this tool, but you will need to use some tool for running local Kubernetes clusters. This is the recommended set of installation instructions for using this tool. +1. [Install Kubernetes.](https://kubernetes.io/releases/download/) +2. [Install `kubectl`.](https://kubernetes.io/releases/download/#kubectl) +3. Install kind. +``` +brew install kind +``` +4. Create kind cluster +``` +./scripts/kind-deploy.sh +``` + +You should be good to go! + +## Usage +Run +``` +cargo run +``` + +to start the server. Currently, the server is hosted on `localhost:8477` and exposes two routes: + - `POST localhost:8477/api/v1/networks` - Creates a new devnet with configuration provided in request body. See [this example](./examples/new-network.example.json) object for the required parameters. + - `DELETE localhost:8477/api/v1/network?network={namespace}` - Deletes all k8s assets deployed under the given namespace. + +### Notes +This project is still very eary in development and the code is fragile and will change a lot. Some known issues: + - if a k8s deployment fails, the app crashes. K8s deployments fail for a lot of reasons, so you'll need to restart the service a lot. + - the project relies on a docker image called `stacks-network`, which is not yet deployed to docker hub. This is in progress. \ No newline at end of file From c408d9ff40a1f4129e79c7ec8b06b390d7a281bb Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 15:59:57 -0400 Subject: [PATCH 20/60] add some metatdata to cargo.toml --- Cargo.lock | 34 +++++++++++++++++----------------- Cargo.toml | 8 +++++++- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c598e81..55b121b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -551,23 +551,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "k8s-experimentation" -version = "0.1.0" -dependencies = [ - "futures", - "k8s-openapi", - "kube", - "serde", - "serde_json", - "serde_qs", - "serde_yaml", - "tiny_http", - "tokio", - "toml", - "url", -] - [[package]] name = "k8s-openapi" version = "0.18.0" @@ -1116,6 +1099,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "stacks-devnet-api" +version = "0.1.0" +dependencies = [ + "futures", + "k8s-openapi", + "kube", + "serde", + "serde_json", + "serde_qs", + "serde_yaml", + "tiny_http", + "tokio", + "toml", + "url", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index fe4c18d..ffdf0ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,13 @@ [package] -name = "k8s-experimentation" +name = "stacks-devnet-api" version = "0.1.0" edition = "2021" +authors = ["Micaiah Reid "] +description = "The Stacks Devnet API runs a server that can be used to deploy, delete, manage, and make requests to Stacks Devnets run on Kubernetes." +readme = "README.md" +exclude = ["examples/**", "scripts/**"] +homepage = "https://github.com/hirosystems/stacks-devnet-api" +repository = "https://github.com/hirosystems/stacks-devnet-api" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 104eb8d2e68137b05e8a3734ef0aa9bfdea3816a Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Tue, 30 May 2023 16:01:05 -0400 Subject: [PATCH 21/60] add license --- LICENSE | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index f288702..9cecc1d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -645,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. From 5bc68fb0bc97783b000c97e0cfda3fd95e83f749 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 31 May 2023 10:18:07 -0400 Subject: [PATCH 22/60] update readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd6a932..80378f1 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ Spins up a server that provides an API to deploy, delete, control, and make requ Running this tool requires having Kubernetes installed. Some [kind](https://kind.sigs.k8s.io/) configuration scripts are included in this repo. Using kind is not a requirement for using this tool, but you will need to use some tool for running local Kubernetes clusters. This is the recommended set of installation instructions for using this tool. 1. [Install Kubernetes.](https://kubernetes.io/releases/download/) 2. [Install `kubectl`.](https://kubernetes.io/releases/download/#kubectl) -3. Install kind. +3. [Install Docker Desktop.](https://docs.docker.com/desktop/install/mac-install/) +4. Install kind. ``` brew install kind ``` -4. Create kind cluster +5. With Docker Desktop running, create kind cluster ``` ./scripts/kind-deploy.sh ``` From 3c28e1415e7d734de96cf81991dcceabe8295232 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 31 May 2023 13:32:13 -0400 Subject: [PATCH 23/60] add todo --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c48f4eb..a5aa7cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ async fn main() -> Result<(), Box> { } _ => request.respond(Response::empty(404))?, }, - //let not_implemented = Response::new( StatusCode::from(501), request.headers(), "not implemented", None, None ); + // TODO: respond with unimplemented _ => request.respond(Response::empty(501))?, } } From ffb4410a1ad8ecc6634320f7f547e8d72d0b3656 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 31 May 2023 13:32:25 -0400 Subject: [PATCH 24/60] rename package import --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index a5aa7cc..c325437 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use k8s_experimentation::{delete_devnet, deploy_devnet, StacksDevnetConfig}; +use stacks_devnet_api::{delete_devnet, deploy_devnet, StacksDevnetConfig}; use serde::Deserialize; use tiny_http::{Method, Response, Server}; use url::Url; From e715b4654f1b14798e79c280728b2682f818341e Mon Sep 17 00:00:00 2001 From: Micaiah Reid Date: Thu, 1 Jun 2023 14:35:26 -0400 Subject: [PATCH 25/60] Apply suggestions from code review Co-authored-by: Charlie <2747302+CharlieC3@users.noreply.github.com> --- scripts/kind-deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/kind-deploy.sh b/scripts/kind-deploy.sh index b369d4c..66b05ee 100755 --- a/scripts/kind-deploy.sh +++ b/scripts/kind-deploy.sh @@ -3,4 +3,4 @@ docker pull hirosystems/stacks-blockchain-api:latest --platform=linux/amd64 && \ kind load docker-image hirosystems/stacks-blockchain-api && \ kind load docker-image stacks-network && \ kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ -kubectl apply -f ../templates/initial-config/storage-class.yaml \ No newline at end of file +kubectl apply -f ./templates/initial-config/storage-class.yaml \ No newline at end of file From 409a6d6032f983b46c67070bc207c3f616ca9882 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 1 Jun 2023 16:05:40 -0400 Subject: [PATCH 26/60] add module to parse template files --- src/template_parser.rs | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/template_parser.rs diff --git a/src/template_parser.rs b/src/template_parser.rs new file mode 100644 index 0000000..dbf56ab --- /dev/null +++ b/src/template_parser.rs @@ -0,0 +1,65 @@ +pub enum Template { + BitcoindChainCoordinatorPod, + BitcoindChainCoordinatorService, + BitcoindConfigmap, + ChainCoordinatorDeploymentPlanConfigmap, + ChainCoordinatorDevnetConfigmap, + ChainCoordinatorNamespaceConfigmap, + ChainCoordinatorProjectDirConfigmap, + ChainCoordinatorProjectManifestConfigmap, + Namespace, + StacksApiConfigmap, + StacksApiPod, + StacksApiPostgresConfigmap, + StacksApiPvc, + StacksApiService, + StacksNodeConfigmap, + StacksNodePod, + StacksNodeService, +} + +pub fn get_yaml_from_filename(template_filename: Template) -> &'static str { + match template_filename { + Template::BitcoindChainCoordinatorPod => { + include_str!("../templates/bitcoind-chain-coordinator-pod.template.yaml") + } + Template::BitcoindChainCoordinatorService => { + include_str!("../templates/bitcoind-chain-coordinator-service.template.yaml") + } + Template::BitcoindConfigmap => { + include_str!("../templates/bitcoind-configmap.template.yaml") + } + Template::ChainCoordinatorDeploymentPlanConfigmap => { + include_str!("../templates/chain-coord-deployment-plan-configmap.template.yaml") + } + Template::ChainCoordinatorDevnetConfigmap => { + include_str!("../templates/chain-coord-devnet-configmap.template.yaml") + } + Template::ChainCoordinatorNamespaceConfigmap => { + include_str!("../templates/chain-coord-namespace-configmap.template.yaml") + } + Template::ChainCoordinatorProjectDirConfigmap => { + include_str!("../templates/chain-coord-project-dir-configmap.template.yaml") + } + Template::ChainCoordinatorProjectManifestConfigmap => { + include_str!("../templates/chain-coord-project-manifest-configmap.template.yaml") + } + Template::Namespace => include_str!("../templates/namespace.template.yaml"), + Template::StacksApiConfigmap => { + include_str!("../templates/stacks-api-configmap.template.yaml") + } + Template::StacksApiPod => include_str!("../templates/stacks-api-pod.template.yaml"), + Template::StacksApiPostgresConfigmap => { + include_str!("../templates/stacks-api-postgres-configmap.template.yaml") + } + Template::StacksApiPvc => include_str!("../templates/stacks-api-pvc.template.yaml"), + Template::StacksApiService => include_str!("../templates/stacks-api-service.template.yaml"), + Template::StacksNodeConfigmap => { + include_str!("../templates/stacks-node-configmap.template.yaml") + } + Template::StacksNodePod => include_str!("../templates/stacks-node-pod.template.yaml"), + Template::StacksNodeService => { + include_str!("../templates/stacks-node-service.template.yaml") + } + } +} From 74f1004446dd55d329e6ded022c69babf07a1a59 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 1 Jun 2023 16:05:47 -0400 Subject: [PATCH 27/60] use new template file parser --- src/lib.rs | 68 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e4a87ef..19c28e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use k8s_openapi::{ NamespaceResourceScope, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, fs, time::Duration}; +use std::{collections::BTreeMap, time::Duration}; use kube::{ api::{Api, DeleteParams, PostParams, ResourceExt}, @@ -11,6 +11,9 @@ use kube::{ }; use std::thread::sleep; +mod template_parser; +use template_parser::{get_yaml_from_filename, Template}; + #[derive(Serialize, Deserialize, Debug)] pub struct StacksDevnetConfig { namespace: String, @@ -98,8 +101,8 @@ async fn deploy_namespace(namespace_str: &str) -> Result<(), Box = kube::Api::all(client); - let file = fs::File::open("./templates/namespace.template.yaml")?; - let mut namespace: Namespace = serde_yaml::from_reader(file)?; + let template_str = get_yaml_from_filename(Template::Namespace); + let mut namespace: Namespace = serde_yaml::from_str(template_str)?; namespace.metadata.name = Some(namespace_str.to_owned()); namespace.metadata.labels = Some(BTreeMap::from([("name".into(), namespace_str.to_owned())])); @@ -112,12 +115,12 @@ async fn deploy_namespace(namespace_str: &str) -> Result<(), Box Result<(), Box> { +async fn deploy_pod(template: Template, namespace: &str) -> Result<(), Box> { let client = Client::try_default().await?; let pods_api: Api = Api::namespaced(client, &namespace); - let file = fs::File::open(format!("./templates/{}", template))?; - let mut pod: Pod = serde_yaml::from_reader(file)?; + let template_str = get_yaml_from_filename(template); + let mut pod: Pod = serde_yaml::from_str(template_str)?; pod.metadata.namespace = Some(namespace.to_owned()); let pp = PostParams::default(); @@ -127,12 +130,15 @@ async fn deploy_pod(template: &str, namespace: &str) -> Result<(), Box Result<(), Box> { +async fn deploy_service( + template: Template, + namespace: &str, +) -> Result<(), Box> { let client = Client::try_default().await?; let service_api: Api = Api::namespaced(client, &namespace); - let file = fs::File::open(format!("./templates/{}", template))?; - let mut service: Service = serde_yaml::from_reader(file)?; + let template_str = get_yaml_from_filename(template); + let mut service: Service = serde_yaml::from_str(template_str)?; service.metadata.namespace = Some(namespace.to_owned()); let pp = PostParams::default(); @@ -143,15 +149,15 @@ async fn deploy_service(template: &str, namespace: &str) -> Result<(), Box>, ) -> Result<(), Box> { let client = Client::try_default().await?; let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - let file = fs::File::open(format!("./templates/{}", template))?; - let mut configmap: ConfigMap = serde_yaml::from_reader(file)?; + let template_str = get_yaml_from_filename(template); + let mut configmap: ConfigMap = serde_yaml::from_str(template_str)?; configmap.metadata.namespace = Some(namespace.to_owned()); if let Some(configmap_data) = configmap_data { @@ -210,21 +216,21 @@ async fn deploy_bitcoin_node_pod( ); deploy_configmap( - "bitcoind-configmap.template.yaml", + Template::BitcoindConfigmap, &namespace, Some(vec![("bitcoin.conf", &bitcoind_conf)]), ) .await?; deploy_configmap( - "chain-coord-namespace-configmap.template.yaml", + Template::ChainCoordinatorNamespaceConfigmap, &namespace, Some(vec![("NAMESPACE", &namespace)]), ) .await?; deploy_configmap( - "chain-coord-project-manifest-configmap.template.yaml", + Template::ChainCoordinatorProjectManifestConfigmap, &namespace, Some(vec![("Clarinet.toml", &config.project_manifest)]), ) @@ -242,14 +248,14 @@ async fn deploy_bitcoin_node_pod( )); println!("{}", devnet_config); deploy_configmap( - "chain-coord-devnet-configmap.template.yaml", + Template::ChainCoordinatorDevnetConfigmap, &namespace, Some(vec![("Devnet.toml", &devnet_config)]), ) .await?; deploy_configmap( - "chain-coord-deployment-plan-configmap.template.yaml", + Template::ChainCoordinatorDeploymentPlanConfigmap, &namespace, Some(vec![("default.devnet-plan.yaml", &config.deployment_plan)]), ) @@ -260,19 +266,15 @@ async fn deploy_bitcoin_node_pod( contracts.push((contract_name, contract_source)); } deploy_configmap( - "chain-coord-project-dir-configmap.template.yaml", + Template::ChainCoordinatorProjectDirConfigmap, &namespace, Some(contracts), ) .await?; - deploy_pod("bitcoind-chain-coordinator-pod.template.yaml", &namespace).await?; + deploy_pod(Template::BitcoindChainCoordinatorPod, &namespace).await?; - deploy_service( - "bitcoind-chain-coordinator-service.template.yaml", - namespace, - ) - .await?; + deploy_service(Template::BitcoindChainCoordinatorService, namespace).await?; Ok(()) } @@ -423,15 +425,15 @@ async fn deploy_stacks_node_pod( }; deploy_configmap( - "stacks-node-configmap.template.yaml", + Template::StacksNodeConfigmap, &namespace, Some(vec![("Stacks.toml", &stacks_conf)]), ) .await?; - deploy_pod("stacks-node-pod.template.yaml", &namespace).await?; + deploy_pod(Template::StacksNodePod, &namespace).await?; - deploy_service("stacks-node-service.template.yaml", namespace).await?; + deploy_service(Template::StacksNodeService, namespace).await?; Ok(()) } @@ -443,7 +445,7 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box Result<(), Box Result<(), Box = Api::namespaced(client, &namespace); - let file = fs::File::open("./templates/stacks-api-pvc.template.yaml")?; - let mut pvc: PersistentVolumeClaim = serde_yaml::from_reader(file)?; + let template_str = get_yaml_from_filename(Template::StacksApiPvc); + let mut pvc: PersistentVolumeClaim = serde_yaml::from_str(template_str)?; pvc.metadata.namespace = Some(namespace.to_owned()); let pp = PostParams::default(); @@ -493,9 +495,9 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box Date: Thu, 1 Jun 2023 16:06:01 -0400 Subject: [PATCH 28/60] set context on automated kubectl command --- scripts/kind-deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/kind-deploy.sh b/scripts/kind-deploy.sh index 66b05ee..4990770 100755 --- a/scripts/kind-deploy.sh +++ b/scripts/kind-deploy.sh @@ -2,5 +2,5 @@ kind create cluster --config=./templates/initial-config/kind.yaml && \ docker pull hirosystems/stacks-blockchain-api:latest --platform=linux/amd64 && \ kind load docker-image hirosystems/stacks-blockchain-api && \ kind load docker-image stacks-network && \ -kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ +kubectl --context kind-kind apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ kubectl apply -f ./templates/initial-config/storage-class.yaml \ No newline at end of file From d27fd5c80a3fcdcc3483492dfe0d9572bf22ceab Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 1 Jun 2023 16:12:57 -0400 Subject: [PATCH 29/60] Switch to ApacheV2 license --- LICENSE | 875 +++++++++++++------------------------------------------- 1 file changed, 201 insertions(+), 674 deletions(-) diff --git a/LICENSE b/LICENSE index 9cecc1d..e4153ce 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,201 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Hiro Systems PBC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file From c619bd1267a58c98c0380401c6775f54e36a92ee Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 1 Jun 2023 09:54:46 -0400 Subject: [PATCH 30/60] convert consts to i32 --- src/lib.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 19c28e9..c2c6072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,11 +54,11 @@ pub struct StacksDevnetConfig { const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; -const BITCOIND_P2P_PORT: &str = "18444"; -const BITCOIND_RPC_PORT: &str = "18443"; -const STACKS_NODE_P2P_PORT: &str = "20444"; -const STACKS_NODE_RPC_PORT: &str = "20443"; -const CHAIN_COORDINATOR_INGESTION_PORT: &str = "20445"; +const BITCOIND_P2P_PORT: i32 = 18444; +const BITCOIND_RPC_PORT: i32 = 18443; +const STACKS_NODE_P2P_PORT: i32 = 20444; +const STACKS_NODE_RPC_PORT: i32 = 20443; +const CHAIN_COORDINATOR_INGESTION_PORT: i32 = 20445; pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box> { let namespace = &config.namespace; @@ -210,9 +210,9 @@ async fn deploy_bitcoin_node_pod( "#, config.bitcoin_node_username, config.bitcoin_node_password, - bitcoind_p2p_port, - bitcoind_rpc_port, - bitcoind_rpc_port + BITCOIND_P2P_PORT, + BITCOIND_RPC_PORT, + BITCOIND_RPC_PORT ); deploy_configmap( @@ -316,8 +316,8 @@ async fn deploy_stacks_node_pod( block_reward_recipient = "{}" # microblock_attempt_time_ms = 15000 "#, - rpc_port, - p2p_port, + STACKS_NODE_RPC_PORT, + STACKS_NODE_P2P_PORT, config.stacks_miner_secret_key_hex, config.stacks_miner_secret_key_hex, config.stacks_node_wait_time_for_microblocks, @@ -454,10 +454,11 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box Date: Thu, 1 Jun 2023 09:55:21 -0400 Subject: [PATCH 31/60] add error handling for resource creation/deletion --- src/lib.rs | 232 ++++++++++++++++++++++++++++++++-------------------- src/main.rs | 17 +++- 2 files changed, 156 insertions(+), 93 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c2c6072..a77f149 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,11 @@ pub struct StacksDevnetConfig { miner_stx_address: String, } +pub struct DevNetError { + pub message: String, + pub code: u16, +} + const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; @@ -60,7 +65,7 @@ const STACKS_NODE_P2P_PORT: i32 = 20444; const STACKS_NODE_RPC_PORT: i32 = 20443; const CHAIN_COORDINATOR_INGESTION_PORT: i32 = 20445; -pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), Box> { +pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), DevNetError> { let namespace = &config.namespace; deploy_namespace(&namespace).await?; @@ -97,67 +102,105 @@ pub async fn delete_devnet(namespace: &str) -> Result<(), Box Result<(), Box> { - let client = Client::try_default().await?; - let namespace_api: Api = kube::Api::all(client); - - let template_str = get_yaml_from_filename(Template::Namespace); - let mut namespace: Namespace = serde_yaml::from_str(template_str)?; +async fn deploy_namespace(namespace_str: &str) -> Result<(), DevNetError> { + let mut namespace: Namespace = get_resource_from_file(Template::Namespace)?; namespace.metadata.name = Some(namespace_str.to_owned()); namespace.metadata.labels = Some(BTreeMap::from([("name".into(), namespace_str.to_owned())])); - let post_params = PostParams::default(); - let created_namespace = namespace_api.create(&post_params, &namespace).await?; - let name = created_namespace.name_any(); - assert_eq!(namespace.name_any(), name); - println!("Created {}", name); - Ok(()) -} + let client = Client::try_default().await.map_err(|e| DevNetError { + message: format!("unable to get kube client: {}", e.to_string()), + code: 500, + })?; + let namespace_api: Api = kube::Api::all(client); -async fn deploy_pod(template: Template, namespace: &str) -> Result<(), Box> { - let client = Client::try_default().await?; - let pods_api: Api = Api::namespaced(client, &namespace); + let pp = PostParams::default(); + match namespace_api.create(&pp, &namespace).await { + Ok(namespace) => { + let name = namespace.name_any(); + println!("created namespace {}", name); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to create namespace: {}", api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to create namespace: {}", e.to_string()), + code: 500, + }), + } +} +fn get_resource_from_file(template: Template) -> Result +where + K: DeserializeOwned, +{ let template_str = get_yaml_from_filename(template); - let mut pod: Pod = serde_yaml::from_str(template_str)?; - pod.metadata.namespace = Some(namespace.to_owned()); - let pp = PostParams::default(); - let response = pods_api.create(&pp, &pod).await?; - let name = response.name_any(); - println!("created pod {}", name); - Ok(()) + let resource: K = serde_yaml::from_str(template_str).map_err(|e| DevNetError { + message: format!("unable to parse template file: {}", e.to_string()), + code: 500, + })?; + Ok(resource) } -async fn deploy_service( - template: Template, +async fn deploy_resource>( namespace: &str, -) -> Result<(), Box> { - let client = Client::try_default().await?; - let service_api: Api = Api::namespaced(client, &namespace); - - let template_str = get_yaml_from_filename(template); - let mut service: Service = serde_yaml::from_str(template_str)?; - service.metadata.namespace = Some(namespace.to_owned()); + resource: K, + resource_name: &str, +) -> Result<(), DevNetError> +where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + K: Serialize, +{ + let client = Client::try_default().await.map_err(|e| DevNetError { + message: format!("unable to get kube client: {}", e.to_string()), + code: 500, + })?; + let resource_api: Api = Api::namespaced(client, &namespace); let pp = PostParams::default(); - let response = service_api.create(&pp, &service).await?; - let name = response.name_any(); - println!("created service {}", name); - Ok(()) + match resource_api.create(&pp, &resource).await { + Ok(resource) => { + let name = resource.name_any(); + println!("created {} {}", resource_name, name); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to create {}: {}", resource_name, api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to create {}: {}", resource_name, e.to_string()), + code: 500, + }), + } +} + +async fn deploy_pod(template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut pod: Pod = get_resource_from_file(template)?; + + pod.metadata.namespace = Some(namespace.to_owned()); + deploy_resource(namespace, pod, "pod").await +} + +async fn deploy_service(template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut service: Service = get_resource_from_file(template)?; + + service.metadata.namespace = Some(namespace.to_owned()); + deploy_resource(namespace, service, "service").await } async fn deploy_configmap( template: Template, namespace: &str, configmap_data: Option>, -) -> Result<(), Box> { - let client = Client::try_default().await?; - let config_map_api: Api = kube::Api::::namespaced(client, &namespace); - - let template_str = get_yaml_from_filename(template); - let mut configmap: ConfigMap = serde_yaml::from_str(template_str)?; +) -> Result<(), DevNetError> { + let mut configmap: ConfigMap = get_resource_from_file(template)?; configmap.metadata.namespace = Some(namespace.to_owned()); if let Some(configmap_data) = configmap_data { @@ -168,20 +211,18 @@ async fn deploy_configmap( configmap.data = Some(map); } - let post_params = PostParams::default(); - let created_config = config_map_api.create(&post_params, &configmap).await?; - let name = created_config.name_any(); - assert_eq!(configmap.name_any(), name); - println!("Created {}", name); - Ok(()) + deploy_resource(namespace, configmap, "configmap").await } -async fn deploy_bitcoin_node_pod( - config: &StacksDevnetConfig, -) -> Result<(), Box> { - let bitcoind_p2p_port = BITCOIND_P2P_PORT.parse::()?; - let bitcoind_rpc_port = BITCOIND_RPC_PORT.parse::()?; +async fn deploy_pvc(template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut pvc: PersistentVolumeClaim = get_resource_from_file(template)?; + pvc.metadata.namespace = Some(namespace.to_owned()); + + deploy_resource(namespace, pvc, "pvc").await +} + +async fn deploy_bitcoin_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNetError> { let namespace = &config.namespace; let bitcoind_conf = format!( @@ -246,7 +287,7 @@ async fn deploy_bitcoin_node_pod( "\nbitcoin_node_password = \"{}\"", &config.bitcoin_node_password )); - println!("{}", devnet_config); + deploy_configmap( Template::ChainCoordinatorDevnetConfigmap, &namespace, @@ -279,11 +320,7 @@ async fn deploy_bitcoin_node_pod( Ok(()) } -async fn deploy_stacks_node_pod( - config: &StacksDevnetConfig, -) -> Result<(), Box> { - let p2p_port = STACKS_NODE_P2P_PORT.parse::()?; - let rpc_port = STACKS_NODE_RPC_PORT.parse::()?; +async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNetError> { let namespace = &config.namespace; let stacks_conf = { @@ -438,7 +475,7 @@ async fn deploy_stacks_node_pod( Ok(()) } -async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box> { +async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), DevNetError> { // configmap env vars for pg conatainer let stacks_api_pg_env = Vec::from([ ("POSTGRES_PASSWORD", "postgres"), @@ -481,20 +518,7 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box = Api::namespaced(client, &namespace); - - let template_str = get_yaml_from_filename(Template::StacksApiPvc); - let mut pvc: PersistentVolumeClaim = serde_yaml::from_str(template_str)?; - pvc.metadata.namespace = Some(namespace.to_owned()); - - let pp = PostParams::default(); - let response = pvc_api.create(&pp, &pvc).await?; - let name = response.name_any(); - println!("created pod {}", name); - } + deploy_pvc(Template::StacksApiPvc, &namespace).await?; deploy_pod(Template::StacksApiPod, &namespace).await?; @@ -506,31 +530,61 @@ async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), Box>( namespace: &str, resource_name: &str, -) -> Result<(), Box> +) -> Result<(), DevNetError> where ::DynamicType: Default, K: Clone, K: DeserializeOwned, K: std::fmt::Debug, { - let client = Client::try_default().await?; + let client = Client::try_default().await.map_err(|e| DevNetError { + message: format!("unable to get kube client: {}", e.to_string()), + code: 500, + })?; let api: Api = Api::namespaced(client, &namespace); let dp = DeleteParams::default(); - api.delete(resource_name, &dp).await?.map_left(|del| { - assert_eq!(del.name_any(), resource_name); - println!("Deleting resource started: {:?}", del); - }); - Ok(()) + match api.delete(resource_name, &dp).await { + Ok(resource) => { + resource.map_left(|del| { + assert_eq!(del.name_any(), resource_name); + println!("Deleting {resource_name} started"); + }); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to delete {}: {}", resource_name, api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to delete {}: {}", resource_name, e.to_string()), + code: 500, + }), + } } -async fn delete_namespace(namespace_str: &str) -> Result<(), Box> { - let client = Client::try_default().await?; +async fn delete_namespace(namespace_str: &str) -> Result<(), DevNetError> { + let client = Client::try_default().await.map_err(|e| DevNetError { + message: format!("unable to get kube client: {}", e.to_string()), + code: 500, + })?; let api: Api = kube::Api::all(client); let dp = DeleteParams::default(); - api.delete(namespace_str, &dp).await?.map_left(|del| { - assert_eq!(del.name_any(), namespace_str); - println!("Deleting resource started: {:?}", del); - }); - Ok(()) + match api.delete(namespace_str, &dp).await { + Ok(namespace) => { + namespace.map_left(|del| { + assert_eq!(del.name_any(), namespace_str); + println!("Deleting namespace started"); + }); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to delete namespace: {}", api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to delete namespace: {}", e.to_string()), + code: 500, + }), + } } diff --git a/src/main.rs b/src/main.rs index c325437..a6b888c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::str::FromStr; -use stacks_devnet_api::{delete_devnet, deploy_devnet, StacksDevnetConfig}; use serde::Deserialize; -use tiny_http::{Method, Response, Server}; +use stacks_devnet_api::{delete_devnet, deploy_devnet, StacksDevnetConfig}; +use tiny_http::{Method, Response, Server, StatusCode}; use url::Url; #[derive(Deserialize, Debug)] @@ -36,8 +36,17 @@ async fn main() -> Result<(), Box> { let mut content = String::new(); request.as_reader().read_to_string(&mut content).unwrap(); let config: StacksDevnetConfig = serde_json::from_str(&content)?; - deploy_devnet(config).await?; - request.respond(Response::empty(200))? + match deploy_devnet(config).await { + Ok(_) => request.respond(Response::empty(200))?, + Err(e) => { + let status_code = match e.code { + _ => StatusCode::from(e.code), + }; + // todo: there's got to be a better way to make a response + let response = Response::from_string(e.message); + request.respond(Response::with_status_code(response, status_code))? + } + } } _ => request.respond(Response::empty(404))?, }, From 3866a64ecbf354e6b9704de6f6f12d142c4b832e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 2 Jun 2023 08:51:17 -0400 Subject: [PATCH 32/60] add context to kubectl commands --- scripts/get-logs.sh | 10 +++++----- scripts/kind-deploy.sh | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/get-logs.sh b/scripts/get-logs.sh index 1008040..3f88f54 100755 --- a/scripts/get-logs.sh +++ b/scripts/get-logs.sh @@ -1,5 +1,5 @@ -kubectl logs stacks-node --namespace $1 > ./logs/stacks-node.txt & \ -kubectl logs stacks-api --namespace $1 -c stacks-api-container > ./logs/stacks-api.txt & \ -kubectl logs stacks-api --namespace $1 -c stacks-api-postgres > ./logs/stacks-api-postgres.txt & \ -kubectl logs bitcoind-chain-coordinator --namespace $1 -c bitcoind-container > ./logs/bitcoin-node.txt & \ -kubectl logs bitcoind-chain-coordinator --namespace $1 -c chain-coordinator-container > ./logs/chain-coordinator.txt \ No newline at end of file +kubectl --context kind-kind logs stacks-node --namespace $1 > ./logs/stacks-node.txt & \ +kubectl --context kind-kind logs stacks-api --namespace $1 -c stacks-api-container > ./logs/stacks-api.txt & \ +kubectl --context kind-kind logs stacks-api --namespace $1 -c stacks-api-postgres > ./logs/stacks-api-postgres.txt & \ +kubectl --context kind-kind logs bitcoind-chain-coordinator --namespace $1 -c bitcoind-container > ./logs/bitcoin-node.txt & \ +kubectl --context kind-kind logs bitcoind-chain-coordinator --namespace $1 -c chain-coordinator-container > ./logs/chain-coordinator.txt \ No newline at end of file diff --git a/scripts/kind-deploy.sh b/scripts/kind-deploy.sh index 4990770..ae7dab4 100755 --- a/scripts/kind-deploy.sh +++ b/scripts/kind-deploy.sh @@ -3,4 +3,4 @@ docker pull hirosystems/stacks-blockchain-api:latest --platform=linux/amd64 && \ kind load docker-image hirosystems/stacks-blockchain-api && \ kind load docker-image stacks-network && \ kubectl --context kind-kind apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ -kubectl apply -f ./templates/initial-config/storage-class.yaml \ No newline at end of file +kubectl --context kind-kind apply -f ./templates/initial-config/storage-class.yaml \ No newline at end of file From 982175e149185e46e044d9738d1e137bcf03bc89 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Fri, 2 Jun 2023 16:51:59 -0400 Subject: [PATCH 33/60] set up proxy server --- Cargo.lock | 21 ++++++ Cargo.toml | 1 + src/main.rs | 214 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 183 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55b121b..5d5855c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,6 +352,25 @@ dependencies = [ "wasi", ] +[[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -417,6 +436,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1104,6 +1124,7 @@ name = "stacks-devnet-api" version = "0.1.0" dependencies = [ "futures", + "hyper", "k8s-openapi", "kube", "serde", diff --git a/Cargo.toml b/Cargo.toml index ffdf0ff..62b5eac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ toml = "0.7.3" tiny_http = "0.12.0" url = "2.3.1" serde_qs = "0.12.0" +hyper = { version = "0.14", features = ["full"] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a6b888c..c8615e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,71 +1,179 @@ -use std::str::FromStr; - +use hyper::server::conn::AddrStream; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Client, Method, Request, Response, Server, StatusCode, Uri}; use serde::Deserialize; use stacks_devnet_api::{delete_devnet, deploy_devnet, StacksDevnetConfig}; -use tiny_http::{Method, Response, Server, StatusCode}; -use url::Url; +use std::net::IpAddr; +use std::str::FromStr; +use std::{convert::Infallible, net::SocketAddr}; #[derive(Deserialize, Debug)] -struct DeleteRequest { +struct DevnetRequestQueryPararms { network: String, } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() { const HOST: &str = "127.0.0.1"; const PORT: &str = "8477"; let endpoint: String = HOST.to_owned() + ":" + PORT; + let addr: SocketAddr = endpoint.parse().expect("Could not parse ip:port."); - let server = Server::http(&endpoint).unwrap(); - loop { - // blocks until the next request is received - let mut request = match server.recv() { - Ok(rq) => rq, - Err(e) => { - println!("error: {}", e); - break; - } - }; + let make_svc = make_service_fn(|conn: &AddrStream| { + let remote_addr = conn.remote_addr().ip(); + async move { Ok::<_, Infallible>(service_fn(move |req| handle(remote_addr, req))) } + }); - let url = request.url(); - let full_url = format!("http://{}{}", &endpoint, url); - let url = Url::from_str(&full_url)?; - match request.method() { - Method::Post => match url.path() { - "/api/v1/networks" => { - let mut content = String::new(); - request.as_reader().read_to_string(&mut content).unwrap(); - let config: StacksDevnetConfig = serde_json::from_str(&content)?; - match deploy_devnet(config).await { - Ok(_) => request.respond(Response::empty(200))?, - Err(e) => { - let status_code = match e.code { - _ => StatusCode::from(e.code), - }; - // todo: there's got to be a better way to make a response - let response = Response::from_string(e.message); - request.respond(Response::with_status_code(response, status_code))? - } - } + let server = Server::bind(&addr).serve(make_svc); + + println!("Running server on {:?}", addr); + + if let Err(e) = server.await { + eprintln!("server error: {}", e); + } +} + +async fn proxy( + mut request: Request, + proxy_data: ProxyData, +) -> Result, Infallible> { + let uri = request.uri(); + + if let Some(query) = uri.query() { + let params: DevnetRequestQueryPararms = serde_qs::from_str(query).unwrap(); + let _network = ¶ms.network; + let forward_url = format!("http://127.0.0.1:{}", proxy_data.destination_service); // format!("http://{}.{}.svc.cluster.local", proxy_data.destination_service, network); + + *request.uri_mut() = { + let forward_uri = format!("{}{}?{}", forward_url, proxy_data.path_to_forward, query); + Uri::from_str(forward_uri.as_str()) + } + .unwrap(); + let client = Client::new(); + + match client.request(request).await { + Ok(response) => Ok(response), + Err(_error) => Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()), + } + } else { + Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty()) + .unwrap()) + } +} + +struct ProxyData { + path_to_forward: String, + destination_service: String, +} +fn get_proxy_data(path: &str) -> Option { + const BITCOIN_NODE_PATH: &str = "/api/v1/network/bitcoin-node"; + const BITCOIN_NODE_SERVICE: &str = "18443"; + const STACKS_NODE_PATH: &str = "/api/v1/network/stacks-node"; + const STACKS_NODE_SERVICE: &str = "20443"; + const STACKS_API_PATH: &str = "/api/v1/network/stacks-api"; + const STACKS_API_SERVICE: &str = "3999"; + + if path.starts_with(BITCOIN_NODE_PATH) { + return Some(ProxyData { + path_to_forward: path.replace(BITCOIN_NODE_PATH, ""), + destination_service: BITCOIN_NODE_SERVICE.into(), + }); + } else if path.starts_with(STACKS_NODE_PATH) { + return Some(ProxyData { + path_to_forward: path.replace(STACKS_NODE_PATH, ""), + destination_service: STACKS_NODE_SERVICE.into(), + }); + } else if path.starts_with(STACKS_API_PATH) { + return Some(ProxyData { + path_to_forward: path.replace(STACKS_API_PATH, ""), + destination_service: STACKS_API_SERVICE.into(), + }); + } + None +} + +async fn handle(_client_ip: IpAddr, request: Request) -> Result, Infallible> { + let uri = request.uri(); + let path = uri.path(); + + match request.method() { + &Method::POST => { + if path == "/api/v1/networks" { + let body = hyper::body::to_bytes(request.into_body()).await; + if body.is_err() { + return Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()); } - _ => request.respond(Response::empty(404))?, - }, - Method::Delete => match url.path() { - "/api/v1/network" => { - if let Some(query) = url.query() { - let delete_request: DeleteRequest = serde_qs::from_str(query)?; - delete_devnet(&delete_request.network).await?; - request.respond(Response::empty(200))? - } else { - request.respond(Response::empty(400))?; - } + let body = body.unwrap(); + let config: StacksDevnetConfig = serde_json::from_slice(&body).unwrap(); + match deploy_devnet(config).await { + Ok(_) => Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()), + Err(e) => Ok(Response::builder() + .status(StatusCode::from_u16(e.code).unwrap()) + .body(Body::try_from(e.message).unwrap()) + .unwrap()), } - _ => request.respond(Response::empty(404))?, - }, - // TODO: respond with unimplemented - _ => request.respond(Response::empty(501))?, + } else { + let proxy_data = get_proxy_data(path); + match proxy_data { + Some(proxy_data) => proxy(request, proxy_data).await, + None => Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap()), + } + } + } + &Method::GET => { + let proxy_data = get_proxy_data(path); + match proxy_data { + Some(proxy_data) => proxy(request, proxy_data).await, + None => Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap()), + } } + &Method::DELETE => match uri.path() { + "/api/v1/network" => { + if let Some(query) = uri.query() { + let delete_request: DevnetRequestQueryPararms = + serde_qs::from_str(query).unwrap(); + match delete_devnet(&delete_request.network).await { + Ok(_) => Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()), + Err(_e) => Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()), + } + } else { + Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::empty()) + .unwrap()) + } + } + _ => Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap()), + }, + _ => Ok(Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::empty()) + .unwrap()), } - - Ok(()) } From 4dfd1fc3255fee815ba855f3aba904b1568856da Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 5 Jun 2023 10:51:05 -0400 Subject: [PATCH 34/60] bind server to 0.0.0.0 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c8615e8..fcc8ac7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ struct DevnetRequestQueryPararms { #[tokio::main] async fn main() { - const HOST: &str = "127.0.0.1"; + const HOST: &str = "0.0.0.0"; const PORT: &str = "8477"; let endpoint: String = HOST.to_owned() + ":" + PORT; let addr: SocketAddr = endpoint.parse().expect("Could not parse ip:port."); From 06d003824a7deed665149d071ca1218222934550 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:34:31 -0400 Subject: [PATCH 35/60] create k8s manager struct --- src/lib.rs | 857 +++++++++++++++++++++++++++++------------------------ 1 file changed, 471 insertions(+), 386 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a77f149..a159e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ +use hyper::{body::Bytes, Body, Request, Response}; use k8s_openapi::{ api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, NamespaceResourceScope, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{collections::BTreeMap, time::Duration}; +use tower::BoxError; use kube::{ api::{Api, DeleteParams, PostParams, ResourceExt}, @@ -14,6 +16,14 @@ use std::thread::sleep; mod template_parser; use template_parser::{get_yaml_from_filename, Template}; +const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; +const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; + +const BITCOIND_P2P_PORT: i32 = 18444; +const BITCOIND_RPC_PORT: i32 = 18443; +const STACKS_NODE_P2P_PORT: i32 = 20444; +const STACKS_NODE_RPC_PORT: i32 = 20443; +const CHAIN_COORDINATOR_INGESTION_PORT: i32 = 20445; #[derive(Serialize, Deserialize, Debug)] pub struct StacksDevnetConfig { namespace: String, @@ -56,177 +66,241 @@ pub struct DevNetError { pub code: u16, } -const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; -const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; +#[derive(Clone)] +pub struct StacksDevnetApiK8sManager { + client: Client, +} -const BITCOIND_P2P_PORT: i32 = 18444; -const BITCOIND_RPC_PORT: i32 = 18443; -const STACKS_NODE_P2P_PORT: i32 = 20444; -const STACKS_NODE_RPC_PORT: i32 = 20443; -const CHAIN_COORDINATOR_INGESTION_PORT: i32 = 20445; +impl StacksDevnetApiK8sManager { + pub async fn default() -> StacksDevnetApiK8sManager { + let client = Client::try_default() + .await + .expect("could not create kube client"); + StacksDevnetApiK8sManager { client } + } -pub async fn deploy_devnet(config: StacksDevnetConfig) -> Result<(), DevNetError> { - let namespace = &config.namespace; + pub async fn new(service: S, default_namespace: T) -> StacksDevnetApiK8sManager + where + S: tower::Service, Response = Response> + Send + 'static, + S::Future: Send + 'static, + S::Error: Into, + B: http_body::Body + Send + 'static, + B::Error: Into, + T: Into, + { + let client = Client::new(service, default_namespace); + StacksDevnetApiK8sManager { client } + } - deploy_namespace(&namespace).await?; - deploy_bitcoin_node_pod(&config).await?; + pub async fn deploy_devnet(&self, config: StacksDevnetConfig) -> Result<(), DevNetError> { + let namespace = &config.namespace; + + let namespace_exists = &self.check_namespace_exists(&namespace).await?; + if !namespace_exists { + if cfg!(debug_assertions) { + self.deploy_namespace(&namespace).await?; + } else { + return Err(DevNetError { + message: "Cannot create devnet before namespace exists.".into(), + code: 400, + }); + } + } + self.deploy_bitcoin_node_pod(&config).await?; - sleep(Duration::from_secs(5)); + sleep(Duration::from_secs(5)); - deploy_stacks_node_pod(&config).await?; + self.deploy_stacks_node_pod(&config).await?; - if !config.disable_stacks_api { - deploy_stacks_api_pod(&namespace).await?; + if !config.disable_stacks_api { + self.deploy_stacks_api_pod(&namespace).await?; + } + Ok(()) } - Ok(()) -} - -pub async fn delete_devnet(namespace: &str) -> Result<(), Box> { - let _ = delete_namespace(namespace).await; - let _ = delete_resource::(namespace, "bitcoind-chain-coordinator").await; - let _ = delete_resource::(namespace, "stacks-node").await; - let _ = delete_resource::(namespace, "stacks-api").await; - let _ = delete_resource::(namespace, "bitcoind-conf").await; - let _ = delete_resource::(namespace, "stacks-node-conf").await; - let _ = delete_resource::(namespace, "stacks-api-conf").await; - let _ = delete_resource::(namespace, "stacks-api-postgres-conf").await; - let _ = delete_resource::(namespace, "deployment-plan-conf").await; - let _ = delete_resource::(namespace, "devnet-conf").await; - let _ = delete_resource::(namespace, "project-dir-conf").await; - let _ = delete_resource::(namespace, "namespace-conf").await; - let _ = delete_resource::(namespace, "project-manifest-conf").await; - let _ = delete_resource::(namespace, "bitcoind-chain-coordinator-service").await; - let _ = delete_resource::(namespace, "stacks-node-service").await; - let _ = delete_resource::(namespace, "stacks-api-service").await; - let _ = delete_resource::(namespace, "stacks-api-pvc").await; - Ok(()) -} - -async fn deploy_namespace(namespace_str: &str) -> Result<(), DevNetError> { - let mut namespace: Namespace = get_resource_from_file(Template::Namespace)?; - namespace.metadata.name = Some(namespace_str.to_owned()); - namespace.metadata.labels = Some(BTreeMap::from([("name".into(), namespace_str.to_owned())])); - - let client = Client::try_default().await.map_err(|e| DevNetError { - message: format!("unable to get kube client: {}", e.to_string()), - code: 500, - })?; - let namespace_api: Api = kube::Api::all(client); - - let pp = PostParams::default(); - match namespace_api.create(&pp, &namespace).await { - Ok(namespace) => { - let name = namespace.name_any(); - println!("created namespace {}", name); - Ok(()) + pub async fn delete_devnet(&self, namespace: &str) -> Result<(), Box> { + if cfg!(debug_assertions) { + let _ = self.delete_namespace(namespace).await; } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to create namespace: {}", api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to create namespace: {}", e.to_string()), - code: 500, - }), + let _ = self + .delete_resource::(namespace, "bitcoind-chain-coordinator") + .await; + let _ = self.delete_resource::(namespace, "stacks-node").await; + let _ = self.delete_resource::(namespace, "stacks-api").await; + let _ = self + .delete_resource::(namespace, "bitcoind-conf") + .await; + let _ = self + .delete_resource::(namespace, "stacks-node-conf") + .await; + let _ = self + .delete_resource::(namespace, "stacks-api-conf") + .await; + let _ = self + .delete_resource::(namespace, "stacks-api-postgres-conf") + .await; + let _ = self + .delete_resource::(namespace, "deployment-plan-conf") + .await; + let _ = self + .delete_resource::(namespace, "devnet-conf") + .await; + let _ = self + .delete_resource::(namespace, "project-dir-conf") + .await; + let _ = self + .delete_resource::(namespace, "namespace-conf") + .await; + let _ = self + .delete_resource::(namespace, "project-manifest-conf") + .await; + let _ = self + .delete_resource::(namespace, "bitcoind-chain-coordinator-service") + .await; + let _ = self + .delete_resource::(namespace, "stacks-node-service") + .await; + let _ = self + .delete_resource::(namespace, "stacks-api-service") + .await; + let _ = self + .delete_resource::(namespace, "stacks-api-pvc") + .await; + Ok(()) } -} -fn get_resource_from_file(template: Template) -> Result -where - K: DeserializeOwned, -{ - let template_str = get_yaml_from_filename(template); + pub async fn check_namespace_exists(&self, namespace_str: &str) -> Result { + let namespace_api: Api = kube::Api::all(self.client.to_owned()); + match namespace_api.get(namespace_str).await { + Ok(_) => Ok(true), + Err(kube::Error::Api(api_error)) => { + if api_error.code == 404 { + Ok(false) + } else { + Err(DevNetError { + message: format!("unable to get namespace: {}", api_error.message), + code: api_error.code, + }) + } + } + Err(e) => Err(DevNetError { + message: format!("unable to get namespace: {}", e.to_string()), + code: 500, + }), + } + } - let resource: K = serde_yaml::from_str(template_str).map_err(|e| DevNetError { - message: format!("unable to parse template file: {}", e.to_string()), - code: 500, - })?; - Ok(resource) -} + async fn deploy_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { + let mut namespace: Namespace = get_resource_from_file(Template::Namespace)?; + + namespace.metadata.name = Some(namespace_str.to_owned()); + namespace.metadata.labels = + Some(BTreeMap::from([("name".into(), namespace_str.to_owned())])); + + let namespace_api: Api = kube::Api::all(self.client.to_owned()); + + let pp = PostParams::default(); + match namespace_api.create(&pp, &namespace).await { + Ok(namespace) => { + let name = namespace.name_any(); + println!("created namespace {}", name); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to create namespace: {}", api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to create namespace: {}", e.to_string()), + code: 500, + }), + } + } -async fn deploy_resource>( - namespace: &str, - resource: K, - resource_name: &str, -) -> Result<(), DevNetError> -where - ::DynamicType: Default, - K: Clone, - K: DeserializeOwned, - K: std::fmt::Debug, - K: Serialize, -{ - let client = Client::try_default().await.map_err(|e| DevNetError { - message: format!("unable to get kube client: {}", e.to_string()), - code: 500, - })?; - let resource_api: Api = Api::namespaced(client, &namespace); - - let pp = PostParams::default(); - match resource_api.create(&pp, &resource).await { - Ok(resource) => { - let name = resource.name_any(); - println!("created {} {}", resource_name, name); - Ok(()) + async fn deploy_resource>( + &self, + namespace: &str, + resource: K, + resource_name: &str, + ) -> Result<(), DevNetError> + where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + K: Serialize, + { + let resource_api: Api = Api::namespaced(self.client.to_owned(), &namespace); + let pp = PostParams::default(); + + match resource_api.create(&pp, &resource).await { + Ok(resource) => { + let name = resource.name_any(); + println!("created {} {}", resource_name, name); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to create {}: {}", resource_name, api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to create {}: {}", resource_name, e.to_string()), + code: 500, + }), } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to create {}: {}", resource_name, api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to create {}: {}", resource_name, e.to_string()), - code: 500, - }), } -} -async fn deploy_pod(template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pod: Pod = get_resource_from_file(template)?; + async fn deploy_pod(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut pod: Pod = get_resource_from_file(template)?; - pod.metadata.namespace = Some(namespace.to_owned()); - deploy_resource(namespace, pod, "pod").await -} + pod.metadata.namespace = Some(namespace.to_owned()); + self.deploy_resource(namespace, pod, "pod").await + } -async fn deploy_service(template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut service: Service = get_resource_from_file(template)?; + async fn deploy_service(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut service: Service = get_resource_from_file(template)?; - service.metadata.namespace = Some(namespace.to_owned()); - deploy_resource(namespace, service, "service").await -} + service.metadata.namespace = Some(namespace.to_owned()); + self.deploy_resource(namespace, service, "service").await + } -async fn deploy_configmap( - template: Template, - namespace: &str, - configmap_data: Option>, -) -> Result<(), DevNetError> { - let mut configmap: ConfigMap = get_resource_from_file(template)?; - - configmap.metadata.namespace = Some(namespace.to_owned()); - if let Some(configmap_data) = configmap_data { - let mut map = BTreeMap::new(); - for (key, value) in configmap_data { - map.insert(key.into(), value.into()); + async fn deploy_configmap( + &self, + template: Template, + namespace: &str, + configmap_data: Option>, + ) -> Result<(), DevNetError> { + let mut configmap: ConfigMap = get_resource_from_file(template)?; + + configmap.metadata.namespace = Some(namespace.to_owned()); + if let Some(configmap_data) = configmap_data { + let mut map = BTreeMap::new(); + for (key, value) in configmap_data { + map.insert(key.into(), value.into()); + } + configmap.data = Some(map); } - configmap.data = Some(map); - } - deploy_resource(namespace, configmap, "configmap").await -} + self.deploy_resource(namespace, configmap, "configmap") + .await + } -async fn deploy_pvc(template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pvc: PersistentVolumeClaim = get_resource_from_file(template)?; + async fn deploy_pvc(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { + let mut pvc: PersistentVolumeClaim = get_resource_from_file(template)?; - pvc.metadata.namespace = Some(namespace.to_owned()); + pvc.metadata.namespace = Some(namespace.to_owned()); - deploy_resource(namespace, pvc, "pvc").await -} + self.deploy_resource(namespace, pvc, "pvc").await + } -async fn deploy_bitcoin_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNetError> { - let namespace = &config.namespace; + async fn deploy_bitcoin_node_pod( + &self, + config: &StacksDevnetConfig, + ) -> Result<(), DevNetError> { + let namespace = &config.namespace; - let bitcoind_conf = format!( - r#" + let bitcoind_conf = format!( + r#" server=1 regtest=1 rpcallowip=0.0.0.0/0 @@ -249,83 +323,85 @@ async fn deploy_bitcoin_node_pod(config: &StacksDevnetConfig) -> Result<(), DevN rpcbind=0.0.0.0:{} rpcport={} "#, - config.bitcoin_node_username, - config.bitcoin_node_password, - BITCOIND_P2P_PORT, - BITCOIND_RPC_PORT, - BITCOIND_RPC_PORT - ); - - deploy_configmap( - Template::BitcoindConfigmap, - &namespace, - Some(vec![("bitcoin.conf", &bitcoind_conf)]), - ) - .await?; - - deploy_configmap( - Template::ChainCoordinatorNamespaceConfigmap, - &namespace, - Some(vec![("NAMESPACE", &namespace)]), - ) - .await?; - - deploy_configmap( - Template::ChainCoordinatorProjectManifestConfigmap, - &namespace, - Some(vec![("Clarinet.toml", &config.project_manifest)]), - ) - .await?; - - let mut devnet_config = config.devnet_config.clone(); - //devnet_config.push_str("\n[devnet]"); - devnet_config.push_str(&format!( - "\nbitcoin_node_username = \"{}\"", - &config.bitcoin_node_username - )); - devnet_config.push_str(&format!( - "\nbitcoin_node_password = \"{}\"", - &config.bitcoin_node_password - )); - - deploy_configmap( - Template::ChainCoordinatorDevnetConfigmap, - &namespace, - Some(vec![("Devnet.toml", &devnet_config)]), - ) - .await?; - - deploy_configmap( - Template::ChainCoordinatorDeploymentPlanConfigmap, - &namespace, - Some(vec![("default.devnet-plan.yaml", &config.deployment_plan)]), - ) - .await?; - - let mut contracts: Vec<(&str, &str)> = vec![]; - for (contract_name, contract_source) in &config.contracts { - contracts.push((contract_name, contract_source)); - } - deploy_configmap( - Template::ChainCoordinatorProjectDirConfigmap, - &namespace, - Some(contracts), - ) - .await?; + config.bitcoin_node_username, + config.bitcoin_node_password, + BITCOIND_P2P_PORT, + BITCOIND_RPC_PORT, + BITCOIND_RPC_PORT + ); - deploy_pod(Template::BitcoindChainCoordinatorPod, &namespace).await?; + self.deploy_configmap( + Template::BitcoindConfigmap, + &namespace, + Some(vec![("bitcoin.conf", &bitcoind_conf)]), + ) + .await?; + + self.deploy_configmap( + Template::ChainCoordinatorNamespaceConfigmap, + &namespace, + Some(vec![("NAMESPACE", &namespace)]), + ) + .await?; + + self.deploy_configmap( + Template::ChainCoordinatorProjectManifestConfigmap, + &namespace, + Some(vec![("Clarinet.toml", &config.project_manifest)]), + ) + .await?; + + let mut devnet_config = config.devnet_config.clone(); + //devnet_config.push_str("\n[devnet]"); + devnet_config.push_str(&format!( + "\nbitcoin_node_username = \"{}\"", + &config.bitcoin_node_username + )); + devnet_config.push_str(&format!( + "\nbitcoin_node_password = \"{}\"", + &config.bitcoin_node_password + )); - deploy_service(Template::BitcoindChainCoordinatorService, namespace).await?; + self.deploy_configmap( + Template::ChainCoordinatorDevnetConfigmap, + &namespace, + Some(vec![("Devnet.toml", &devnet_config)]), + ) + .await?; + + self.deploy_configmap( + Template::ChainCoordinatorDeploymentPlanConfigmap, + &namespace, + Some(vec![("default.devnet-plan.yaml", &config.deployment_plan)]), + ) + .await?; + + let mut contracts: Vec<(&str, &str)> = vec![]; + for (contract_name, contract_source) in &config.contracts { + contracts.push((contract_name, contract_source)); + } + self.deploy_configmap( + Template::ChainCoordinatorProjectDirConfigmap, + &namespace, + Some(contracts), + ) + .await?; - Ok(()) -} + self.deploy_pod(Template::BitcoindChainCoordinatorPod, &namespace) + .await?; -async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNetError> { - let namespace = &config.namespace; + self.deploy_service(Template::BitcoindChainCoordinatorService, namespace) + .await?; - let stacks_conf = { - let mut stacks_conf = format!( - r#" + Ok(()) + } + + async fn deploy_stacks_node_pod(&self, config: &StacksDevnetConfig) -> Result<(), DevNetError> { + let namespace = &config.namespace; + + let stacks_conf = { + let mut stacks_conf = format!( + r#" [node] working_dir = "/devnet" rpc_bind = "0.0.0.0:{}" @@ -353,45 +429,45 @@ async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNe block_reward_recipient = "{}" # microblock_attempt_time_ms = 15000 "#, - STACKS_NODE_RPC_PORT, - STACKS_NODE_P2P_PORT, - config.stacks_miner_secret_key_hex, - config.stacks_miner_secret_key_hex, - config.stacks_node_wait_time_for_microblocks, - config.stacks_node_first_attempt_time_ms, - config.stacks_node_subsequent_attempt_time_ms, - config.miner_coinbase_recipient - ); - - for (address, balance) in config.accounts.iter() { - stacks_conf.push_str(&format!( - r#" + STACKS_NODE_RPC_PORT, + STACKS_NODE_P2P_PORT, + config.stacks_miner_secret_key_hex, + config.stacks_miner_secret_key_hex, + config.stacks_node_wait_time_for_microblocks, + config.stacks_node_first_attempt_time_ms, + config.stacks_node_subsequent_attempt_time_ms, + config.miner_coinbase_recipient + ); + + for (address, balance) in config.accounts.iter() { + stacks_conf.push_str(&format!( + r#" [[ustx_balance]] address = "{}" amount = {} "#, - address, balance - )); - } + address, balance + )); + } - let balance: u64 = 100_000_000_000_000; - stacks_conf.push_str(&format!( - r#" + let balance: u64 = 100_000_000_000_000; + stacks_conf.push_str(&format!( + r#" [[ustx_balance]] address = "{}" amount = {} "#, - config.miner_coinbase_recipient, balance - )); + config.miner_coinbase_recipient, balance + )); - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let bitcoind_chain_coordinator_host = format!( - "{}.{}", - &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host - ); + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let bitcoind_chain_coordinator_host = format!( + "{}.{}", + &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host + ); - stacks_conf.push_str(&format!( - r#" + stacks_conf.push_str(&format!( + r#" # Add orchestrator (docker-host) as an event observer [[events_observer]] endpoint = "{}:{}" @@ -399,23 +475,23 @@ async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNe include_data_events = true events_keys = ["*"] "#, - bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT - )); + bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT + )); - // stacks_conf.push_str(&format!( - // r#" - // # Add stacks-api as an event observer - // [[events_observer]] - // endpoint = "host.docker.internal:{}" - // retry_count = 255 - // include_data_events = false - // events_keys = ["*"] - // "#, - // 30007, - // )); - - stacks_conf.push_str(&format!( - r#" + // stacks_conf.push_str(&format!( + // r#" + // # Add stacks-api as an event observer + // [[events_observer]] + // endpoint = "host.docker.internal:{}" + // retry_count = 255 + // include_data_events = false + // events_keys = ["*"] + // "#, + // 30007, + // )); + + stacks_conf.push_str(&format!( + r#" [burnchain] chain = "bitcoin" mode = "krypton" @@ -429,15 +505,15 @@ async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNe rpc_port = {} peer_port = {} "#, - bitcoind_chain_coordinator_host, - config.bitcoin_node_username, - config.bitcoin_node_password, - CHAIN_COORDINATOR_INGESTION_PORT, - BITCOIND_P2P_PORT - )); + bitcoind_chain_coordinator_host, + config.bitcoin_node_username, + config.bitcoin_node_password, + CHAIN_COORDINATOR_INGESTION_PORT, + BITCOIND_P2P_PORT + )); - stacks_conf.push_str(&format!( - r#" + stacks_conf.push_str(&format!( + r#" pox_2_activation = {} [[burnchain.epochs]] @@ -456,135 +532,144 @@ async fn deploy_stacks_node_pod(config: &StacksDevnetConfig) -> Result<(), DevNe epoch_name = "2.1" start_height = {} "#, - config.pox_2_activation, config.epoch_2_0, config.epoch_2_05, config.epoch_2_1 - )); - stacks_conf - }; + config.pox_2_activation, config.epoch_2_0, config.epoch_2_05, config.epoch_2_1 + )); + stacks_conf + }; - deploy_configmap( - Template::StacksNodeConfigmap, - &namespace, - Some(vec![("Stacks.toml", &stacks_conf)]), - ) - .await?; + self.deploy_configmap( + Template::StacksNodeConfigmap, + &namespace, + Some(vec![("Stacks.toml", &stacks_conf)]), + ) + .await?; - deploy_pod(Template::StacksNodePod, &namespace).await?; + self.deploy_pod(Template::StacksNodePod, &namespace).await?; - deploy_service(Template::StacksNodeService, namespace).await?; + self.deploy_service(Template::StacksNodeService, namespace) + .await?; - Ok(()) -} + Ok(()) + } + + async fn deploy_stacks_api_pod(&self, namespace: &str) -> Result<(), DevNetError> { + // configmap env vars for pg conatainer + let stacks_api_pg_env = Vec::from([ + ("POSTGRES_PASSWORD", "postgres"), + ("POSTGRES_DB", "stacks_api"), + ]); + self.deploy_configmap( + Template::StacksApiPostgresConfigmap, + &namespace, + Some(stacks_api_pg_env), + ) + .await?; + + // configmap env vars for api conatainer + let namespaced_host = format!("{}.svc.cluster.local", &namespace); + let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); + let rpc_port = STACKS_NODE_RPC_PORT.to_string(); + let stacks_api_env = Vec::from([ + ("STACKS_CORE_RPC_HOST", &stacks_node_host[..]), + ("STACKS_BLOCKCHAIN_API_DB", "pg"), + ("STACKS_CORE_RPC_PORT", &rpc_port), + ("STACKS_BLOCKCHAIN_API_PORT", "3999"), + ("STACKS_BLOCKCHAIN_API_HOST", "0.0.0.0"), + ("STACKS_CORE_EVENT_PORT", "3700"), + ("STACKS_CORE_EVENT_HOST", "0.0.0.0"), + ("STACKS_API_ENABLE_FT_METADATA", "1"), + ("PG_HOST", "0.0.0.0"), + ("PG_PORT", "5432"), + ("PG_USER", "postgres"), + ("PG_PASSWORD", "postgres"), + ("PG_DATABASE", "stacks_api"), + ("STACKS_CHAIN_ID", "2147483648"), + ("V2_POX_MIN_AMOUNT_USTX", "90000000260"), + ("NODE_ENV", "production"), + ("STACKS_API_LOG_LEVEL", "debug"), + ]); + self.deploy_configmap( + Template::StacksApiConfigmap, + &namespace, + Some(stacks_api_env), + ) + .await?; + + self.deploy_pvc(Template::StacksApiPvc, &namespace).await?; + + self.deploy_pod(Template::StacksApiPod, &namespace).await?; + + self.deploy_service(Template::StacksApiService, &namespace) + .await?; + + Ok(()) + } -async fn deploy_stacks_api_pod(namespace: &str) -> Result<(), DevNetError> { - // configmap env vars for pg conatainer - let stacks_api_pg_env = Vec::from([ - ("POSTGRES_PASSWORD", "postgres"), - ("POSTGRES_DB", "stacks_api"), - ]); - deploy_configmap( - Template::StacksApiPostgresConfigmap, - &namespace, - Some(stacks_api_pg_env), - ) - .await?; - - // configmap env vars for api conatainer - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); - let rpc_port = STACKS_NODE_RPC_PORT.to_string(); - let stacks_api_env = Vec::from([ - ("STACKS_CORE_RPC_HOST", &stacks_node_host[..]), - ("STACKS_BLOCKCHAIN_API_DB", "pg"), - ("STACKS_CORE_RPC_PORT", &rpc_port), - ("STACKS_BLOCKCHAIN_API_PORT", "3999"), - ("STACKS_BLOCKCHAIN_API_HOST", "0.0.0.0"), - ("STACKS_CORE_EVENT_PORT", "3700"), - ("STACKS_CORE_EVENT_HOST", "0.0.0.0"), - ("STACKS_API_ENABLE_FT_METADATA", "1"), - ("PG_HOST", "0.0.0.0"), - ("PG_PORT", "5432"), - ("PG_USER", "postgres"), - ("PG_PASSWORD", "postgres"), - ("PG_DATABASE", "stacks_api"), - ("STACKS_CHAIN_ID", "2147483648"), - ("V2_POX_MIN_AMOUNT_USTX", "90000000260"), - ("NODE_ENV", "production"), - ("STACKS_API_LOG_LEVEL", "debug"), - ]); - deploy_configmap( - Template::StacksApiConfigmap, - &namespace, - Some(stacks_api_env), - ) - .await?; - - deploy_pvc(Template::StacksApiPvc, &namespace).await?; - - deploy_pod(Template::StacksApiPod, &namespace).await?; - - deploy_service(Template::StacksApiService, &namespace).await?; - - Ok(()) + async fn delete_resource>( + &self, + namespace: &str, + resource_name: &str, + ) -> Result<(), DevNetError> + where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + { + let api: Api = Api::namespaced(self.client.to_owned(), &namespace); + let dp = DeleteParams::default(); + match api.delete(resource_name, &dp).await { + Ok(resource) => { + resource.map_left(|del| { + assert_eq!(del.name_any(), resource_name); + println!("Deleting {resource_name} started"); + }); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to delete {}: {}", resource_name, api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to delete {}: {}", resource_name, e.to_string()), + code: 500, + }), + } + } + + async fn delete_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { + let api: Api = kube::Api::all(self.client.to_owned()); + + let dp = DeleteParams::default(); + match api.delete(namespace_str, &dp).await { + Ok(namespace) => { + namespace.map_left(|del| { + assert_eq!(del.name_any(), namespace_str); + println!("Deleting namespace started"); + }); + Ok(()) + } + Err(kube::Error::Api(api_error)) => Err(DevNetError { + message: format!("unable to delete namespace: {}", api_error.message), + code: api_error.code, + }), + Err(e) => Err(DevNetError { + message: format!("unable to delete namespace: {}", e.to_string()), + code: 500, + }), + } + } } -async fn delete_resource>( - namespace: &str, - resource_name: &str, -) -> Result<(), DevNetError> +fn get_resource_from_file(template: Template) -> Result where - ::DynamicType: Default, - K: Clone, K: DeserializeOwned, - K: std::fmt::Debug, { - let client = Client::try_default().await.map_err(|e| DevNetError { - message: format!("unable to get kube client: {}", e.to_string()), - code: 500, - })?; - let api: Api = Api::namespaced(client, &namespace); - let dp = DeleteParams::default(); - match api.delete(resource_name, &dp).await { - Ok(resource) => { - resource.map_left(|del| { - assert_eq!(del.name_any(), resource_name); - println!("Deleting {resource_name} started"); - }); - Ok(()) - } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to delete {}: {}", resource_name, api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to delete {}: {}", resource_name, e.to_string()), - code: 500, - }), - } -} + let template_str = get_yaml_from_filename(template); -async fn delete_namespace(namespace_str: &str) -> Result<(), DevNetError> { - let client = Client::try_default().await.map_err(|e| DevNetError { - message: format!("unable to get kube client: {}", e.to_string()), + let resource: K = serde_yaml::from_str(template_str).map_err(|e| DevNetError { + message: format!("unable to parse template file: {}", e.to_string()), code: 500, })?; - let api: Api = kube::Api::all(client); - - let dp = DeleteParams::default(); - match api.delete(namespace_str, &dp).await { - Ok(namespace) => { - namespace.map_left(|del| { - assert_eq!(del.name_any(), namespace_str); - println!("Deleting namespace started"); - }); - Ok(()) - } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to delete namespace: {}", api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to delete namespace: {}", e.to_string()), - code: 500, - }), - } + Ok(resource) } From 0e4823c5da1089186b9f55791f740dd80f6be5b0 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:35:14 -0400 Subject: [PATCH 36/60] add proxy routing to downstream pods --- src/main.rs | 262 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 189 insertions(+), 73 deletions(-) diff --git a/src/main.rs b/src/main.rs index fcc8ac7..3950844 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,27 @@ use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client, Method, Request, Response, Server, StatusCode, Uri}; -use serde::Deserialize; -use stacks_devnet_api::{delete_devnet, deploy_devnet, StacksDevnetConfig}; +use stacks_devnet_api::{StacksDevnetApiK8sManager, StacksDevnetConfig}; use std::net::IpAddr; use std::str::FromStr; use std::{convert::Infallible, net::SocketAddr}; -#[derive(Deserialize, Debug)] -struct DevnetRequestQueryPararms { - network: String, -} - #[tokio::main] async fn main() { const HOST: &str = "0.0.0.0"; const PORT: &str = "8477"; let endpoint: String = HOST.to_owned() + ":" + PORT; let addr: SocketAddr = endpoint.parse().expect("Could not parse ip:port."); + let k8s_manager = StacksDevnetApiK8sManager::default().await; let make_svc = make_service_fn(|conn: &AddrStream| { + let k8s_manager = k8s_manager.clone(); let remote_addr = conn.remote_addr().ip(); - async move { Ok::<_, Infallible>(service_fn(move |req| handle(remote_addr, req))) } + async move { + Ok::<_, Infallible>(service_fn(move |req| { + handle_request(remote_addr, req, k8s_manager.clone()) + })) + } }); let server = Server::bind(&addr).serve(make_svc); @@ -33,24 +33,34 @@ async fn main() { } } -async fn proxy( +fn mutate_request_for_proxy( mut request: Request, + network: &str, + path_to_forward: &str, proxy_data: ProxyData, -) -> Result, Infallible> { - let uri = request.uri(); +) -> Request { + let forward_url = format!( + "http://{}.{}.svc.cluster.local:{}", + proxy_data.destination_service, network, proxy_data.destination_port + ); - if let Some(query) = uri.query() { - let params: DevnetRequestQueryPararms = serde_qs::from_str(query).unwrap(); - let _network = ¶ms.network; - let forward_url = format!("http://127.0.0.1:{}", proxy_data.destination_service); // format!("http://{}.{}.svc.cluster.local", proxy_data.destination_service, network); + let query = match request.uri().query() { + Some(query) => format!("?{}", query), + None => String::new(), + }; *request.uri_mut() = { - let forward_uri = format!("{}{}?{}", forward_url, proxy_data.path_to_forward, query); + let forward_uri = format!("{}/{}{}", forward_url, path_to_forward, query); Uri::from_str(forward_uri.as_str()) } .unwrap(); + request +} + +async fn proxy(request: Request) -> Result, Infallible> { let client = Client::new(); + println!("forwarding request to {}", request.uri()); match client.request(request).await { Ok(response) => Ok(response), Err(_error) => Ok(Response::builder() @@ -58,62 +68,115 @@ async fn proxy( .body(Body::empty()) .unwrap()), } - } else { - Ok(Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::empty()) - .unwrap()) - } } struct ProxyData { - path_to_forward: String, destination_service: String, + destination_port: String, } -fn get_proxy_data(path: &str) -> Option { - const BITCOIN_NODE_PATH: &str = "/api/v1/network/bitcoin-node"; - const BITCOIN_NODE_SERVICE: &str = "18443"; - const STACKS_NODE_PATH: &str = "/api/v1/network/stacks-node"; - const STACKS_NODE_SERVICE: &str = "20443"; - const STACKS_API_PATH: &str = "/api/v1/network/stacks-api"; - const STACKS_API_SERVICE: &str = "3999"; - - if path.starts_with(BITCOIN_NODE_PATH) { - return Some(ProxyData { - path_to_forward: path.replace(BITCOIN_NODE_PATH, ""), +fn get_proxy_data(proxy_path: &str) -> Option { + const BITCOIN_NODE_PATH: &str = "bitcoin-node"; + const STACKS_NODE_PATH: &str = "stacks-node"; + const STACKS_API_PATH: &str = "stacks-api"; + const BITCOIN_NODE_SERVICE: &str = "bitcoind-chain-coordinator-service"; + const STACKS_NODE_SERVICE: &str = "stacks-node-service"; + const STACKS_API_SERVICE: &str = "stacks-api-service"; + const BITCOIN_NODE_PORT: &str = "18443"; + const STACKS_NODE_PORT: &str = "20443"; + const STACKS_API_PORT: &str = "3999"; + + match proxy_path { + BITCOIN_NODE_PATH => Some(ProxyData { destination_service: BITCOIN_NODE_SERVICE.into(), - }); - } else if path.starts_with(STACKS_NODE_PATH) { - return Some(ProxyData { - path_to_forward: path.replace(STACKS_NODE_PATH, ""), + destination_port: BITCOIN_NODE_PORT.into(), + }), + STACKS_NODE_PATH => Some(ProxyData { destination_service: STACKS_NODE_SERVICE.into(), - }); - } else if path.starts_with(STACKS_API_PATH) { - return Some(ProxyData { - path_to_forward: path.replace(STACKS_API_PATH, ""), + destination_port: STACKS_NODE_PORT.into(), + }), + STACKS_API_PATH => Some(ProxyData { destination_service: STACKS_API_SERVICE.into(), - }); + destination_port: STACKS_API_PORT.into(), + }), + _ => None, + } +} + +const API_PATH: &str = "/api/v1/"; +#[derive(Default, PartialEq, Debug)] +struct PathParts { + route: String, + network: Option, + subroute: Option, + remainder: Option, +} +fn get_standardized_path_parts(path: &str) -> PathParts { + let path = path.replace(API_PATH, ""); + let path = path.trim_matches('/'); + let parts: Vec<&str> = path.split("/").collect(); + + match parts.len() { + 0 => PathParts { + route: String::new(), + ..Default::default() + }, + 1 => PathParts { + route: parts[0].into(), + ..Default::default() + }, + 2 => PathParts { + route: parts[0].into(), + network: Some(parts[1].into()), + ..Default::default() + }, + 3 => PathParts { + route: parts[0].into(), + network: Some(parts[1].into()), + subroute: Some(parts[2].into()), + ..Default::default() + }, + _ => { + let remainder = parts[3..].join("/"); + PathParts { + route: parts[0].into(), + network: Some(parts[1].into()), + subroute: Some(parts[2].into()), + remainder: Some(remainder), + } + } } - None } -async fn handle(_client_ip: IpAddr, request: Request) -> Result, Infallible> { +async fn handle_request( + _client_ip: IpAddr, + request: Request, + k8s_manager: StacksDevnetApiK8sManager, +) -> Result, Infallible> { let uri = request.uri(); let path = uri.path(); + let method = request.method(); + println!("received request, method: {}. path: {}", method, path); - match request.method() { - &Method::POST => { if path == "/api/v1/networks" { + return match method { + &Method::POST => { let body = hyper::body::to_bytes(request.into_body()).await; if body.is_err() { return Ok(Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) + .body(Body::try_from("failed to parse request body").unwrap()) .unwrap()); } let body = body.unwrap(); - let config: StacksDevnetConfig = serde_json::from_slice(&body).unwrap(); - match deploy_devnet(config).await { + let config: Result = serde_json::from_slice(&body); + if config.is_err() { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::try_from("invalid configuration to create network").unwrap()) + .unwrap()); + } + let config = config.unwrap(); + match k8s_manager.deploy_devnet(config).await { Ok(_) => Ok(Response::builder() .status(StatusCode::OK) .body(Body::empty()) @@ -123,33 +186,52 @@ async fn handle(_client_ip: IpAddr, request: Request) -> Result proxy(request, proxy_data).await, - None => Ok(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::empty()) + } + _ => Ok(Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::try_from("network creation must be a POST request").unwrap()) .unwrap()), + }; + } else if path.starts_with(API_PATH) { + let path_parts = get_standardized_path_parts(uri.path()); + + if path_parts.route != "network" { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::try_from("invalid request path").unwrap()) + .unwrap()); } + // the api path must be followed by a network id + if path_parts.network.is_none() { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::try_from("no network id provided").unwrap()) + .unwrap()); } + let network = path_parts.network.unwrap(); + + // verify that we have a valid namespace and the network actually exists + let exists = match k8s_manager.check_namespace_exists(&network).await { + Ok(exists) => exists, + Err(e) => { + return Ok(Response::builder() + .status(StatusCode::from_u16(e.code).unwrap()) + .body(Body::try_from(e.message).unwrap()) + .unwrap()); } - &Method::GET => { - let proxy_data = get_proxy_data(path); - match proxy_data { - Some(proxy_data) => proxy(request, proxy_data).await, - None => Ok(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::empty()) - .unwrap()), + }; + if !exists { + return Ok(Response::builder() + .status(StatusCode::from_u16(404).unwrap()) + .body(Body::try_from("network does not exist").unwrap()) + .unwrap()); } - } - &Method::DELETE => match uri.path() { - "/api/v1/network" => { - if let Some(query) = uri.query() { - let delete_request: DevnetRequestQueryPararms = - serde_qs::from_str(query).unwrap(); - match delete_devnet(&delete_request.network).await { + + // the path only contained the network path and network id, + // so it must be a request to DELETE a network or GET network info + if path_parts.subroute.is_none() { + return match method { + &Method::DELETE => match k8s_manager.delete_devnet(&network).await { Ok(_) => Ok(Response::builder() .status(StatusCode::OK) .body(Body::empty()) @@ -158,13 +240,47 @@ async fn handle(_client_ip: IpAddr, request: Request) -> Result Ok(Response::builder() + .status(StatusCode::NOT_IMPLEMENTED) + .body(Body::empty()) + .unwrap()), + _ => Ok(Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::empty()) + .unwrap()), + }; } + let subroute = path_parts.subroute.unwrap(); + if subroute == "commands" { + return Ok(Response::builder() + .status(StatusCode::NOT_IMPLEMENTED) + .body(Body::empty()) + .unwrap()); } else { + let remaining_path = path_parts.remainder.unwrap_or(String::new()); + + let proxy_data = get_proxy_data(&subroute); + return match proxy_data { + Some(proxy_data) => { + let proxy_request = + mutate_request_for_proxy(request, &network, &remaining_path, proxy_data); + proxy(proxy_request).await + } + None => Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::try_from("invalid request path").unwrap()) + .unwrap()), + }; + } + } + Ok(Response::builder() .status(StatusCode::BAD_REQUEST) - .body(Body::empty()) + .body(Body::try_from("invalid request path").unwrap()) .unwrap()) } + } _ => Ok(Response::builder() .status(StatusCode::NOT_FOUND) From c60fa307ac21f6438e44583fb2950b07fb419791 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:35:28 -0400 Subject: [PATCH 37/60] add tests --- src/main.rs | 353 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 312 insertions(+), 41 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3950844..51b6728 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,25 +49,25 @@ fn mutate_request_for_proxy( None => String::new(), }; - *request.uri_mut() = { + *request.uri_mut() = { let forward_uri = format!("{}/{}{}", forward_url, path_to_forward, query); - Uri::from_str(forward_uri.as_str()) - } - .unwrap(); + Uri::from_str(forward_uri.as_str()) + } + .unwrap(); request } async fn proxy(request: Request) -> Result, Infallible> { - let client = Client::new(); + let client = Client::new(); println!("forwarding request to {}", request.uri()); - match client.request(request).await { - Ok(response) => Ok(response), - Err(_error) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), - } + match client.request(request).await { + Ok(response) => Ok(response), + Err(_error) => Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()), + } } struct ProxyData { @@ -157,7 +157,7 @@ async fn handle_request( let method = request.method(); println!("received request, method: {}. path: {}", method, path); - if path == "/api/v1/networks" { + if path == "/api/v1/networks" { return match method { &Method::POST => { let body = hyper::body::to_bytes(request.into_body()).await; @@ -190,7 +190,7 @@ async fn handle_request( _ => Ok(Response::builder() .status(StatusCode::METHOD_NOT_ALLOWED) .body(Body::try_from("network creation must be a POST request").unwrap()) - .unwrap()), + .unwrap()), }; } else if path.starts_with(API_PATH) { let path_parts = get_standardized_path_parts(uri.path()); @@ -200,14 +200,14 @@ async fn handle_request( .status(StatusCode::BAD_REQUEST) .body(Body::try_from("invalid request path").unwrap()) .unwrap()); - } + } // the api path must be followed by a network id if path_parts.network.is_none() { return Ok(Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::try_from("no network id provided").unwrap()) .unwrap()); - } + } let network = path_parts.network.unwrap(); // verify that we have a valid namespace and the network actually exists @@ -218,28 +218,28 @@ async fn handle_request( .status(StatusCode::from_u16(e.code).unwrap()) .body(Body::try_from(e.message).unwrap()) .unwrap()); - } + } }; if !exists { return Ok(Response::builder() .status(StatusCode::from_u16(404).unwrap()) .body(Body::try_from("network does not exist").unwrap()) .unwrap()); - } + } // the path only contained the network path and network id, // so it must be a request to DELETE a network or GET network info if path_parts.subroute.is_none() { return match method { &Method::DELETE => match k8s_manager.delete_devnet(&network).await { - Ok(_) => Ok(Response::builder() - .status(StatusCode::OK) - .body(Body::empty()) - .unwrap()), - Err(_e) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), + Ok(_) => Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()), + Err(_e) => Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()), }, &Method::GET => Ok(Response::builder() .status(StatusCode::NOT_IMPLEMENTED) @@ -250,14 +250,14 @@ async fn handle_request( .body(Body::empty()) .unwrap()), }; - } + } let subroute = path_parts.subroute.unwrap(); if subroute == "commands" { return Ok(Response::builder() .status(StatusCode::NOT_IMPLEMENTED) .body(Body::empty()) .unwrap()); - } else { + } else { let remaining_path = path_parts.remainder.unwrap_or(String::new()); let proxy_data = get_proxy_data(&subroute); @@ -275,21 +275,292 @@ async fn handle_request( } } - Ok(Response::builder() - .status(StatusCode::BAD_REQUEST) + Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) .body(Body::try_from("invalid request path").unwrap()) - .unwrap()) - } + .unwrap()) +} + +#[cfg(test)] +mod tests { + use super::*; + use hyper::body; + use k8s_openapi::api::core::v1::Namespace; + use tower_test::mock::{self, Handle}; + async fn mock_k8s_handler(handle: &mut Handle, Response>) { + let (request, send) = handle.next_request().await.expect("Service not called"); + + let (body, status) = match ( + request.method().as_str(), + request.uri().to_string().as_str(), + ) { + ("GET", "/api/v1/namespaces/test") => { + let pod: Namespace = serde_json::from_value(serde_json::json!({ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "test", + "labels": { + "name": "test" + } + }, + })) + .unwrap(); + (serde_json::to_vec(&pod).unwrap(), 200) } - _ => Ok(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::empty()) - .unwrap()), - }, - _ => Ok(Response::builder() - .status(StatusCode::METHOD_NOT_ALLOWED) - .body(Body::empty()) - .unwrap()), + ("GET", "/api/v1/namespaces/undeployed") => (vec![], 404), + _ => panic!("Unexpected API request {:?}", request), + }; + + send.send_response( + Response::builder() + .status(status) + .body(Body::from(body)) + .unwrap(), + ); + } + + #[tokio::test] + async fn it_responds_400_for_invalid_paths() { + let (mock_service, mut handle) = mock::pair::, Response>(); + let _spawned = tokio::spawn(async move { + mock_k8s_handler(&mut handle).await; + }); + + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); + let invalid_paths = vec![ + "/path", + "/api", + "/api/v1", + "/api/v1/network2", + "/api/v1/network/test/invalid_path", + ]; + for path in invalid_paths { + let request_builder = Request::builder().uri(path).method("GET"); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(client_ip, request, k8s_manager.clone()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(body_str, "invalid request path"); + } + } + + #[tokio::test] + async fn it_responds_404_undeployed_namespaces() { + let (mock_service, mut handle) = mock::pair::, Response>(); + let _spawned = tokio::spawn(async move { + mock_k8s_handler(&mut handle).await; + }); + + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); + let path = "/api/v1/network/undeployed"; + + let request_builder = Request::builder().uri(path).method("GET"); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(client_ip, request, k8s_manager.clone()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(body_str, "network does not exist"); + } + + #[tokio::test] + async fn it_responds_400_missing_network() { + let (mock_service, mut handle) = mock::pair::, Response>(); + let _spawned = tokio::spawn(async move { + mock_k8s_handler(&mut handle).await; + }); + + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); + let path = "/api/v1/network/"; + + let request_builder = Request::builder().uri(path).method("GET"); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(client_ip, request, k8s_manager.clone()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(body_str, "no network id provided"); + } + + #[tokio::test] + async fn network_creation_responds_405_for_non_post_requests() { + let (mock_service, mut handle) = mock::pair::, Response>(); + let _spawned = tokio::spawn(async move { + mock_k8s_handler(&mut handle).await; + }); + + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); + let path = "/api/v1/networks"; + + let methods = ["GET", "DELETE"]; + for method in methods { + let request_builder = Request::builder().uri(path).method(method); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(client_ip, request, k8s_manager.clone()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(body_str, "network creation must be a POST request"); + } + } + #[tokio::test] + async fn network_creation_responds_400_for_invalid_config_data() { + let (mock_service, mut handle) = mock::pair::, Response>(); + let _spawned = tokio::spawn(async move { + mock_k8s_handler(&mut handle).await; + }); + + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); + let path = "/api/v1/networks"; + + let request_builder = Request::builder().uri(path).method("POST"); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(client_ip, request, k8s_manager.clone()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(body_str, "invalid configuration to create network"); + } + + #[test] + fn request_paths_are_parsed_correctly() { + let path = "/api/v1/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::new(), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/some-subroute"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + subroute: Some(String::from("some-subroute")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/some-subroute/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + subroute: Some(String::from("some-subroute")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/some-subroute/the/remaining/path"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + subroute: Some(String::from("some-subroute")), + remainder: Some(String::from("the/remaining/path")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/some-subroute/the/remaining/path/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + subroute: Some(String::from("some-subroute")), + remainder: Some(String::from("the/remaining/path")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + + let path = "/api/v1/some-route/some-network/some-subroute/the//remaining//path/"; + let path_parts = get_standardized_path_parts(path); + let expected = PathParts { + route: String::from("some-route"), + network: Some(String::from("some-network")), + subroute: Some(String::from("some-subroute")), + remainder: Some(String::from("the//remaining//path")), + ..Default::default() + }; + assert_eq!(path_parts, expected); + } + + #[tokio::test] + async fn request_mutation_should_create_valid_proxy_destination() { + let path = "/api/v1/some-route/some-network/stacks-node/the//remaining///path"; + let path_parts = get_standardized_path_parts(path); + let network = path_parts.network.unwrap(); + let subroute = path_parts.subroute.unwrap(); + let remainder = path_parts.remainder.unwrap(); + println!("{}", &remainder); + let proxy_data = get_proxy_data(&subroute); + let request_builder = Request::builder().uri("/").method("POST"); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let request = mutate_request_for_proxy(request, &network, &remainder, proxy_data.unwrap()); + let actual_url = request.uri().to_string(); + let expected = format!( + "http://stacks-node-service.{}.svc.cluster.local:20443/{}", + network, &remainder + ); + println!("{expected}"); + assert_eq!(actual_url, expected); } } From 3c9d6d6d2c9bc73ff0567624212d58949068b0f3 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:35:36 -0400 Subject: [PATCH 38/60] deps updatee --- Cargo.lock | 142 +++++++++++++++++++++-------------------------------- Cargo.toml | 11 +++-- 2 files changed, 63 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d5855c..ef71431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,10 +24,26 @@ dependencies = [ ] [[package]] -name = "ascii" -version = "1.1.0" +name = "async-stream" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] [[package]] name = "async-trait" @@ -118,12 +134,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "chunked_transfer" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1052,26 +1062,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_qs" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" -dependencies = [ - "percent-encoding", - "serde", - "thiserror", -] - -[[package]] -name = "serde_spanned" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" -dependencies = [ - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.21" @@ -1124,17 +1114,16 @@ name = "stacks-devnet-api" version = "0.1.0" dependencies = [ "futures", + "http-body", "hyper", "k8s-openapi", "kube", "serde", "serde_json", - "serde_qs", "serde_yaml", - "tiny_http", "tokio", - "toml", - "url", + "tower", + "tower-test", ] [[package]] @@ -1188,18 +1177,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -1268,52 +1245,42 @@ dependencies = [ ] [[package]] -name = "tokio-util" -version = "0.7.7" +name = "tokio-stream" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ - "bytes", "futures-core", - "futures-sink", "pin-project-lite", - "slab", "tokio", - "tracing", ] [[package]] -name = "toml" -version = "0.7.3" +name = "tokio-test" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" -dependencies = [ - "serde", + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", ] [[package]] -name = "toml_edit" -version = "0.19.8" +name = "tokio-util" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", ] [[package]] @@ -1366,6 +1333,20 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tower-test" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4546773ffeab9e4ea02b8872faa49bb616a80a7da66afc2f32688943f97efa7" +dependencies = [ + "futures-util", + "pin-project", + "tokio", + "tokio-test", + "tower-layer", + "tower-service", +] + [[package]] name = "tracing" version = "0.1.37" @@ -1703,15 +1684,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "winnow" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" -dependencies = [ - "memchr", -] - [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 62b5eac..dc16a20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,9 @@ tokio = { version = "1.27.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.96" serde_yaml = "0.9.21" -toml = "0.7.3" -tiny_http = "0.12.0" -url = "2.3.1" -serde_qs = "0.12.0" -hyper = { version = "0.14", features = ["full"] } \ No newline at end of file +hyper = { version = "0.14", features = ["full"] } +tower = "0.4.13" +http-body = "0.4.5" + +[dev-dependencies] +tower-test = "0.4.0" From 1761fbd8c38f508a41c41bb9768d66aec7508143 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:36:32 -0400 Subject: [PATCH 39/60] remove NodePort type from services --- ...ind-chain-coordinator-service.template.yaml | 5 ----- templates/initial-config/kind.yaml | 18 +----------------- templates/stacks-api-service.template.yaml | 4 ---- templates/stacks-node-service.template.yaml | 3 --- 4 files changed, 1 insertion(+), 29 deletions(-) diff --git a/templates/bitcoind-chain-coordinator-service.template.yaml b/templates/bitcoind-chain-coordinator-service.template.yaml index feee3aa..961474d 100644 --- a/templates/bitcoind-chain-coordinator-service.template.yaml +++ b/templates/bitcoind-chain-coordinator-service.template.yaml @@ -9,22 +9,17 @@ spec: port: 18444 protocol: TCP targetPort: 18444 - nodePort: 30000 - name: rpc port: 18443 protocol: TCP targetPort: 18443 - nodePort: 30001 - name: coordinator-in port: 20445 protocol: TCP targetPort: 20445 - nodePort: 30005 - name: coordinator-con port: 20446 protocol: TCP targetPort: 20446 - nodePort: 30006 selector: name: bitcoind-chain-coordinator - type: NodePort diff --git a/templates/initial-config/kind.yaml b/templates/initial-config/kind.yaml index fb1c46c..a884547 100644 --- a/templates/initial-config/kind.yaml +++ b/templates/initial-config/kind.yaml @@ -4,20 +4,4 @@ nodes: - role: control-plane extraPortMappings: - containerPort: 30000 - hostPort: 18444 - - containerPort: 30001 - hostPort: 18443 - - containerPort: 30002 - hostPort: 20444 - - containerPort: 30003 - hostPort: 20443 - - containerPort: 30005 - hostPort: 20445 - - containerPort: 30006 - hostPort: 20446 - - containerPort: 30007 - hostPort: 3999 - - containerPort: 30008 - hostPort: 5432 - - containerPort: 30009 - hostPort: 3700 \ No newline at end of file + hostPort: 8477 \ No newline at end of file diff --git a/templates/stacks-api-service.template.yaml b/templates/stacks-api-service.template.yaml index 25d6c3c..3a23341 100644 --- a/templates/stacks-api-service.template.yaml +++ b/templates/stacks-api-service.template.yaml @@ -9,17 +9,13 @@ spec: port: 3999 protocol: TCP targetPort: 3999 - nodePort: 30007 - name: postgres port: 5432 protocol: TCP targetPort: 5432 - nodePort: 30008 - name: eventport port: 3700 protocol: TCP targetPort: 3700 - nodePort: 30009 selector: name: stacks-api - type: NodePort diff --git a/templates/stacks-node-service.template.yaml b/templates/stacks-node-service.template.yaml index 75d9290..190b98c 100644 --- a/templates/stacks-node-service.template.yaml +++ b/templates/stacks-node-service.template.yaml @@ -8,11 +8,8 @@ spec: - name: p2p port: 20444 protocol: TCP - nodePort: 30002 - name: rpc port: 20443 protocol: TCP - nodePort: 30003 selector: name: stacks-node - type: NodePort From ddfe03f5aeaf07a924417fefdefdcf9ef14313e9 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:37:02 -0400 Subject: [PATCH 40/60] create template to deploy devnet-api-server to k8s --- templates/stacks-devnet-api.template.yaml | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 templates/stacks-devnet-api.template.yaml diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml new file mode 100644 index 0000000..e5233d3 --- /dev/null +++ b/templates/stacks-devnet-api.template.yaml @@ -0,0 +1,77 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: devnet + labels: + name: devnet + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: stacks-devnet-api-service-account + namespace: devnet + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: stacks-devnet-api-service-account +rules: + - apiGroups: [""] + resources: ["pods", "services", "configmaps", "persistentvolumeclaims", "namespaces"] + verbs: ["get", "delete", "create"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: stacks-devnet-api-service-account +subjects: + - kind: ServiceAccount + name: stacks-devnet-api-service-account + namespace: devnet +roleRef: + kind: ClusterRole + name: stacks-devnet-api-service-account + apiGroup: rbac.authorization.k8s.io + +--- + +apiVersion: v1 +kind: Pod +metadata: + labels: + name: stacks-devnet-api + name: stacks-devnet-api + namespace: devnet +spec: + serviceAccountName: stacks-devnet-api-service-account + containers: + - command: + - ./stacks-devnet-api + name: stacks-devnet-api-container + image: stacks-devnet-api + imagePullPolicy: Never + ports: + - containerPort: 8478 + name: api + protocol: TCP + +--- +apiVersion: v1 +kind: Service +metadata: + name: stacks-devnet-api-service + namespace: devnet +spec: + ports: + - name: api + port: 8478 + protocol: TCP + targetPort: 8478 + nodePort: 30000 + selector: + name: stacks-devnet-api + type: NodePort + From e572b4887b6328868dcf3688cb928346e77d0885 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:37:14 -0400 Subject: [PATCH 41/60] docker file for devnet api --- .dockerignore | 9 +++++++++ Dockerfile | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..53a20cf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +/target/ +/examples/ +/dockerfiles/ +/Dockerfile +/.dockerignore +/.git* +/scripts/ +/logs/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d2d39b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM arm64v8/rust:1.67 as builder + +WORKDIR ./ +COPY . ./ + +RUN cargo build --manifest-path ./Cargo.toml + +FROM gcr.io/distroless/cc +COPY --from=builder target/debug/stacks-devnet-api / + +ENTRYPOINT ["./stacks-devnet-api"] \ No newline at end of file From f629c18eea309ae8a009f73b45ead4f1d06d6e50 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 13:39:15 -0400 Subject: [PATCH 42/60] update port for stacks-devnet-api service --- templates/stacks-devnet-api.template.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index e5233d3..1adcebc 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -67,9 +67,9 @@ metadata: spec: ports: - name: api - port: 8478 + port: 8477 protocol: TCP - targetPort: 8478 + targetPort: 8477 nodePort: 30000 selector: name: stacks-devnet-api From 285dc0d724bca0383c34002d708be4f280e9350b Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 14:05:55 -0400 Subject: [PATCH 43/60] use deployed pods! --- README.md | 5 ----- scripts/kind-deploy.sh | 1 - templates/bitcoind-chain-coordinator-pod.template.yaml | 4 ++-- templates/stacks-devnet-api.template.yaml | 5 +++-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 80378f1..58f7d58 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,3 @@ cargo run to start the server. Currently, the server is hosted on `localhost:8477` and exposes two routes: - `POST localhost:8477/api/v1/networks` - Creates a new devnet with configuration provided in request body. See [this example](./examples/new-network.example.json) object for the required parameters. - `DELETE localhost:8477/api/v1/network?network={namespace}` - Deletes all k8s assets deployed under the given namespace. - -### Notes -This project is still very eary in development and the code is fragile and will change a lot. Some known issues: - - if a k8s deployment fails, the app crashes. K8s deployments fail for a lot of reasons, so you'll need to restart the service a lot. - - the project relies on a docker image called `stacks-network`, which is not yet deployed to docker hub. This is in progress. \ No newline at end of file diff --git a/scripts/kind-deploy.sh b/scripts/kind-deploy.sh index ae7dab4..0168de6 100755 --- a/scripts/kind-deploy.sh +++ b/scripts/kind-deploy.sh @@ -1,6 +1,5 @@ kind create cluster --config=./templates/initial-config/kind.yaml && \ docker pull hirosystems/stacks-blockchain-api:latest --platform=linux/amd64 && \ kind load docker-image hirosystems/stacks-blockchain-api && \ -kind load docker-image stacks-network && \ kubectl --context kind-kind apply -f https://openebs.github.io/charts/openebs-operator.yaml && \ kubectl --context kind-kind apply -f ./templates/initial-config/storage-class.yaml \ No newline at end of file diff --git a/templates/bitcoind-chain-coordinator-pod.template.yaml b/templates/bitcoind-chain-coordinator-pod.template.yaml index d5057b4..b1048e0 100644 --- a/templates/bitcoind-chain-coordinator-pod.template.yaml +++ b/templates/bitcoind-chain-coordinator-pod.template.yaml @@ -38,8 +38,8 @@ spec: configMapKeyRef: name: namespace-conf key: NAMESPACE - image: stacks-network - imagePullPolicy: Never + image: quay.io/hirosystems/stacks-network-orchestrator:latest + imagePullPolicy: IfNotPresent name: chain-coordinator-container ports: - containerPort: 20445 diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index 1adcebc..26644d3 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -19,6 +19,7 @@ metadata: name: stacks-devnet-api-service-account rules: - apiGroups: [""] + # TODO: production version should not be able to create/delete namespaces (only get) resources: ["pods", "services", "configmaps", "persistentvolumeclaims", "namespaces"] verbs: ["get", "delete", "create"] @@ -51,8 +52,8 @@ spec: - command: - ./stacks-devnet-api name: stacks-devnet-api-container - image: stacks-devnet-api - imagePullPolicy: Never + image: quay.io/hirosystems/stacks-devnet-api:latest + imagePullPolicy: IfNotPresent ports: - containerPort: 8478 name: api From 0ef407b1f00a47ec9617c900651518a0113da367 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 7 Jun 2023 15:17:57 -0400 Subject: [PATCH 44/60] use Always imagePullPolicy for newer images --- templates/bitcoind-chain-coordinator-pod.template.yaml | 2 +- templates/stacks-devnet-api.template.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/bitcoind-chain-coordinator-pod.template.yaml b/templates/bitcoind-chain-coordinator-pod.template.yaml index b1048e0..4ca9520 100644 --- a/templates/bitcoind-chain-coordinator-pod.template.yaml +++ b/templates/bitcoind-chain-coordinator-pod.template.yaml @@ -39,7 +39,7 @@ spec: name: namespace-conf key: NAMESPACE image: quay.io/hirosystems/stacks-network-orchestrator:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always name: chain-coordinator-container ports: - containerPort: 20445 diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index 26644d3..31672c7 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -53,7 +53,7 @@ spec: - ./stacks-devnet-api name: stacks-devnet-api-container image: quay.io/hirosystems/stacks-devnet-api:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always ports: - containerPort: 8478 name: api From 6bc424f1f6542c8a652ecf6153931766580ba4c4 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 8 Jun 2023 10:16:19 -0400 Subject: [PATCH 45/60] build docker image in release mode --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d2d39b..e98f24c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,9 @@ FROM arm64v8/rust:1.67 as builder WORKDIR ./ COPY . ./ -RUN cargo build --manifest-path ./Cargo.toml +RUN cargo build --release --manifest-path ./Cargo.toml FROM gcr.io/distroless/cc -COPY --from=builder target/debug/stacks-devnet-api / +COPY --from=builder target/release/stacks-devnet-api / ENTRYPOINT ["./stacks-devnet-api"] \ No newline at end of file From 3d480a0442704713d760c694f35837e3e806a4b7 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 8 Jun 2023 12:15:48 -0400 Subject: [PATCH 46/60] remove sleep! --- src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a159e78..8167fc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,14 +4,13 @@ use k8s_openapi::{ NamespaceResourceScope, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, time::Duration}; +use std::collections::BTreeMap; use tower::BoxError; use kube::{ api::{Api, DeleteParams, PostParams, ResourceExt}, Client, }; -use std::thread::sleep; mod template_parser; use template_parser::{get_yaml_from_filename, Template}; @@ -108,8 +107,6 @@ impl StacksDevnetApiK8sManager { } self.deploy_bitcoin_node_pod(&config).await?; - sleep(Duration::from_secs(5)); - self.deploy_stacks_node_pod(&config).await?; if !config.disable_stacks_api { From 3f1b47ba33653fd5b5d3bc20198882c5223486af Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 8 Jun 2023 12:32:30 -0400 Subject: [PATCH 47/60] add back sleep :( --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8167fc5..a159e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,14 @@ use k8s_openapi::{ NamespaceResourceScope, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, time::Duration}; use tower::BoxError; use kube::{ api::{Api, DeleteParams, PostParams, ResourceExt}, Client, }; +use std::thread::sleep; mod template_parser; use template_parser::{get_yaml_from_filename, Template}; @@ -107,6 +108,8 @@ impl StacksDevnetApiK8sManager { } self.deploy_bitcoin_node_pod(&config).await?; + sleep(Duration::from_secs(5)); + self.deploy_stacks_node_pod(&config).await?; if !config.disable_stacks_api { From fd94b0e43df0cc335c7bb9696c217980b5bbd85f Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 12 Jun 2023 13:00:18 -0400 Subject: [PATCH 48/60] started logging implementation --- Cargo.lock | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/lib.rs | 70 ++++++++++++++--- src/main.rs | 117 ++++++++++++++++++++-------- 4 files changed, 364 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef71431..3cac9ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,21 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + [[package]] name = "async-stream" version = "0.3.5" @@ -56,6 +71,17 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -150,6 +176,25 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + [[package]] name = "cxx" version = "1.0.94" @@ -387,6 +432,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -396,6 +450,25 @@ dependencies = [ "libc", ] +[[package]] +name = "hiro-system-kit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69a7ca4bddaacbc8180886d378ad8c2b9f74217503002346719b57adbd83124" +dependencies = [ + "ansi_term", + "atty", + "futures", + "lazy_static", + "slog", + "slog-async", + "slog-atomic", + "slog-json", + "slog-scope", + "slog-term", + "tokio", +] + [[package]] name = "http" version = "0.2.9" @@ -687,6 +760,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.142" @@ -785,7 +864,16 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ "libc", ] @@ -992,6 +1080,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "ryu" version = "1.0.13" @@ -1093,6 +1187,70 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-async" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "766c59b252e62a34651412870ff55d8c4e6d04df19b43eecb2703e417b097ffe" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-atomic" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b517f2dda9e1458733eb8350bad1a3632ffed8141be4c0f3d6def899a9b066" +dependencies = [ + "arc-swap", + "slog", +] + +[[package]] +name = "slog-json" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" +dependencies = [ + "serde", + "serde_json", + "slog", + "time", +] + +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-term" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d29185c55b7b258b4f120eab00f48557d4d9bc814f41713f449d35b0f8977c" +dependencies = [ + "atty", + "slog", + "term", + "thread_local", + "time", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -1114,6 +1272,7 @@ name = "stacks-devnet-api" version = "0.1.0" dependencies = [ "futures", + "hiro-system-kit", "http-body", "hyper", "k8s-openapi", @@ -1148,6 +1307,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1177,6 +1353,45 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index dc16a20..869a42e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ serde_yaml = "0.9.21" hyper = { version = "0.14", features = ["full"] } tower = "0.4.13" http-body = "0.4.5" +hiro-system-kit = {version = "0.1.0", features = ["log"]} [dev-dependencies] tower-test = "0.4.0" diff --git a/src/lib.rs b/src/lib.rs index a159e78..f4b3af9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use hiro_system_kit::{slog, Logger}; use hyper::{body::Bytes, Body, Request, Response}; use k8s_openapi::{ api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, @@ -65,21 +66,54 @@ pub struct DevNetError { pub message: String, pub code: u16, } - +#[derive(Clone)] +pub struct Context { + pub logger: Option, + pub tracer: bool, +} #[derive(Clone)] pub struct StacksDevnetApiK8sManager { client: Client, + ctx: Context, +} +impl Context { + pub fn empty() -> Context { + Context { + logger: None, + tracer: false, + } + } + + pub fn try_log(&self, closure: F) + where + F: FnOnce(&Logger), + { + if let Some(ref logger) = self.logger { + closure(logger) + } + } + + pub fn expect_logger(&self) -> &Logger { + self.logger.as_ref().unwrap() + } } impl StacksDevnetApiK8sManager { - pub async fn default() -> StacksDevnetApiK8sManager { + pub async fn default(ctx: &Context) -> StacksDevnetApiK8sManager { let client = Client::try_default() .await .expect("could not create kube client"); - StacksDevnetApiK8sManager { client } + StacksDevnetApiK8sManager { + client, + ctx: ctx.to_owned(), + } } - pub async fn new(service: S, default_namespace: T) -> StacksDevnetApiK8sManager + pub async fn new( + service: S, + default_namespace: T, + ctx: &Context, + ) -> StacksDevnetApiK8sManager where S: tower::Service, Response = Response> + Send + 'static, S::Future: Send + 'static, @@ -89,7 +123,10 @@ impl StacksDevnetApiK8sManager { T: Into, { let client = Client::new(service, default_namespace); - StacksDevnetApiK8sManager { client } + StacksDevnetApiK8sManager { + client, + ctx: ctx.to_owned(), + } } pub async fn deploy_devnet(&self, config: StacksDevnetConfig) -> Result<(), DevNetError> { @@ -177,16 +214,29 @@ impl StacksDevnetApiK8sManager { if api_error.code == 404 { Ok(false) } else { + let msg = format!( + "error getting namespace {}: {}", + namespace_str, api_error.message + ); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); Err(DevNetError { - message: format!("unable to get namespace: {}", api_error.message), + message: msg, code: api_error.code, }) } } - Err(e) => Err(DevNetError { - message: format!("unable to get namespace: {}", e.to_string()), - code: 500, - }), + Err(e) => { + let msg = format!( + "error getting namespace {}: {}", + namespace_str, + e.to_string() + ); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } } } diff --git a/src/main.rs b/src/main.rs index 51b6728..1046acc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ +use hiro_system_kit::slog; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client, Method, Request, Response, Server, StatusCode, Uri}; -use stacks_devnet_api::{StacksDevnetApiK8sManager, StacksDevnetConfig}; +use stacks_devnet_api::{Context, StacksDevnetApiK8sManager, StacksDevnetConfig}; use std::net::IpAddr; use std::str::FromStr; use std::{convert::Infallible, net::SocketAddr}; @@ -12,24 +13,32 @@ async fn main() { const PORT: &str = "8477"; let endpoint: String = HOST.to_owned() + ":" + PORT; let addr: SocketAddr = endpoint.parse().expect("Could not parse ip:port."); - let k8s_manager = StacksDevnetApiK8sManager::default().await; + + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::default(&ctx).await; let make_svc = make_service_fn(|conn: &AddrStream| { let k8s_manager = k8s_manager.clone(); + let ctx = ctx.clone(); let remote_addr = conn.remote_addr().ip(); async move { Ok::<_, Infallible>(service_fn(move |req| { - handle_request(remote_addr, req, k8s_manager.clone()) + handle_request(remote_addr, req, k8s_manager.clone(), ctx.clone()) })) } }); let server = Server::bind(&addr).serve(make_svc); - println!("Running server on {:?}", addr); + ctx.try_log(|logger| slog::info!(logger, "Running server on {:?}", addr)); if let Err(e) = server.await { - eprintln!("server error: {}", e); + ctx.try_log(|logger| slog::error!(logger, "server error: {}", e)); } } @@ -57,16 +66,20 @@ fn mutate_request_for_proxy( request } -async fn proxy(request: Request) -> Result, Infallible> { +async fn proxy(request: Request, ctx: &Context) -> Result, Infallible> { let client = Client::new(); - println!("forwarding request to {}", request.uri()); + ctx.try_log(|logger| slog::info!(logger, "forwarding request to {}", request.uri())); match client.request(request).await { Ok(response) => Ok(response), - Err(_error) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), + Err(e) => { + let msg = format!("error proxying request: {}", e.to_string()); + ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::try_from(msg).unwrap()) + .unwrap()) + } } } @@ -151,20 +164,30 @@ async fn handle_request( _client_ip: IpAddr, request: Request, k8s_manager: StacksDevnetApiK8sManager, + ctx: Context, ) -> Result, Infallible> { let uri = request.uri(); let path = uri.path(); let method = request.method(); - println!("received request, method: {}. path: {}", method, path); + ctx.try_log(|logger| { + slog::info!( + logger, + "received request with method {} and path {}", + method, + path + ) + }); if path == "/api/v1/networks" { return match method { &Method::POST => { let body = hyper::body::to_bytes(request.into_body()).await; if body.is_err() { + let msg = "failed to parse request body"; + ctx.try_log(|logger| slog::error!(logger, "{}", msg)); return Ok(Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::try_from("failed to parse request body").unwrap()) + .body(Body::try_from(msg).unwrap()) .unwrap()); } let body = body.unwrap(); @@ -236,10 +259,14 @@ async fn handle_request( .status(StatusCode::OK) .body(Body::empty()) .unwrap()), - Err(_e) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), + Err(e) => { + let msg = format!("error deleting network {}: {}", &network, e.to_string()); + ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::try_from(msg).unwrap()) + .unwrap()) + } }, &Method::GET => Ok(Response::builder() .status(StatusCode::NOT_IMPLEMENTED) @@ -265,7 +292,7 @@ async fn handle_request( Some(proxy_data) => { let proxy_request = mutate_request_for_proxy(request, &network, &remaining_path, proxy_data); - proxy(proxy_request).await + proxy(proxy_request, &ctx).await } None => Ok(Response::builder() .status(StatusCode::BAD_REQUEST) @@ -328,7 +355,13 @@ mod tests { mock_k8s_handler(&mut handle).await; }); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); let invalid_paths = vec![ "/path", @@ -340,7 +373,7 @@ mod tests { for path in invalid_paths { let request_builder = Request::builder().uri(path).method("GET"); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(client_ip, request, k8s_manager.clone()) + let mut response = handle_request(client_ip, request, k8s_manager.clone(), ctx.clone()) .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); @@ -358,13 +391,19 @@ mod tests { mock_k8s_handler(&mut handle).await; }); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); let path = "/api/v1/network/undeployed"; let request_builder = Request::builder().uri(path).method("GET"); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(client_ip, request, k8s_manager.clone()) + let mut response = handle_request(client_ip, request, k8s_manager.clone(), ctx) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); @@ -381,13 +420,19 @@ mod tests { mock_k8s_handler(&mut handle).await; }); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); let path = "/api/v1/network/"; let request_builder = Request::builder().uri(path).method("GET"); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(client_ip, request, k8s_manager.clone()) + let mut response = handle_request(client_ip, request, k8s_manager.clone(), ctx) .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); @@ -404,7 +449,13 @@ mod tests { mock_k8s_handler(&mut handle).await; }); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); let path = "/api/v1/networks"; @@ -412,7 +463,7 @@ mod tests { for method in methods { let request_builder = Request::builder().uri(path).method(method); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(client_ip, request, k8s_manager.clone()) + let mut response = handle_request(client_ip, request, k8s_manager.clone(), ctx.clone()) .await .unwrap(); assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); @@ -429,13 +480,19 @@ mod tests { mock_k8s_handler(&mut handle).await; }); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default").await; + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; + let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; let client_ip: IpAddr = IpAddr::V4([0, 0, 0, 0].into()); let path = "/api/v1/networks"; let request_builder = Request::builder().uri(path).method("POST"); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(client_ip, request, k8s_manager.clone()) + let mut response = handle_request(client_ip, request, k8s_manager.clone(), ctx) .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); @@ -550,7 +607,7 @@ mod tests { let network = path_parts.network.unwrap(); let subroute = path_parts.subroute.unwrap(); let remainder = path_parts.remainder.unwrap(); - println!("{}", &remainder); + let proxy_data = get_proxy_data(&subroute); let request_builder = Request::builder().uri("/").method("POST"); let request: Request = request_builder.body(Body::empty()).unwrap(); @@ -560,7 +617,7 @@ mod tests { "http://stacks-node-service.{}.svc.cluster.local:20443/{}", network, &remainder ); - println!("{expected}"); + assert_eq!(actual_url, expected); } } From 790773deae6f5b365edce512ab6cc31c6390b935 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 12 Jun 2023 21:39:53 -0400 Subject: [PATCH 49/60] add more logging --- src/lib.rs | 284 +++++++++++++++++++++++++++++++--------------------- src/main.rs | 23 +++-- 2 files changed, 183 insertions(+), 124 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4b3af9..d53a2d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use std::{collections::BTreeMap, time::Duration}; use tower::BoxError; use kube::{ - api::{Api, DeleteParams, PostParams, ResourceExt}, + api::{Api, DeleteParams, PostParams}, Client, }; use std::thread::sleep; @@ -66,16 +66,13 @@ pub struct DevNetError { pub message: String, pub code: u16, } + #[derive(Clone)] pub struct Context { pub logger: Option, pub tracer: bool, } -#[derive(Clone)] -pub struct StacksDevnetApiK8sManager { - client: Client, - ctx: Context, -} + impl Context { pub fn empty() -> Context { Context { @@ -97,6 +94,11 @@ impl Context { self.logger.as_ref().unwrap() } } +#[derive(Clone)] +pub struct StacksDevnetApiK8sManager { + client: Client, + ctx: Context, +} impl StacksDevnetApiK8sManager { pub async fn default(ctx: &Context) -> StacksDevnetApiK8sManager { @@ -137,8 +139,13 @@ impl StacksDevnetApiK8sManager { if cfg!(debug_assertions) { self.deploy_namespace(&namespace).await?; } else { + let msg = format!( + "cannot create devnet because namespace {} does not exist", + namespace + ); + self.ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); return Err(DevNetError { - message: "Cannot create devnet before namespace exists.".into(), + message: msg.into(), code: 400, }); } @@ -156,53 +163,44 @@ impl StacksDevnetApiK8sManager { } pub async fn delete_devnet(&self, namespace: &str) -> Result<(), Box> { + let pods = vec!["bitcoind-chain-coordinator", "stacks-node", "stacks-api"]; + for pod in pods { + let _ = self.delete_resource::(namespace, pod).await; + } + + let pods = vec!["bitcoind-chain-coordinator", "stacks-node", "stacks-api"]; + for pod in pods { + let _ = self.delete_resource::(namespace, pod).await; + } + let configmaps = vec![ + "bitcoind-conf", + "stacks-node-conf", + "stacks-api-conf", + "stacks-api-postgres-conf", + "deployment-plan-conf", + "devnet-conf", + "project-dir-conf", + "namespace-conf", + "project-manifest-conf", + ]; + for configmap in configmaps { + let _ = self.delete_resource::(namespace, configmap).await; + } + let services = vec![ + "bitcoind-chain-coordinator-service", + "stacks-node-service", + "stacks-api-service", + ]; + for service in services { + let _ = self.delete_resource::(namespace, service).await; + } + let pvcs = vec!["stacks-api-pvc"]; + for pvc in pvcs { + let _ = self.delete_resource::(namespace, pvc).await; + } if cfg!(debug_assertions) { let _ = self.delete_namespace(namespace).await; } - let _ = self - .delete_resource::(namespace, "bitcoind-chain-coordinator") - .await; - let _ = self.delete_resource::(namespace, "stacks-node").await; - let _ = self.delete_resource::(namespace, "stacks-api").await; - let _ = self - .delete_resource::(namespace, "bitcoind-conf") - .await; - let _ = self - .delete_resource::(namespace, "stacks-node-conf") - .await; - let _ = self - .delete_resource::(namespace, "stacks-api-conf") - .await; - let _ = self - .delete_resource::(namespace, "stacks-api-postgres-conf") - .await; - let _ = self - .delete_resource::(namespace, "deployment-plan-conf") - .await; - let _ = self - .delete_resource::(namespace, "devnet-conf") - .await; - let _ = self - .delete_resource::(namespace, "project-dir-conf") - .await; - let _ = self - .delete_resource::(namespace, "namespace-conf") - .await; - let _ = self - .delete_resource::(namespace, "project-manifest-conf") - .await; - let _ = self - .delete_resource::(namespace, "bitcoind-chain-coordinator-service") - .await; - let _ = self - .delete_resource::(namespace, "stacks-node-service") - .await; - let _ = self - .delete_resource::(namespace, "stacks-api-service") - .await; - let _ = self - .delete_resource::(namespace, "stacks-api-pvc") - .await; Ok(()) } @@ -241,7 +239,7 @@ impl StacksDevnetApiK8sManager { } async fn deploy_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { - let mut namespace: Namespace = get_resource_from_file(Template::Namespace)?; + let mut namespace: Namespace = self.get_resource_from_file(Template::Namespace)?; namespace.metadata.name = Some(namespace_str.to_owned()); namespace.metadata.labels = @@ -250,20 +248,28 @@ impl StacksDevnetApiK8sManager { let namespace_api: Api = kube::Api::all(self.client.to_owned()); let pp = PostParams::default(); + + self.ctx + .try_log(|logger| slog::info!(logger, "creating namespace {}", namespace_str)); match namespace_api.create(&pp, &namespace).await { - Ok(namespace) => { - let name = namespace.name_any(); - println!("created namespace {}", name); + Ok(_) => { + self.ctx.try_log(|logger| { + slog::info!(logger, "successfully created namespace {}", namespace_str) + }); Ok(()) } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to create namespace: {}", api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to create namespace: {}", e.to_string()), - code: 500, - }), + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to create namespace {}: {}", namespace_str, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } } } @@ -271,7 +277,7 @@ impl StacksDevnetApiK8sManager { &self, namespace: &str, resource: K, - resource_name: &str, + resource_type: &str, ) -> Result<(), DevNetError> where ::DynamicType: Default, @@ -283,32 +289,56 @@ impl StacksDevnetApiK8sManager { let resource_api: Api = Api::namespaced(self.client.to_owned(), &namespace); let pp = PostParams::default(); + let name = match resource.meta().name.as_ref() { + Some(name) => name, + None => { + self.ctx.try_log(|logger| { + slog::warn!( + logger, + "resource does not have a name field. it really should" + ) + }); + "no-name" + } + }; + let resource_details = format!( + "RESOURCE: {}, NAME: {}, NAMESPACE: {}", + resource_type, name, namespace + ); + self.ctx + .try_log(|logger| slog::info!(logger, "creating {}", resource_details)); + match resource_api.create(&pp, &resource).await { - Ok(resource) => { - let name = resource.name_any(); - println!("created {} {}", resource_name, name); + Ok(_) => { + self.ctx.try_log(|logger| { + slog::info!(logger, "successfully created {}", resource_details) + }); Ok(()) } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to create {}: {}", resource_name, api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to create {}: {}", resource_name, e.to_string()), - code: 500, - }), + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to create {}, ERROR: {}", resource_details, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } } } async fn deploy_pod(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pod: Pod = get_resource_from_file(template)?; + let mut pod: Pod = self.get_resource_from_file(template)?; pod.metadata.namespace = Some(namespace.to_owned()); self.deploy_resource(namespace, pod, "pod").await } async fn deploy_service(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut service: Service = get_resource_from_file(template)?; + let mut service: Service = self.get_resource_from_file(template)?; service.metadata.namespace = Some(namespace.to_owned()); self.deploy_resource(namespace, service, "service").await @@ -320,7 +350,7 @@ impl StacksDevnetApiK8sManager { namespace: &str, configmap_data: Option>, ) -> Result<(), DevNetError> { - let mut configmap: ConfigMap = get_resource_from_file(template)?; + let mut configmap: ConfigMap = self.get_resource_from_file(template)?; configmap.metadata.namespace = Some(namespace.to_owned()); if let Some(configmap_data) = configmap_data { @@ -336,7 +366,7 @@ impl StacksDevnetApiK8sManager { } async fn deploy_pvc(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pvc: PersistentVolumeClaim = get_resource_from_file(template)?; + let mut pvc: PersistentVolumeClaim = self.get_resource_from_file(template)?; pvc.metadata.namespace = Some(namespace.to_owned()); @@ -668,58 +698,82 @@ impl StacksDevnetApiK8sManager { { let api: Api = Api::namespaced(self.client.to_owned(), &namespace); let dp = DeleteParams::default(); + + let resource_details = format!( + "RESOURCE: {}, NAME: {}, NAMESPACE: {}", + std::any::type_name::(), + resource_name, + namespace + ); + self.ctx + .try_log(|logger| slog::info!(logger, "deleting {}", resource_details)); match api.delete(resource_name, &dp).await { - Ok(resource) => { - resource.map_left(|del| { - assert_eq!(del.name_any(), resource_name); - println!("Deleting {resource_name} started"); + Ok(_) => { + self.ctx.try_log(|logger| { + slog::info!(logger, "successfully deleted {}", resource_details) }); Ok(()) } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to delete {}: {}", resource_name, api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to delete {}: {}", resource_name, e.to_string()), - code: 500, - }), + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to delete {}, ERROR: {}", resource_details, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } } } async fn delete_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { let api: Api = kube::Api::all(self.client.to_owned()); + let resource_details = format!("RESOURCE: namespace, NAME: {}", namespace_str); + self.ctx + .try_log(|logger| slog::info!(logger, "deleting {}", resource_details)); + let dp = DeleteParams::default(); match api.delete(namespace_str, &dp).await { - Ok(namespace) => { - namespace.map_left(|del| { - assert_eq!(del.name_any(), namespace_str); - println!("Deleting namespace started"); + Ok(_) => { + self.ctx.try_log(|logger| { + slog::info!(logger, "succesfully deleted {}", resource_details) }); Ok(()) } - Err(kube::Error::Api(api_error)) => Err(DevNetError { - message: format!("unable to delete namespace: {}", api_error.message), - code: api_error.code, - }), - Err(e) => Err(DevNetError { - message: format!("unable to delete namespace: {}", e.to_string()), - code: 500, - }), + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to delete {}, ERROR: {}", resource_details, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } } } -} + fn get_resource_from_file(&self, template: Template) -> Result + where + K: DeserializeOwned, + { + let template_str = get_yaml_from_filename(template); -fn get_resource_from_file(template: Template) -> Result -where - K: DeserializeOwned, -{ - let template_str = get_yaml_from_filename(template); - - let resource: K = serde_yaml::from_str(template_str).map_err(|e| DevNetError { - message: format!("unable to parse template file: {}", e.to_string()), - code: 500, - })?; - Ok(resource) + match serde_yaml::from_str(template_str) { + Ok(resource) => Ok(resource), + Err(e) => { + let msg = format!("unable to parse template file: {}", e.to_string()); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } + } + } } diff --git a/src/main.rs b/src/main.rs index 1046acc..fd38855 100644 --- a/src/main.rs +++ b/src/main.rs @@ -244,9 +244,11 @@ async fn handle_request( } }; if !exists { + let msg = format!("network {} does not exist", &network); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); return Ok(Response::builder() .status(StatusCode::from_u16(404).unwrap()) - .body(Body::try_from("network does not exist").unwrap()) + .body(Body::try_from(msg).unwrap()) .unwrap()); } @@ -259,14 +261,17 @@ async fn handle_request( .status(StatusCode::OK) .body(Body::empty()) .unwrap()), - Err(e) => { - let msg = format!("error deleting network {}: {}", &network, e.to_string()); - ctx.try_log(|logger| slog::error!(logger, "{}", msg)); - Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::try_from(msg).unwrap()) - .unwrap()) - } + Err(e) => Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body( + Body::try_from(format!( + "error deleting network {}: {}", + &network, + e.to_string() + )) + .unwrap(), + ) + .unwrap()), }, &Method::GET => Ok(Response::builder() .status(StatusCode::NOT_IMPLEMENTED) From 5cf7e1b346f7cc138dd2111e8ef26ef49d546235 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Mon, 12 Jun 2023 21:40:28 -0400 Subject: [PATCH 50/60] fix port number --- templates/stacks-devnet-api.template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index 31672c7..e0c4999 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -55,7 +55,7 @@ spec: image: quay.io/hirosystems/stacks-devnet-api:latest imagePullPolicy: Always ports: - - containerPort: 8478 + - containerPort: 8477 name: api protocol: TCP From 87ca8dd761012f9c8e36f82dae5a2e30bcdfcb44 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 14 Jun 2023 11:48:12 -0400 Subject: [PATCH 51/60] fix types for deleteing resources --- src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d53a2d4..e792a2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,10 +168,6 @@ impl StacksDevnetApiK8sManager { let _ = self.delete_resource::(namespace, pod).await; } - let pods = vec!["bitcoind-chain-coordinator", "stacks-node", "stacks-api"]; - for pod in pods { - let _ = self.delete_resource::(namespace, pod).await; - } let configmaps = vec![ "bitcoind-conf", "stacks-node-conf", @@ -184,7 +180,9 @@ impl StacksDevnetApiK8sManager { "project-manifest-conf", ]; for configmap in configmaps { - let _ = self.delete_resource::(namespace, configmap).await; + let _ = self + .delete_resource::(namespace, configmap) + .await; } let services = vec![ "bitcoind-chain-coordinator-service", @@ -192,14 +190,13 @@ impl StacksDevnetApiK8sManager { "stacks-api-service", ]; for service in services { - let _ = self.delete_resource::(namespace, service).await; + let _ = self.delete_resource::(namespace, service).await; } let pvcs = vec!["stacks-api-pvc"]; for pvc in pvcs { - let _ = self.delete_resource::(namespace, pvc).await; - } - if cfg!(debug_assertions) { - let _ = self.delete_namespace(namespace).await; + let _ = self + .delete_resource::(namespace, pvc) + .await; } Ok(()) } From f07a082e85fdeff444349242bc6a9e69a7b199dd Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Wed, 14 Jun 2023 11:48:40 -0400 Subject: [PATCH 52/60] remove delete_namespace func --- src/lib.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e792a2f..09f77d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -726,35 +726,6 @@ impl StacksDevnetApiK8sManager { } } - async fn delete_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { - let api: Api = kube::Api::all(self.client.to_owned()); - - let resource_details = format!("RESOURCE: namespace, NAME: {}", namespace_str); - self.ctx - .try_log(|logger| slog::info!(logger, "deleting {}", resource_details)); - - let dp = DeleteParams::default(); - match api.delete(namespace_str, &dp).await { - Ok(_) => { - self.ctx.try_log(|logger| { - slog::info!(logger, "succesfully deleted {}", resource_details) - }); - Ok(()) - } - Err(e) => { - let e = match e { - kube::Error::Api(api_error) => (api_error.message, api_error.code), - e => (e.to_string(), 500), - }; - let msg = format!("failed to delete {}, ERROR: {}", resource_details, e.0); - self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); - Err(DevNetError { - message: msg, - code: e.1, - }) - } - } - } fn get_resource_from_file(&self, template: Template) -> Result where K: DeserializeOwned, From 117bcdf0380d635831073360595893ba2e3195af Mon Sep 17 00:00:00 2001 From: Micaiah Reid Date: Tue, 20 Jun 2023 14:55:19 -0400 Subject: [PATCH 53/60] feat: add route to get network info (#21) * add modules to manage k8s resources * update delete devnet to use utils * add strum deps * add struct for devnet info response * add struct for stacksv2info response * helper function to fetch status from a pod * helper function to get stacks v2 info route * function to query/assemble devnet info * remove get_proxy_data * add route to get devnet info * rename struct * add pvc module * update delete devnet to use pvc mod * rename utils -> resources * add ports to service * use service ports enum * revert server port number * refactor template parser to use new resource enums * fix service url * improve logging * add content type to response * add to ClusterRole resource list --- Cargo.lock | 27 ++ Cargo.toml | 2 + src/lib.rs | 348 +++++++++++++++++----- src/main.rs | 103 +++---- src/resources/configmap.rs | 31 ++ src/resources/mod.rs | 17 ++ src/resources/pod.rs | 19 ++ src/resources/pvc.rs | 15 + src/resources/service.rs | 59 ++++ src/template_parser.rs | 69 ++--- templates/stacks-devnet-api.template.yaml | 2 +- 11 files changed, 521 insertions(+), 171 deletions(-) create mode 100644 src/resources/configmap.rs create mode 100644 src/resources/mod.rs create mode 100644 src/resources/pod.rs create mode 100644 src/resources/pvc.rs create mode 100644 src/resources/service.rs diff --git a/Cargo.lock b/Cargo.lock index 3cac9ba..883094c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,6 +432,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1280,11 +1286,32 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "strum", + "strum_macros", "tokio", "tower", "tower-test", ] +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 869a42e..eb1ddd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ hyper = { version = "0.14", features = ["full"] } tower = "0.4.13" http-body = "0.4.5" hiro-system-kit = {version = "0.1.0", features = ["log"]} +strum_macros = "0.24.3" +strum = "0.24.1" [dev-dependencies] tower-test = "0.4.0" diff --git a/src/lib.rs b/src/lib.rs index 09f77d9..a5e892e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,33 @@ +use futures::future::try_join4; use hiro_system_kit::{slog, Logger}; -use hyper::{body::Bytes, Body, Request, Response}; +use hyper::{body::Bytes, Body, Client as HttpClient, Request, Response, Uri}; use k8s_openapi::{ api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, NamespaceResourceScope, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, time::Duration}; -use tower::BoxError; - use kube::{ api::{Api, DeleteParams, PostParams}, Client, }; +use resources::{ + pvc::StacksDevnetPvc, + service::{get_service_port, ServicePort}, + StacksDevnetResource, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::thread::sleep; +use std::{collections::BTreeMap, str::FromStr, time::Duration}; +use strum::IntoEnumIterator; +use tower::BoxError; mod template_parser; -use template_parser::{get_yaml_from_filename, Template}; +use template_parser::get_yaml_from_resource; -const BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME: &str = "bitcoind-chain-coordinator-service"; -const STACKS_NODE_SERVICE_NAME: &str = "stacks-node-service"; +pub mod resources; +use crate::resources::configmap::StacksDevnetConfigmap; +use crate::resources::pod::StacksDevnetPod; +use crate::resources::service::{get_service_url, StacksDevnetService}; -const BITCOIND_P2P_PORT: i32 = 18444; -const BITCOIND_RPC_PORT: i32 = 18443; -const STACKS_NODE_P2P_PORT: i32 = 20444; -const STACKS_NODE_RPC_PORT: i32 = 20443; -const CHAIN_COORDINATOR_INGESTION_PORT: i32 = 20445; #[derive(Serialize, Deserialize, Debug)] pub struct StacksDevnetConfig { namespace: String, @@ -94,6 +97,24 @@ impl Context { self.logger.as_ref().unwrap() } } + +#[derive(Serialize, Deserialize, Debug)] +pub struct StacksDevnetInfoResponse { + bitcoind_node_status: Option, + stacks_node_status: Option, + stacks_api_status: Option, + bitcoind_node_started_at: Option, + stacks_node_started_at: Option, + stacks_api_started_at: Option, + stacks_chain_tip: u64, + bitcoin_chain_tip: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +struct StacksV2InfoResponse { + burn_block_height: u64, + stacks_tip_height: u64, +} #[derive(Clone)] pub struct StacksDevnetApiK8sManager { client: Client, @@ -163,39 +184,29 @@ impl StacksDevnetApiK8sManager { } pub async fn delete_devnet(&self, namespace: &str) -> Result<(), Box> { - let pods = vec!["bitcoind-chain-coordinator", "stacks-node", "stacks-api"]; + let pods: Vec = StacksDevnetPod::iter().map(|p| p.to_string()).collect(); for pod in pods { - let _ = self.delete_resource::(namespace, pod).await; + let _ = self.delete_resource::(namespace, &pod).await; } - let configmaps = vec![ - "bitcoind-conf", - "stacks-node-conf", - "stacks-api-conf", - "stacks-api-postgres-conf", - "deployment-plan-conf", - "devnet-conf", - "project-dir-conf", - "namespace-conf", - "project-manifest-conf", - ]; + let configmaps: Vec = StacksDevnetConfigmap::iter() + .map(|c| c.to_string()) + .collect(); for configmap in configmaps { let _ = self - .delete_resource::(namespace, configmap) + .delete_resource::(namespace, &configmap) .await; } - let services = vec![ - "bitcoind-chain-coordinator-service", - "stacks-node-service", - "stacks-api-service", - ]; + + let services: Vec = StacksDevnetService::iter().map(|s| s.to_string()).collect(); for service in services { - let _ = self.delete_resource::(namespace, service).await; + let _ = self.delete_resource::(namespace, &service).await; } - let pvcs = vec!["stacks-api-pvc"]; + + let pvcs: Vec = StacksDevnetPvc::iter().map(|s| s.to_string()).collect(); for pvc in pvcs { let _ = self - .delete_resource::(namespace, pvc) + .delete_resource::(namespace, &pvc) .await; } Ok(()) @@ -235,8 +246,164 @@ impl StacksDevnetApiK8sManager { } } + async fn get_pod_status_info( + &self, + namespace: &str, + pod: StacksDevnetPod, + ) -> Result<(Option, Option), DevNetError> { + let context = format!("NAMESPACE: {}, POD: {}", namespace, pod); + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!(logger, "getting pod status {}", context) + }); + let pod_api: Api = Api::namespaced(self.client.to_owned(), &namespace); + let pod_name = pod.to_string(); + match pod_api.get_status(&pod_name).await { + Ok(pod_with_status) => match pod_with_status.status { + Some(status) => { + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!(logger, "successfully retrieved pod status {}", context) + }); + let start_time = match status.start_time { + Some(st) => Some(st.0.to_string()), + None => None, + }; + Ok((status.phase, start_time)) + } + None => Ok((None, None)), + }, + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to get pod status {}, ERROR: {}", context, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } + } + } + + async fn get_stacks_v2_info( + &self, + namespace: &str, + ) -> Result { + let client = HttpClient::new(); + let url = get_service_url(namespace, StacksDevnetService::StacksNode); + let port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); + let url = format!("{}:{}/v2/info", url, port); + + let context = format!("NAMESPACE: {}", namespace); + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!( + logger, + "requesting /v2/info route of stacks node {}", + context + ) + }); + + match Uri::from_str(&url) { + Ok(uri) => match client.get(uri).await { + Ok(response) => match hyper::body::to_bytes(response.into_body()).await { + Ok(body) => match serde_json::from_slice::(&body) { + Ok(config) => { + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!( + logger, + "successfully requested /v2/info route of stacks node {}", + context + ) + }); + Ok(config) + } + Err(e) => { + let msg = format!( + "failed to parse response: {}, ERROR: {}", + context, + e.to_string() + ); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } + }, + Err(e) => { + let msg = format!( + "failed to parse response: {}, ERROR: {}", + context, + e.to_string() + ); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } + }, + Err(e) => { + let msg = format!( + "failed to query stacks node: {}, ERROR: {}", + context, + e.to_string() + ); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } + }, + Err(e) => { + let msg = format!("failed to parse url: {} ERROR: {}", context, e.to_string()); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: 500, + }) + } + } + } + + pub async fn get_devnet_info( + &self, + namespace: &str, + ) -> Result { + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!(logger, "getting devnet info NAMESPACE: {}", namespace) + }); + + let ( + (bitcoind_node_status, bitcoind_node_started_at), + (stacks_node_status, stacks_node_started_at), + (stacks_api_status, stacks_api_started_at), + chain_info, + ) = try_join4( + self.get_pod_status_info(&namespace, StacksDevnetPod::BitcoindNode), + self.get_pod_status_info(&namespace, StacksDevnetPod::StacksNode), + self.get_pod_status_info(&namespace, StacksDevnetPod::StacksApi), + self.get_stacks_v2_info(&namespace), + ) + .await?; + + Ok(StacksDevnetInfoResponse { + bitcoind_node_status, + stacks_node_status, + stacks_api_status, + bitcoind_node_started_at, + stacks_node_started_at, + stacks_api_started_at, + stacks_chain_tip: chain_info.stacks_tip_height, + bitcoin_chain_tip: chain_info.burn_block_height, + }) + } + async fn deploy_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { - let mut namespace: Namespace = self.get_resource_from_file(Template::Namespace)?; + let mut namespace: Namespace = + self.get_resource_from_file(StacksDevnetResource::Namespace)?; namespace.metadata.name = Some(namespace_str.to_owned()); namespace.metadata.labels = @@ -327,15 +494,20 @@ impl StacksDevnetApiK8sManager { } } - async fn deploy_pod(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pod: Pod = self.get_resource_from_file(template)?; + async fn deploy_pod(&self, pod: StacksDevnetPod, namespace: &str) -> Result<(), DevNetError> { + let mut pod: Pod = self.get_resource_from_file(StacksDevnetResource::Pod(pod))?; pod.metadata.namespace = Some(namespace.to_owned()); self.deploy_resource(namespace, pod, "pod").await } - async fn deploy_service(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut service: Service = self.get_resource_from_file(template)?; + async fn deploy_service( + &self, + service: StacksDevnetService, + namespace: &str, + ) -> Result<(), DevNetError> { + let mut service: Service = + self.get_resource_from_file(StacksDevnetResource::Service(service))?; service.metadata.namespace = Some(namespace.to_owned()); self.deploy_resource(namespace, service, "service").await @@ -343,11 +515,12 @@ impl StacksDevnetApiK8sManager { async fn deploy_configmap( &self, - template: Template, + configmap: StacksDevnetConfigmap, namespace: &str, configmap_data: Option>, ) -> Result<(), DevNetError> { - let mut configmap: ConfigMap = self.get_resource_from_file(template)?; + let mut configmap: ConfigMap = + self.get_resource_from_file(StacksDevnetResource::Configmap(configmap))?; configmap.metadata.namespace = Some(namespace.to_owned()); if let Some(configmap_data) = configmap_data { @@ -362,8 +535,9 @@ impl StacksDevnetApiK8sManager { .await } - async fn deploy_pvc(&self, template: Template, namespace: &str) -> Result<(), DevNetError> { - let mut pvc: PersistentVolumeClaim = self.get_resource_from_file(template)?; + async fn deploy_pvc(&self, pvc: StacksDevnetPvc, namespace: &str) -> Result<(), DevNetError> { + let mut pvc: PersistentVolumeClaim = + self.get_resource_from_file(StacksDevnetResource::Pvc(pvc))?; pvc.metadata.namespace = Some(namespace.to_owned()); @@ -376,6 +550,11 @@ impl StacksDevnetApiK8sManager { ) -> Result<(), DevNetError> { let namespace = &config.namespace; + let bitcoin_rpc_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::RPC).unwrap(); + let bitcoin_p2p_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::P2P).unwrap(); + let bitcoind_conf = format!( r#" server=1 @@ -402,27 +581,27 @@ impl StacksDevnetApiK8sManager { "#, config.bitcoin_node_username, config.bitcoin_node_password, - BITCOIND_P2P_PORT, - BITCOIND_RPC_PORT, - BITCOIND_RPC_PORT + bitcoin_p2p_port, + bitcoin_rpc_port, + bitcoin_rpc_port ); self.deploy_configmap( - Template::BitcoindConfigmap, + StacksDevnetConfigmap::BitcoindNode, &namespace, Some(vec![("bitcoin.conf", &bitcoind_conf)]), ) .await?; self.deploy_configmap( - Template::ChainCoordinatorNamespaceConfigmap, + StacksDevnetConfigmap::Namespace, &namespace, Some(vec![("NAMESPACE", &namespace)]), ) .await?; self.deploy_configmap( - Template::ChainCoordinatorProjectManifestConfigmap, + StacksDevnetConfigmap::ProjectManifest, &namespace, Some(vec![("Clarinet.toml", &config.project_manifest)]), ) @@ -440,14 +619,14 @@ impl StacksDevnetApiK8sManager { )); self.deploy_configmap( - Template::ChainCoordinatorDevnetConfigmap, + StacksDevnetConfigmap::Devnet, &namespace, Some(vec![("Devnet.toml", &devnet_config)]), ) .await?; self.deploy_configmap( - Template::ChainCoordinatorDeploymentPlanConfigmap, + StacksDevnetConfigmap::DeploymentPlan, &namespace, Some(vec![("default.devnet-plan.yaml", &config.deployment_plan)]), ) @@ -458,16 +637,16 @@ impl StacksDevnetApiK8sManager { contracts.push((contract_name, contract_source)); } self.deploy_configmap( - Template::ChainCoordinatorProjectDirConfigmap, + StacksDevnetConfigmap::ProjectDir, &namespace, Some(contracts), ) .await?; - self.deploy_pod(Template::BitcoindChainCoordinatorPod, &namespace) + self.deploy_pod(StacksDevnetPod::BitcoindNode, &namespace) .await?; - self.deploy_service(Template::BitcoindChainCoordinatorService, namespace) + self.deploy_service(StacksDevnetService::BitcoindNode, namespace) .await?; Ok(()) @@ -476,6 +655,9 @@ impl StacksDevnetApiK8sManager { async fn deploy_stacks_node_pod(&self, config: &StacksDevnetConfig) -> Result<(), DevNetError> { let namespace = &config.namespace; + let chain_coordinator_ingestion_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Ingestion).unwrap(); + let stacks_conf = { let mut stacks_conf = format!( r#" @@ -506,8 +688,8 @@ impl StacksDevnetApiK8sManager { block_reward_recipient = "{}" # microblock_attempt_time_ms = 15000 "#, - STACKS_NODE_RPC_PORT, - STACKS_NODE_P2P_PORT, + get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(), + get_service_port(StacksDevnetService::StacksNode, ServicePort::P2P).unwrap(), config.stacks_miner_secret_key_hex, config.stacks_miner_secret_key_hex, config.stacks_node_wait_time_for_microblocks, @@ -537,11 +719,8 @@ impl StacksDevnetApiK8sManager { config.miner_coinbase_recipient, balance )); - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let bitcoind_chain_coordinator_host = format!( - "{}.{}", - &BITCOIND_CHAIN_COORDINATOR_SERVICE_NAME, namespaced_host - ); + let bitcoind_chain_coordinator_host = + get_service_url(&namespace, StacksDevnetService::BitcoindNode); stacks_conf.push_str(&format!( r#" @@ -552,7 +731,7 @@ impl StacksDevnetApiK8sManager { include_data_events = true events_keys = ["*"] "#, - bitcoind_chain_coordinator_host, CHAIN_COORDINATOR_INGESTION_PORT + bitcoind_chain_coordinator_host, chain_coordinator_ingestion_port )); // stacks_conf.push_str(&format!( @@ -585,8 +764,8 @@ impl StacksDevnetApiK8sManager { bitcoind_chain_coordinator_host, config.bitcoin_node_username, config.bitcoin_node_password, - CHAIN_COORDINATOR_INGESTION_PORT, - BITCOIND_P2P_PORT + chain_coordinator_ingestion_port, + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::P2P).unwrap() )); stacks_conf.push_str(&format!( @@ -615,15 +794,16 @@ impl StacksDevnetApiK8sManager { }; self.deploy_configmap( - Template::StacksNodeConfigmap, + StacksDevnetConfigmap::StacksNode, &namespace, Some(vec![("Stacks.toml", &stacks_conf)]), ) .await?; - self.deploy_pod(Template::StacksNodePod, &namespace).await?; + self.deploy_pod(StacksDevnetPod::StacksNode, &namespace) + .await?; - self.deploy_service(Template::StacksNodeService, namespace) + self.deploy_service(StacksDevnetService::StacksNode, namespace) .await?; Ok(()) @@ -636,27 +816,31 @@ impl StacksDevnetApiK8sManager { ("POSTGRES_DB", "stacks_api"), ]); self.deploy_configmap( - Template::StacksApiPostgresConfigmap, + StacksDevnetConfigmap::StacksApiPostgres, &namespace, Some(stacks_api_pg_env), ) .await?; // configmap env vars for api conatainer - let namespaced_host = format!("{}.svc.cluster.local", &namespace); - let stacks_node_host = format!("{}.{}", &STACKS_NODE_SERVICE_NAME, namespaced_host); - let rpc_port = STACKS_NODE_RPC_PORT.to_string(); + let stacks_node_host = get_service_url(&namespace, StacksDevnetService::StacksNode); + let rpc_port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); + let api_port = get_service_port(StacksDevnetService::StacksApi, ServicePort::API).unwrap(); + let event_port = + get_service_port(StacksDevnetService::StacksNode, ServicePort::Event).unwrap(); + let db_port = + get_service_port(StacksDevnetService::StacksNode, ServicePort::Event).unwrap(); let stacks_api_env = Vec::from([ ("STACKS_CORE_RPC_HOST", &stacks_node_host[..]), ("STACKS_BLOCKCHAIN_API_DB", "pg"), ("STACKS_CORE_RPC_PORT", &rpc_port), - ("STACKS_BLOCKCHAIN_API_PORT", "3999"), + ("STACKS_BLOCKCHAIN_API_PORT", &api_port), ("STACKS_BLOCKCHAIN_API_HOST", "0.0.0.0"), - ("STACKS_CORE_EVENT_PORT", "3700"), + ("STACKS_CORE_EVENT_PORT", &event_port), ("STACKS_CORE_EVENT_HOST", "0.0.0.0"), ("STACKS_API_ENABLE_FT_METADATA", "1"), ("PG_HOST", "0.0.0.0"), - ("PG_PORT", "5432"), + ("PG_PORT", &db_port), ("PG_USER", "postgres"), ("PG_PASSWORD", "postgres"), ("PG_DATABASE", "stacks_api"), @@ -666,17 +850,19 @@ impl StacksDevnetApiK8sManager { ("STACKS_API_LOG_LEVEL", "debug"), ]); self.deploy_configmap( - Template::StacksApiConfigmap, + StacksDevnetConfigmap::StacksApi, &namespace, Some(stacks_api_env), ) .await?; - self.deploy_pvc(Template::StacksApiPvc, &namespace).await?; + self.deploy_pvc(StacksDevnetPvc::StacksApi, &namespace) + .await?; - self.deploy_pod(Template::StacksApiPod, &namespace).await?; + self.deploy_pod(StacksDevnetPod::StacksApi, &namespace) + .await?; - self.deploy_service(Template::StacksApiService, &namespace) + self.deploy_service(StacksDevnetService::StacksApi, &namespace) .await?; Ok(()) @@ -726,11 +912,11 @@ impl StacksDevnetApiK8sManager { } } - fn get_resource_from_file(&self, template: Template) -> Result + fn get_resource_from_file(&self, template: StacksDevnetResource) -> Result where K: DeserializeOwned, { - let template_str = get_yaml_from_filename(template); + let template_str = get_yaml_from_resource(template); match serde_yaml::from_str(template_str) { Ok(resource) => Ok(resource), diff --git a/src/main.rs b/src/main.rs index 02a946c..2b47720 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ use hiro_system_kit::slog; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client, Method, Request, Response, Server, StatusCode, Uri}; +use stacks_devnet_api::resources::service::{ + get_service_from_path_part, get_service_port, get_service_url, ServicePort, +}; use stacks_devnet_api::{Context, StacksDevnetApiK8sManager, StacksDevnetConfig}; use std::net::IpAddr; use std::str::FromStr; @@ -44,15 +47,9 @@ async fn main() { fn mutate_request_for_proxy( mut request: Request, - network: &str, + forward_url: &str, path_to_forward: &str, - proxy_data: ProxyData, ) -> Request { - let forward_url = format!( - "http://{}.{}.svc.cluster.local:{}", - proxy_data.destination_service, network, proxy_data.destination_port - ); - let query = match request.uri().query() { Some(query) => format!("?{}", query), None => String::new(), @@ -83,38 +80,6 @@ async fn proxy(request: Request, ctx: &Context) -> Result, } } -struct ProxyData { - destination_service: String, - destination_port: String, -} -fn get_proxy_data(proxy_path: &str) -> Option { - const BITCOIN_NODE_PATH: &str = "bitcoin-node"; - const STACKS_NODE_PATH: &str = "stacks-node"; - const STACKS_API_PATH: &str = "stacks-api"; - const BITCOIN_NODE_SERVICE: &str = "bitcoind-chain-coordinator-service"; - const STACKS_NODE_SERVICE: &str = "stacks-node-service"; - const STACKS_API_SERVICE: &str = "stacks-api-service"; - const BITCOIN_NODE_PORT: &str = "18443"; - const STACKS_NODE_PORT: &str = "20443"; - const STACKS_API_PORT: &str = "3999"; - - match proxy_path { - BITCOIN_NODE_PATH => Some(ProxyData { - destination_service: BITCOIN_NODE_SERVICE.into(), - destination_port: BITCOIN_NODE_PORT.into(), - }), - STACKS_NODE_PATH => Some(ProxyData { - destination_service: STACKS_NODE_SERVICE.into(), - destination_port: STACKS_NODE_PORT.into(), - }), - STACKS_API_PATH => Some(ProxyData { - destination_service: STACKS_API_SERVICE.into(), - destination_port: STACKS_API_PORT.into(), - }), - _ => None, - } -} - const API_PATH: &str = "/api/v1/"; #[derive(Default, PartialEq, Debug)] struct PathParts { @@ -273,10 +238,33 @@ async fn handle_request( ) .unwrap()), }, - &Method::GET => Ok(Response::builder() - .status(StatusCode::NOT_IMPLEMENTED) - .body(Body::empty()) - .unwrap()), + &Method::GET => match k8s_manager.get_devnet_info(&network).await { + Ok(devnet_info) => match serde_json::to_vec(&devnet_info) { + Ok(body) => Ok(Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(Body::from(body)) + .unwrap()), + Err(e) => { + let msg = format!( + "failed to form response body: NAMESPACE: {}, ERROR: {}", + &network, + e.to_string() + ); + ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::error!(logger, "{}", msg) + }); + Ok(Response::builder() + .status(StatusCode::from_u16(500).unwrap()) + .body(Body::try_from(msg).unwrap()) + .unwrap()) + } + }, + Err(e) => Ok(Response::builder() + .status(StatusCode::from_u16(e.code).unwrap()) + .body(Body::try_from(e.message).unwrap()) + .unwrap()), + }, _ => Ok(Response::builder() .status(StatusCode::METHOD_NOT_ALLOWED) .body(Body::empty()) @@ -292,11 +280,14 @@ async fn handle_request( } else { let remaining_path = path_parts.remainder.unwrap_or(String::new()); - let proxy_data = get_proxy_data(&subroute); - return match proxy_data { - Some(proxy_data) => { + let service = get_service_from_path_part(&subroute); + return match service { + Some(service) => { + let base_url = get_service_url(&network, service.clone()); + let port = get_service_port(service, ServicePort::RPC).unwrap(); + let forward_url = format!("{}:{}", base_url, port); let proxy_request = - mutate_request_for_proxy(request, &network, &remaining_path, proxy_data); + mutate_request_for_proxy(request, &forward_url, &remaining_path); proxy(proxy_request, &ctx).await } None => Ok(Response::builder() @@ -318,6 +309,7 @@ mod tests { use super::*; use hyper::body; use k8s_openapi::api::core::v1::Namespace; + use stacks_devnet_api::resources::service::{get_service_port, StacksDevnetService}; use tower_test::mock::{self, Handle}; async fn mock_k8s_handler(handle: &mut Handle, Response>) { @@ -612,14 +604,23 @@ mod tests { let network = path_parts.network.unwrap(); let subroute = path_parts.subroute.unwrap(); let remainder = path_parts.remainder.unwrap(); - let proxy_data = get_proxy_data(&subroute); + + let service = get_service_from_path_part(&subroute).unwrap(); + let forward_url = format!( + "{}:{}", + get_service_url(&network, service.clone()), + get_service_port(service, ServicePort::RPC).unwrap() + ); let request_builder = Request::builder().uri("/").method("POST"); let request: Request = request_builder.body(Body::empty()).unwrap(); - let request = mutate_request_for_proxy(request, &network, &remainder, proxy_data.unwrap()); + let request = mutate_request_for_proxy(request, &forward_url, &remainder); let actual_url = request.uri().to_string(); let expected = format!( - "http://stacks-node-service.{}.svc.cluster.local:20443/{}", - network, &remainder + "http://{}.{}.svc.cluster.local:{}/{}", + StacksDevnetService::StacksNode, + network, + get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(), + &remainder ); assert_eq!(actual_url, expected); } diff --git a/src/resources/configmap.rs b/src/resources/configmap.rs new file mode 100644 index 0000000..4658d28 --- /dev/null +++ b/src/resources/configmap.rs @@ -0,0 +1,31 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug)] +pub enum StacksDevnetConfigmap { + BitcoindNode, + StacksNode, + StacksApi, + StacksApiPostgres, + DeploymentPlan, + Devnet, + ProjectDir, + Namespace, + ProjectManifest, +} + +impl fmt::Display for StacksDevnetConfigmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetConfigmap::BitcoindNode => write!(f, "bitcoind-conf"), + StacksDevnetConfigmap::StacksNode => write!(f, "stacks-node-conf"), + StacksDevnetConfigmap::StacksApi => write!(f, "stacks-api-conf"), + StacksDevnetConfigmap::StacksApiPostgres => write!(f, "stacks-api-postgres-conf"), + StacksDevnetConfigmap::DeploymentPlan => write!(f, "deployment-plan-conf"), + StacksDevnetConfigmap::Devnet => write!(f, "devnet-conf"), + StacksDevnetConfigmap::ProjectDir => write!(f, "project-dir-conf"), + StacksDevnetConfigmap::Namespace => write!(f, "namespace-conf"), + StacksDevnetConfigmap::ProjectManifest => write!(f, "project-manifest-conf"), + } + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..4fb94e9 --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1,17 @@ +use self::{ + configmap::StacksDevnetConfigmap, pod::StacksDevnetPod, pvc::StacksDevnetPvc, + service::StacksDevnetService, +}; + +pub mod configmap; +pub mod pod; +pub mod pvc; +pub mod service; + +pub enum StacksDevnetResource { + Configmap(StacksDevnetConfigmap), + Pod(StacksDevnetPod), + Pvc(StacksDevnetPvc), + Service(StacksDevnetService), + Namespace, +} diff --git a/src/resources/pod.rs b/src/resources/pod.rs new file mode 100644 index 0000000..e04cab8 --- /dev/null +++ b/src/resources/pod.rs @@ -0,0 +1,19 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug)] +pub enum StacksDevnetPod { + BitcoindNode, + StacksNode, + StacksApi, +} + +impl fmt::Display for StacksDevnetPod { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetPod::BitcoindNode => write!(f, "bitcoind-chain-coordinator"), + StacksDevnetPod::StacksNode => write!(f, "stacks-node"), + StacksDevnetPod::StacksApi => write!(f, "stacks-api"), + } + } +} diff --git a/src/resources/pvc.rs b/src/resources/pvc.rs new file mode 100644 index 0000000..21ad26c --- /dev/null +++ b/src/resources/pvc.rs @@ -0,0 +1,15 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug)] +pub enum StacksDevnetPvc { + StacksApi, +} + +impl fmt::Display for StacksDevnetPvc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetPvc::StacksApi => write!(f, "stacks-api-pvc"), + } + } +} diff --git a/src/resources/service.rs b/src/resources/service.rs new file mode 100644 index 0000000..d37fef8 --- /dev/null +++ b/src/resources/service.rs @@ -0,0 +1,59 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug, Clone)] +pub enum StacksDevnetService { + BitcoindNode, + StacksNode, + StacksApi, +} + +pub enum ServicePort { + RPC, + P2P, + Ingestion, + Event, + API, + DB, +} + +impl fmt::Display for StacksDevnetService { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetService::BitcoindNode => write!(f, "bitcoind-chain-coordinator-service"), + StacksDevnetService::StacksNode => write!(f, "stacks-node-service"), + StacksDevnetService::StacksApi => write!(f, "stacks-api-service"), + } + } +} + +pub fn get_service_port(service: StacksDevnetService, port_type: ServicePort) -> Option { + match (service, port_type) { + (StacksDevnetService::BitcoindNode, ServicePort::RPC) => Some("18443".into()), + (StacksDevnetService::BitcoindNode, ServicePort::P2P) => Some("18444".into()), + (StacksDevnetService::BitcoindNode, ServicePort::Ingestion) => Some("20445".into()), + (StacksDevnetService::StacksNode, ServicePort::RPC) => Some("20443".into()), + (StacksDevnetService::StacksNode, ServicePort::P2P) => Some("20444".into()), + (StacksDevnetService::StacksApi, ServicePort::API) => Some("3999".into()), + (StacksDevnetService::StacksApi, ServicePort::Event) => Some("3700".into()), + (StacksDevnetService::StacksApi, ServicePort::DB) => Some("5432".into()), + (_, _) => None, + } +} + +pub fn get_service_url(namespace: &str, service: StacksDevnetService) -> String { + format!( + "http://{}.{}.svc.cluster.local", + service.to_string(), + namespace + ) +} + +pub fn get_service_from_path_part(path_part: &str) -> Option { + match path_part { + "bitcoin-node" => Some(StacksDevnetService::BitcoindNode), + "stacks-node" => Some(StacksDevnetService::StacksNode), + "stacks-api" => Some(StacksDevnetService::StacksApi), + _ => None, + } +} diff --git a/src/template_parser.rs b/src/template_parser.rs index dbf56ab..9e63384 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -1,65 +1,58 @@ -pub enum Template { - BitcoindChainCoordinatorPod, - BitcoindChainCoordinatorService, - BitcoindConfigmap, - ChainCoordinatorDeploymentPlanConfigmap, - ChainCoordinatorDevnetConfigmap, - ChainCoordinatorNamespaceConfigmap, - ChainCoordinatorProjectDirConfigmap, - ChainCoordinatorProjectManifestConfigmap, - Namespace, - StacksApiConfigmap, - StacksApiPod, - StacksApiPostgresConfigmap, - StacksApiPvc, - StacksApiService, - StacksNodeConfigmap, - StacksNodePod, - StacksNodeService, -} +use crate::resources::{ + configmap::StacksDevnetConfigmap, pod::StacksDevnetPod, pvc::StacksDevnetPvc, + service::StacksDevnetService, StacksDevnetResource, +}; -pub fn get_yaml_from_filename(template_filename: Template) -> &'static str { - match template_filename { - Template::BitcoindChainCoordinatorPod => { +pub fn get_yaml_from_resource(resource: StacksDevnetResource) -> &'static str { + match resource { + StacksDevnetResource::Pod(StacksDevnetPod::BitcoindNode) => { include_str!("../templates/bitcoind-chain-coordinator-pod.template.yaml") } - Template::BitcoindChainCoordinatorService => { + StacksDevnetResource::Service(StacksDevnetService::BitcoindNode) => { include_str!("../templates/bitcoind-chain-coordinator-service.template.yaml") } - Template::BitcoindConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::BitcoindNode) => { include_str!("../templates/bitcoind-configmap.template.yaml") } - Template::ChainCoordinatorDeploymentPlanConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::DeploymentPlan) => { include_str!("../templates/chain-coord-deployment-plan-configmap.template.yaml") } - Template::ChainCoordinatorDevnetConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::Devnet) => { include_str!("../templates/chain-coord-devnet-configmap.template.yaml") } - Template::ChainCoordinatorNamespaceConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::Namespace) => { include_str!("../templates/chain-coord-namespace-configmap.template.yaml") } - Template::ChainCoordinatorProjectDirConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::ProjectDir) => { include_str!("../templates/chain-coord-project-dir-configmap.template.yaml") } - Template::ChainCoordinatorProjectManifestConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::ProjectManifest) => { include_str!("../templates/chain-coord-project-manifest-configmap.template.yaml") } - Template::Namespace => include_str!("../templates/namespace.template.yaml"), - Template::StacksApiConfigmap => { + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksApi) => { include_str!("../templates/stacks-api-configmap.template.yaml") } - Template::StacksApiPod => include_str!("../templates/stacks-api-pod.template.yaml"), - Template::StacksApiPostgresConfigmap => { + StacksDevnetResource::Pod(StacksDevnetPod::StacksApi) => { + include_str!("../templates/stacks-api-pod.template.yaml") + } + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksApiPostgres) => { include_str!("../templates/stacks-api-postgres-configmap.template.yaml") } - Template::StacksApiPvc => include_str!("../templates/stacks-api-pvc.template.yaml"), - Template::StacksApiService => include_str!("../templates/stacks-api-service.template.yaml"), - Template::StacksNodeConfigmap => { + StacksDevnetResource::Pvc(StacksDevnetPvc::StacksApi) => { + include_str!("../templates/stacks-api-pvc.template.yaml") + } + StacksDevnetResource::Service(StacksDevnetService::StacksApi) => { + include_str!("../templates/stacks-api-service.template.yaml") + } + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksNode) => { include_str!("../templates/stacks-node-configmap.template.yaml") } - Template::StacksNodePod => include_str!("../templates/stacks-node-pod.template.yaml"), - Template::StacksNodeService => { + StacksDevnetResource::Pod(StacksDevnetPod::StacksNode) => { + include_str!("../templates/stacks-node-pod.template.yaml") + } + StacksDevnetResource::Service(StacksDevnetService::StacksNode) => { include_str!("../templates/stacks-node-service.template.yaml") } + StacksDevnetResource::Namespace => include_str!("../templates/namespace.template.yaml"), } } diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index e0c4999..4cb8458 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -20,7 +20,7 @@ metadata: rules: - apiGroups: [""] # TODO: production version should not be able to create/delete namespaces (only get) - resources: ["pods", "services", "configmaps", "persistentvolumeclaims", "namespaces"] + resources: ["pods", "pods/status", "services", "configmaps", "persistentvolumeclaims", "namespaces"] verbs: ["get", "delete", "create"] --- From 67a3fd596a4a8d2e9e0b70bd6c0667d4f547cc3e Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 09:41:34 -0400 Subject: [PATCH 54/60] fix event/db ports for stacks api config --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a5e892e..c8b5b54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -827,9 +827,8 @@ impl StacksDevnetApiK8sManager { let rpc_port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); let api_port = get_service_port(StacksDevnetService::StacksApi, ServicePort::API).unwrap(); let event_port = - get_service_port(StacksDevnetService::StacksNode, ServicePort::Event).unwrap(); - let db_port = - get_service_port(StacksDevnetService::StacksNode, ServicePort::Event).unwrap(); + get_service_port(StacksDevnetService::StacksApi, ServicePort::Event).unwrap(); + let db_port = get_service_port(StacksDevnetService::StacksApi, ServicePort::DB).unwrap(); let stacks_api_env = Vec::from([ ("STACKS_CORE_RPC_HOST", &stacks_node_host[..]), ("STACKS_BLOCKCHAIN_API_DB", "pg"), From 9a1d94fc0ba6e45ea63d76cbaf0dd89babc4bc76 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:07:31 -0400 Subject: [PATCH 55/60] fix service url --- src/resources/service.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/resources/service.rs b/src/resources/service.rs index d37fef8..293e81e 100644 --- a/src/resources/service.rs +++ b/src/resources/service.rs @@ -42,11 +42,7 @@ pub fn get_service_port(service: StacksDevnetService, port_type: ServicePort) -> } pub fn get_service_url(namespace: &str, service: StacksDevnetService) -> String { - format!( - "http://{}.{}.svc.cluster.local", - service.to_string(), - namespace - ) + format!("{}.{}.svc.cluster.local", service.to_string(), namespace) } pub fn get_service_from_path_part(path_part: &str) -> Option { From 8a9e0a08128c18495002695cd419d66cef843d48 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:07:59 -0400 Subject: [PATCH 56/60] fix url to get v2 info --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c8b5b54..529c5b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,7 +293,7 @@ impl StacksDevnetApiK8sManager { let client = HttpClient::new(); let url = get_service_url(namespace, StacksDevnetService::StacksNode); let port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); - let url = format!("{}:{}/v2/info", url, port); + let url = format!("http://{}:{}/v2/info", url, port); let context = format!("NAMESPACE: {}", namespace); self.ctx.try_log(|logger: &hiro_system_kit::Logger| { From 1abe6bf8cbb1bf5af6f7b60897e326046f444cfb Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:27:09 -0400 Subject: [PATCH 57/60] add helper to get user facing port --- src/resources/service.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/resources/service.rs b/src/resources/service.rs index 293e81e..d734c1f 100644 --- a/src/resources/service.rs +++ b/src/resources/service.rs @@ -41,6 +41,15 @@ pub fn get_service_port(service: StacksDevnetService, port_type: ServicePort) -> } } +pub fn get_user_facing_port(service: StacksDevnetService) -> Option { + match service { + StacksDevnetService::BitcoindNode | StacksDevnetService::StacksNode => { + get_service_port(service, ServicePort::RPC) + } + StacksDevnetService::StacksApi => get_service_port(service, ServicePort::API), + } +} + pub fn get_service_url(namespace: &str, service: StacksDevnetService) -> String { format!("{}.{}.svc.cluster.local", service.to_string(), namespace) } From 454ba10b60fd330dd6e54e43afbac563898723d6 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:27:42 -0400 Subject: [PATCH 58/60] get user facing port for proxy requests --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2b47720..44a1817 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Client, Method, Request, Response, Server, StatusCode, Uri}; use stacks_devnet_api::resources::service::{ - get_service_from_path_part, get_service_port, get_service_url, ServicePort, + get_service_from_path_part, get_service_url, get_user_facing_port, }; use stacks_devnet_api::{Context, StacksDevnetApiK8sManager, StacksDevnetConfig}; use std::net::IpAddr; @@ -284,7 +284,7 @@ async fn handle_request( return match service { Some(service) => { let base_url = get_service_url(&network, service.clone()); - let port = get_service_port(service, ServicePort::RPC).unwrap(); + let port = get_user_facing_port(service).unwrap(); let forward_url = format!("{}:{}", base_url, port); let proxy_request = mutate_request_for_proxy(request, &forward_url, &remaining_path); From d1528bae5b50515110c86676d34c78c6c38c4bb5 Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:27:51 -0400 Subject: [PATCH 59/60] fix test --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 44a1817..d692400 100644 --- a/src/main.rs +++ b/src/main.rs @@ -309,7 +309,9 @@ mod tests { use super::*; use hyper::body; use k8s_openapi::api::core::v1::Namespace; - use stacks_devnet_api::resources::service::{get_service_port, StacksDevnetService}; + use stacks_devnet_api::resources::service::{ + get_service_port, ServicePort, StacksDevnetService, + }; use tower_test::mock::{self, Handle}; async fn mock_k8s_handler(handle: &mut Handle, Response>) { From a986ac7144ff44915366a9db5a4230a5ae1159de Mon Sep 17 00:00:00 2001 From: MicaiahReid Date: Thu, 22 Jun 2023 10:28:02 -0400 Subject: [PATCH 60/60] fix proxy url --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d692400..5ed48e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ fn mutate_request_for_proxy( }; *request.uri_mut() = { - let forward_uri = format!("{}/{}{}", forward_url, path_to_forward, query); + let forward_uri = format!("http://{}/{}{}", forward_url, path_to_forward, query); Uri::from_str(forward_uri.as_str()) } .unwrap();