diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2f606b6b..10a4ea56 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,8 +13,8 @@ jobs: fail-fast: false matrix: # TODO: Re-enable the multitenant and upgrades tests in followup to #247 - # e2e_test: [e2e_multiple_hosts, e2e_multitenant, e2e_upgrades] - e2e_test: [e2e_multiple_hosts, e2e_upgrades] + # test: [e2e_multiple_hosts, e2e_multitenant, e2e_upgrades] + test: [e2e_multiple_hosts, e2e_upgrades] steps: - uses: actions/checkout@v4 @@ -30,11 +30,18 @@ jobs: with: key: 'ubuntu-22.04-rust-cache' + # If the test uses a docker compose file, pre-emptively pull images used in docker compose + - name: Pull images for test ${{ matrix.test }} + shell: bash + run: | + export DOCKER_COMPOSE_FILE=tests/docker-compose-${{ matrix.test }}.yaml; + [[ -f "$DOCKER_COMPOSE_FILE" ]] && docker compose -f $DOCKER_COMPOSE_FILE pull; + # Run e2e tests in a matrix for efficiency - - name: Run tests ${{ matrix.e2e_test }} + - name: Run tests ${{ matrix.test }} id: test env: - WADM_E2E_TEST: ${{ matrix.e2e_test }} + WADM_E2E_TEST: ${{ matrix.test }} run: make test-individual-e2e # if the previous step fails, upload logs @@ -42,7 +49,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ failure() && steps.test.outcome == 'failure' }} with: - name: e2e-logs-${{ matrix.e2e_test }} + name: e2e-logs-${{ matrix.test }} path: ./tests/e2e_log/* # Be nice and only retain the logs for 7 days retention-days: 7 diff --git a/Cargo.lock b/Cargo.lock index 870a3480..9825ff83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3893,9 +3893,9 @@ dependencies = [ [[package]] name = "wasmcloud-control-interface" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c24228587ab5677ebd3693fafaab88049ec2f2e8664584e932950ffa0e11623" +checksum = "2ca8a9d9e20c30eef3a92cf5fad7392df703b7594fc7e794d49023106e921cb4" dependencies = [ "anyhow", "async-nats", diff --git a/Cargo.toml b/Cargo.toml index 76de0c99..d00f8d79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ uuid = "1" wadm = { version = "0.15.0", path = "./crates/wadm" } wadm-client = { version = "0.5.0", path = "./crates/wadm-client" } wadm-types = { version = "0.5.0", path = "./crates/wadm-types" } -wasmcloud-control-interface = { version = "2.0.0" } +wasmcloud-control-interface = { version = "2.2.0" } wasmcloud-secrets-types = "0.2.0" wit-bindgen-wrpc = { version = "0.3.7", default-features = false } diff --git a/crates/wadm/src/commands/mod.rs b/crates/wadm/src/commands/mod.rs index 93514a28..e8a4ac21 100644 --- a/crates/wadm/src/commands/mod.rs +++ b/crates/wadm/src/commands/mod.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeMap, HashMap}, + error::Error, hash::{Hash, Hasher}, }; @@ -235,18 +236,20 @@ pub struct PutLink { pub model_name: String, } -impl From for Link { - fn from(value: PutLink) -> Link { - Link { - source_id: value.source_id, - target: value.target, - name: value.name, - wit_namespace: value.wit_namespace, - wit_package: value.wit_package, - interfaces: value.interfaces, - source_config: value.source_config, - target_config: value.target_config, - } +impl TryFrom for Link { + type Error = Box; + + fn try_from(value: PutLink) -> Result { + Link::builder() + .source_id(&value.source_id) + .target(&value.target) + .name(&value.name) + .wit_namespace(&value.wit_namespace) + .wit_package(&value.wit_package) + .interfaces(value.interfaces) + .source_config(value.source_config) + .target_config(value.target_config) + .build() } } diff --git a/crates/wadm/src/scaler/spreadscaler/link.rs b/crates/wadm/src/scaler/spreadscaler/link.rs index 9421ae9a..c4f10b1a 100644 --- a/crates/wadm/src/scaler/spreadscaler/link.rs +++ b/crates/wadm/src/scaler/spreadscaler/link.rs @@ -122,9 +122,9 @@ where self.reconcile().await } Event::LinkdefSet(LinkdefSet { linkdef }) - if linkdef.source_id == self.config.source_id - && linkdef.target == self.config.target - && linkdef.name == self.config.name => + if linkdef.source_id() == self.config.source_id + && linkdef.target() == self.config.target + && linkdef.name() == self.config.name => { *self.status.write().await = StatusInfo::deployed(""); Ok(Vec::new()) @@ -141,9 +141,9 @@ where let (exists, _config_different) = linkdefs .into_iter() .find(|linkdef| { - &linkdef.source_id == source_id - && &linkdef.target == target - && linkdef.name == self.config.name + linkdef.source_id() == source_id + && linkdef.target() == target + && linkdef.name() == self.config.name }) .map(|linkdef| { ( @@ -151,11 +151,11 @@ where // TODO(#88): reverse compare too // Ensure all supplied configs (both source and target) are the same linkdef - .source_config + .source_config() .iter() .eq(self.config.source_config.iter()) && linkdef - .target_config + .target_config() .iter() .eq(self.config.target_config.iter()), ) @@ -430,26 +430,25 @@ mod test { let provider_ref = "provider_ref".to_string(); let provider_id = "provider".to_string(); - let linkdef = Link { - source_id: component_id.to_string(), - target: provider_id.to_string(), - wit_namespace: "namespace".to_string(), - wit_package: "package".to_string(), - interfaces: vec!["interface".to_string()], - name: "default".to_string(), - source_config: vec![], - target_config: vec![], - }; + let linkdef = Link::builder() + .source_id(&component_id) + .target(&provider_id) + .wit_namespace("namespace") + .wit_package("package") + .interfaces(vec!["interface".to_string()]) + .name("default") + .build() + .unwrap(); let scaler = LinkScaler::new( create_store(&lattice_id, &component_ref, &provider_ref).await, LinkScalerConfig { - source_id: linkdef.source_id.clone(), - target: linkdef.target.clone(), - wit_namespace: linkdef.wit_namespace.clone(), - wit_package: linkdef.wit_package.clone(), - wit_interfaces: linkdef.interfaces.clone(), - name: linkdef.name.clone(), + source_id: linkdef.source_id().to_string(), + target: linkdef.target().to_string(), + wit_namespace: linkdef.wit_namespace().to_string(), + wit_package: linkdef.wit_package().to_string(), + wit_interfaces: linkdef.interfaces().clone(), + name: linkdef.name().to_string(), source_config: vec![], target_config: vec![], lattice_id: lattice_id.clone(), @@ -583,17 +582,15 @@ mod test { let commands = link_scaler .handle_event(&Event::LinkdefSet(LinkdefSet { - linkdef: Link { + linkdef: Link::builder() // NOTE: contract, link, and provider id matches but the component is different - source_id: "nm0001772".to_string(), - target: "VASDASD".to_string(), - wit_namespace: "wasmcloud".to_string(), - wit_package: "httpserver".to_string(), - interfaces: vec![], - name: "default".to_string(), - source_config: vec![], - target_config: vec![], - }, + .source_id("nm0001772") + .target("VASDASD") + .wit_namespace("wasmcloud") + .wit_package("httpserver") + .name("default") + .build() + .unwrap(), })) .await .expect(""); diff --git a/crates/wadm/src/workers/command.rs b/crates/wadm/src/workers/command.rs index 91861a6d..d89aaaa8 100644 --- a/crates/wadm/src/workers/command.rs +++ b/crates/wadm/src/workers/command.rs @@ -74,7 +74,7 @@ impl Worker for CommandWorker { trace!(command = ?ld, "Handling put linkdef command"); // TODO(thomastaylor312): We should probably change ScopedMessage to allow us `pub` // access to the inner type so we don't have to clone, but no need to worry for now - self.client.put_link(ld.clone().into()).await + self.client.put_link(ld.clone().try_into()?).await } Command::DeleteLink(ld) => { trace!(command = ?ld, "Handling delete linkdef command"); diff --git a/tests/command_worker_integration.rs b/tests/command_worker_integration.rs index 251402a3..a7d2749c 100644 --- a/tests/command_worker_integration.rs +++ b/tests/command_worker_integration.rs @@ -213,10 +213,10 @@ async fn test_commands() { inventory .into_iter() .find(|ld| { - ld.source_id == HTTP_SERVER_COMPONENT_ID - && ld.target == HELLO_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "http" + ld.source_id() == HTTP_SERVER_COMPONENT_ID + && ld.target() == HELLO_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "http" }) .expect("Linkdef should exist"); @@ -249,10 +249,10 @@ async fn test_commands() { // We could have more than one link due to local testing, so search for the proper link assert!( !inventory.into_iter().any(|ld| { - ld.target == HELLO_COMPONENT_ID - && ld.source_id == HTTP_SERVER_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "http" + ld.target() == HELLO_COMPONENT_ID + && ld.source_id() == HTTP_SERVER_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "http" }), "Linkdef should be deleted" ); diff --git a/tests/docker-compose-e2e.yaml b/tests/docker-compose-e2e_multiple_hosts.yaml similarity index 100% rename from tests/docker-compose-e2e.yaml rename to tests/docker-compose-e2e_multiple_hosts.yaml diff --git a/tests/docker-compose-e2e-multitenant.yaml b/tests/docker-compose-e2e_multitenant.yaml similarity index 100% rename from tests/docker-compose-e2e-multitenant.yaml rename to tests/docker-compose-e2e_multitenant.yaml diff --git a/tests/docker-compose-e2e-upgrade.yaml b/tests/docker-compose-e2e_upgrades.yaml similarity index 100% rename from tests/docker-compose-e2e-upgrade.yaml rename to tests/docker-compose-e2e_upgrades.yaml diff --git a/tests/e2e.rs b/tests/e2e.rs index 476c2418..7d83c9bf 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -94,10 +94,18 @@ impl ClientInfo { .kill_on_drop(true) .spawn() .expect("Unable to watch docker logs"); + // Connect to NATS - let client = async_nats::connect("127.0.0.1:4222") - .await - .expect("Unable to connect to nats"); + let client = tokio::time::timeout(Duration::from_secs(3), async { + loop { + if let Ok(client) = async_nats::connect("127.0.0.1:4222").await { + return client; + } + tokio::time::sleep(Duration::from_millis(250)).await; + } + }) + .await + .expect("timed out while creating NATS client"); ClientInfo { client, diff --git a/tests/e2e_multiple_hosts.rs b/tests/e2e_multiple_hosts.rs index b3d5af71..6cc082d2 100644 --- a/tests/e2e_multiple_hosts.rs +++ b/tests/e2e_multiple_hosts.rs @@ -20,7 +20,7 @@ use crate::{ }; const MANIFESTS_PATH: &str = "tests/fixtures/manifests"; -const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e.yaml"; +const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e_multiple_hosts.yaml"; const BLOBSTORE_FS_IMAGE_REF: &str = "ghcr.io/wasmcloud/blobstore-fs:0.6.0"; const BLOBSTORE_FS_PROVIDER_ID: &str = "fileserver"; const BLOBBY_IMAGE_REF: &str = "ghcr.io/wasmcloud/components/blobby-rust:0.4.0"; @@ -152,12 +152,12 @@ async fn test_no_requirements(client_info: &ClientInfo) { .context("Should have links")?; if !links.iter().any(|ld| { - ld.source_id == HTTP_SERVER_COMPONENT_ID - && ld.target == HELLO_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "http" - && ld.interfaces == vec!["incoming-handler"] - && ld.name == "default" + ld.source_id() == HTTP_SERVER_COMPONENT_ID + && ld.target() == HELLO_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "http" + && ld.name() == "default" + && *ld.interfaces() == vec!["incoming-handler"] }) { anyhow::bail!( "Link between http provider and hello component should exist: {:#?}", @@ -366,12 +366,12 @@ async fn test_complex_app(client_info: &ClientInfo) { .context("Should have links")?; if !links.iter().any(|ld| { - ld.source_id == HTTP_SERVER_COMPONENT_ID - && ld.target == BLOBBY_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "http" - && ld.interfaces == vec!["incoming-handler"] - && ld.name == "default" + ld.source_id() == HTTP_SERVER_COMPONENT_ID + && ld.target() == BLOBBY_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "http" + && ld.name() == "default" + && *ld.interfaces() == vec!["incoming-handler"] }) { anyhow::bail!( "Link between blobby component and http provider should exist: {:#?}", @@ -380,12 +380,12 @@ async fn test_complex_app(client_info: &ClientInfo) { } if !links.iter().any(|ld| { - ld.source_id == BLOBBY_COMPONENT_ID - && ld.target == BLOBSTORE_FS_PROVIDER_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "blobstore" - && ld.interfaces == vec!["blobstore"] - && ld.name == "default" + ld.source_id() == BLOBBY_COMPONENT_ID + && ld.target() == BLOBSTORE_FS_PROVIDER_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "blobstore" + && ld.name() == "default" + && *ld.interfaces() == vec!["blobstore"] }) { anyhow::bail!( "Link between blobby component and blobstore-fs provider should exist: {:#?}", diff --git a/tests/e2e_multitenant.rs b/tests/e2e_multitenant.rs index cf22bcc4..154c5266 100644 --- a/tests/e2e_multitenant.rs +++ b/tests/e2e_multitenant.rs @@ -15,7 +15,7 @@ // use crate::e2e::check_status; // const MANIFESTS_PATH: &str = "tests/fixtures/manifests"; -// const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e-multitenant.yaml"; +// const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e_multitenant.yaml"; // const MESSAGE_PUB_ACTOR_ID: &str = "MC3QONHYH3FY4KYFCOSVJWIDJG4WA2PVD6FHKR7FFT457GVUTZJYR2TJ"; // const NATS_PROVIDER_ID: &str = "VADNMSIML2XGO2X4TPIONTIC55R2UUQGPPDZPAVSC2QD7E76CR77SPW7"; diff --git a/tests/e2e_upgrades.rs b/tests/e2e_upgrades.rs index 2ff9b0cd..cc546ce8 100644 --- a/tests/e2e_upgrades.rs +++ b/tests/e2e_upgrades.rs @@ -17,7 +17,7 @@ use e2e::{ use helpers::{HELLO_COMPONENT_ID, HELLO_IMAGE_REF, HTTP_SERVER_COMPONENT_ID}; const MANIFESTS_PATH: &str = "tests/fixtures/manifests"; -const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e-upgrade.yaml"; +const DOCKER_COMPOSE_FILE: &str = "tests/docker-compose-e2e_upgrades.yaml"; const KEYVALUE_REDIS_COMPONENT_ID: &str = "keyvalue_redis"; const DOG_FETCHER_GENERATED_ID: &str = "dog_fetcher"; const KVCOUNTER_GENERATED_ID: &str = "kvcounter"; @@ -142,15 +142,15 @@ async fn test_upgrade(client_info: &ClientInfo) { let http_link = links .iter() .find(|link| { - link.target == HELLO_COMPONENT_ID - && link.source_id == HTTP_SERVER_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "http" + link.target() == HELLO_COMPONENT_ID + && link.source_id() == HTTP_SERVER_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "http" }) .context("Should have http link with hello")?; if let Err(e) = check_config( client_info.ctl_client("default"), - &http_link.source_config[0], + &http_link.source_config()[0], &HashMap::from_iter([("address".to_string(), "0.0.0.0:8080".to_string())]), ) .await @@ -164,15 +164,15 @@ async fn test_upgrade(client_info: &ClientInfo) { let dog_link = links .iter() .find(|link| { - link.target == generated_dogfetcher_id - && link.source_id == HTTP_SERVER_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "http" + link.target() == generated_dogfetcher_id + && link.source_id() == HTTP_SERVER_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "http" }) .context("Should have http link with dog-fetcher")?; if let Err(e) = check_config( client_info.ctl_client("default"), - &dog_link.source_config[0], + &dog_link.source_config()[0], &HashMap::from_iter([("address".to_string(), "0.0.0.0:8081".to_string())]), ) .await @@ -186,15 +186,15 @@ async fn test_upgrade(client_info: &ClientInfo) { let kv_link = links .iter() .find(|link| { - link.source_id == generated_kvcounter_id - && link.target == KEYVALUE_REDIS_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "keyvalue" + link.source_id() == generated_kvcounter_id + && link.target() == KEYVALUE_REDIS_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "keyvalue" }) .context("Should have redis link with kvcounter")?; if let Err(e) = check_config( client_info.ctl_client("default"), - &kv_link.target_config[0], + &kv_link.target_config()[0], &HashMap::from_iter([("URL".to_string(), "redis://127.0.0.1:6379".to_string())]), ) .await @@ -311,16 +311,16 @@ async fn test_upgrade(client_info: &ClientInfo) { let http_link = links .iter() .find(|link| { - link.target == HELLO_COMPONENT_ID - && link.source_id == HTTP_SERVER_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "http" + link.target() == HELLO_COMPONENT_ID + && link.source_id() == HTTP_SERVER_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "http" }) .ok_or_else(|| anyhow::anyhow!("Should have http link with hello"))?; if let Err(e) = check_config( client_info.ctl_client("default"), - &http_link.source_config[0], + &http_link.source_config()[0], &HashMap::from([("address".to_string(), "0.0.0.0:8082".to_string())]), ) .await @@ -332,10 +332,10 @@ async fn test_upgrade(client_info: &ClientInfo) { } if links.iter().any(|ld| { - ld.source_id == generated_kvcounter_id - && ld.target == KEYVALUE_REDIS_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "keyvalue" + ld.source_id() == generated_kvcounter_id + && ld.target() == KEYVALUE_REDIS_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "keyvalue" }) { anyhow::bail!( "Link between kvcounter component and redis provider should not exist: {:#?}", @@ -429,16 +429,16 @@ async fn test_upgrade(client_info: &ClientInfo) { let http_link = links .iter() .find(|link| { - link.target == HELLO_COMPONENT_ID - && link.source_id == HTTP_SERVER_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "http" + link.target() == HELLO_COMPONENT_ID + && link.source_id() == HTTP_SERVER_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "http" }) .ok_or_else(|| anyhow::anyhow!("Should have http link with hello"))?; if let Err(e) = check_config( client_info.ctl_client("default"), - &http_link.source_config[0], + &http_link.source_config()[0], &HashMap::from([("address".to_string(), "0.0.0.0:8080".to_string())]), ) .await @@ -452,16 +452,16 @@ async fn test_upgrade(client_info: &ClientInfo) { let dog_link = links .iter() .find(|link| { - link.target == generated_dogfetcher_id - && link.source_id == HTTP_SERVER_COMPONENT_ID - && link.wit_namespace == "wasi" - && link.wit_package == "http" + link.target() == generated_dogfetcher_id + && link.source_id() == HTTP_SERVER_COMPONENT_ID + && link.wit_namespace() == "wasi" + && link.wit_package() == "http" }) .ok_or_else(|| anyhow::anyhow!("Should have dog link with hello"))?; if let Err(e) = check_config( client_info.ctl_client("default"), - &dog_link.source_config[0], + &dog_link.source_config()[0], &HashMap::from([("address".to_string(), "0.0.0.0:8081".to_string())]), ) .await @@ -473,10 +473,10 @@ async fn test_upgrade(client_info: &ClientInfo) { } if links.iter().any(|ld| { - ld.source_id == generated_kvcounter_id - && ld.target == KEYVALUE_REDIS_COMPONENT_ID - && ld.wit_namespace == "wasi" - && ld.wit_package == "keyvalue" + ld.source_id() == generated_kvcounter_id + && ld.target() == KEYVALUE_REDIS_COMPONENT_ID + && ld.wit_namespace() == "wasi" + && ld.wit_package() == "keyvalue" }) { anyhow::bail!( "Link between kvcounter component and redis provider should not exist: {:#?}", diff --git a/tests/event_consumer_integration.rs b/tests/event_consumer_integration.rs index 5e911792..e3bc6de0 100644 --- a/tests/event_consumer_integration.rs +++ b/tests/event_consumer_integration.rs @@ -161,14 +161,14 @@ async fn test_event_stream() -> Result<()> { let mut evt = wait_for_event(&mut stream, LINK_OPERATION_TIMEOUT_DURATION).await; if let Event::LinkdefSet(event) = evt.as_ref() { assert_eq!( - event.linkdef.source_id, HELLO_COMPONENT_ID, + event.linkdef.source_id(), HELLO_COMPONENT_ID, "Expected to get a linkdef event for the right component and provider, got component ID: {}", - event.linkdef.source_id, + event.linkdef.source_id(), ); assert_eq!( - event.linkdef.target, HTTP_SERVER_COMPONENT_ID, + event.linkdef.target(), HTTP_SERVER_COMPONENT_ID, "Expected to get a linkdef event for the right component and provider, got provider ID: {}", - event.linkdef.target, + event.linkdef.target(), ); } else { panic!("Event wasn't an link set event");