diff --git a/golem-api-grpc/Cargo.toml b/golem-api-grpc/Cargo.toml index 72e0fd349..b5fb244bc 100644 --- a/golem-api-grpc/Cargo.toml +++ b/golem-api-grpc/Cargo.toml @@ -12,7 +12,7 @@ harness = false [dependencies] golem-wasm-ast = { path = "../wasm-ast", version = "0.0.0", default-features = false, features = ["protobuf"] } -golem-wasm-rpc = { path = "../wasm-rpc", version = "0.0.0", default-features = false, features = ["protobuf"] } +golem-wasm-rpc = { path = "../wasm-rpc", version = "0.0.0", default-features = false, features = ["host-bindings", "protobuf"] } async-trait = { workspace = true } bincode = { workspace = true } diff --git a/golem-api-grpc/proto/golem/component/component_metadata.proto b/golem-api-grpc/proto/golem/component/component_metadata.proto index 6bacdb9b3..d053c9cdc 100644 --- a/golem-api-grpc/proto/golem/component/component_metadata.proto +++ b/golem-api-grpc/proto/golem/component/component_metadata.proto @@ -5,9 +5,11 @@ package golem.component; import "golem/component/export.proto"; import "golem/component/producers.proto"; import "golem/component/linear_memory.proto"; +import "golem/component/dynamic_linked_instance.proto"; message ComponentMetadata { repeated Export exports = 1; repeated Producers producers = 2; repeated LinearMemory memories = 3; + map dynamic_linking = 4; } diff --git a/golem-api-grpc/proto/golem/component/dynamic_linked_instance.proto b/golem-api-grpc/proto/golem/component/dynamic_linked_instance.proto new file mode 100644 index 000000000..8cc6b4e8a --- /dev/null +++ b/golem-api-grpc/proto/golem/component/dynamic_linked_instance.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package golem.component; + +message DynamicLinkedInstance { + oneof dynamic_linked_instance { + DynamicLinkedWasmRpc wasm_rpc = 1; + } +} + +message DynamicLinkedWasmRpc { + string target_interface_name = 1; +} \ No newline at end of file diff --git a/golem-api-grpc/proto/golem/component/v1/component_service.proto b/golem-api-grpc/proto/golem/component/v1/component_service.proto index 0de929391..0feca9519 100644 --- a/golem-api-grpc/proto/golem/component/v1/component_service.proto +++ b/golem-api-grpc/proto/golem/component/v1/component_service.proto @@ -7,6 +7,7 @@ import public "golem/common/project_id.proto"; import public "golem/common/empty.proto"; import public "golem/component/component.proto"; import public "golem/component/component_type.proto"; +import public "golem/component/dynamic_linked_instance.proto"; import public "golem/component/v1/component_error.proto"; import public "golem/component/component_id.proto"; import public "golem/component/component_constraints.proto"; @@ -74,6 +75,7 @@ message CreateComponentRequestHeader { optional ComponentType componentType = 3; // All files need to be uploaded to the blob storage before providing them here repeated InitialComponentFile files = 4; + map dynamic_linking = 5; } message CreateComponentRequestChunk { @@ -148,6 +150,7 @@ message UpdateComponentRequestHeader { bool updateFiles = 3; // All files need to be uploaded to the blob storage before providing them here repeated InitialComponentFile files = 4; + map dynamic_linking = 5; } message UpdateComponentRequestChunk { diff --git a/golem-cli/src/oss/clients/component.rs b/golem-cli/src/oss/clients/component.rs index 25d850520..f1f3ae746 100644 --- a/golem-cli/src/oss/clients/component.rs +++ b/golem-cli/src/oss/clients/component.rs @@ -114,6 +114,7 @@ impl ComponentClient file, files_permissions, files_archive_file, + None, ) .await? } @@ -131,6 +132,7 @@ impl ComponentClient bytes, files_permissions, files_archive_file, + None, ) .await? } @@ -172,6 +174,7 @@ impl ComponentClient file, files_permissions, files_archive_file, + None, ) .await? } @@ -189,6 +192,7 @@ impl ComponentClient bytes, files_permissions, files_archive_file, + None, ) .await? } diff --git a/golem-common/src/model/component_metadata.rs b/golem-common/src/model/component_metadata.rs index c533baa89..123518e72 100644 --- a/golem-common/src/model/component_metadata.rs +++ b/golem-common/src/model/component_metadata.rs @@ -327,7 +327,8 @@ fn drop_from_constructor(constructor: &AnalysedFunction) -> AnalysedFunction { #[cfg(feature = "protobuf")] mod protobuf { use crate::model::component_metadata::{ - ComponentMetadata, LinearMemory, ProducerField, Producers, VersionedName, + ComponentMetadata, DynamicLinkedInstance, DynamicLinkedWasmRpc, LinearMemory, + ProducerField, Producers, VersionedName, }; use std::collections::HashMap; @@ -446,7 +447,64 @@ mod protobuf { .into_iter() .map(|memory| memory.into()) .collect(), + dynamic_linking: HashMap::from_iter( + value + .dynamic_linking + .into_iter() + .map(|(k, v)| (k, v.into())), + ), } } } + + impl From + for golem_api_grpc::proto::golem::component::DynamicLinkedInstance + { + fn from(value: DynamicLinkedInstance) -> Self { + match value { + DynamicLinkedInstance::WasmRpc(dynamic_linked_wasm_rpc) => Self { + dynamic_linked_instance: Some( + golem_api_grpc::proto::golem::component::dynamic_linked_instance::DynamicLinkedInstance::WasmRpc( + dynamic_linked_wasm_rpc.into())), + }, + } + } + } + + impl TryFrom + for DynamicLinkedInstance + { + type Error = String; + + fn try_from( + value: golem_api_grpc::proto::golem::component::DynamicLinkedInstance, + ) -> Result { + match value.dynamic_linked_instance { + Some(golem_api_grpc::proto::golem::component::dynamic_linked_instance::DynamicLinkedInstance::WasmRpc(dynamic_linked_wasm_rpc)) => Ok(Self::WasmRpc(dynamic_linked_wasm_rpc.try_into()?)), + None => Err("Missing dynamic_linked_instance".to_string()), + } + } + } + + impl From for golem_api_grpc::proto::golem::component::DynamicLinkedWasmRpc { + fn from(value: DynamicLinkedWasmRpc) -> Self { + Self { + target_interface_name: value.target_interface_name, + } + } + } + + impl TryFrom + for DynamicLinkedWasmRpc + { + type Error = String; + + fn try_from( + value: golem_api_grpc::proto::golem::component::DynamicLinkedWasmRpc, + ) -> Result { + Ok(Self { + target_interface_name: value.target_interface_name, + }) + } + } } diff --git a/golem-component-service-base/src/model/component.rs b/golem-component-service-base/src/model/component.rs index ed58cd292..10f2f57f5 100644 --- a/golem-component-service-base/src/model/component.rs +++ b/golem-component-service-base/src/model/component.rs @@ -15,12 +15,15 @@ use chrono::Utc; use golem_common::model::component::ComponentOwner; use golem_common::model::component_constraint::{FunctionConstraint, FunctionConstraintCollection}; -use golem_common::model::component_metadata::{ComponentMetadata, ComponentProcessingError}; +use golem_common::model::component_metadata::{ + ComponentMetadata, ComponentProcessingError, DynamicLinkedInstance, +}; use golem_common::model::plugin::PluginInstallation; use golem_common::model::InitialComponentFile; use golem_common::model::{ComponentFilePathWithPermissions, ComponentId, ComponentType}; use golem_service_base::model::{ComponentName, VersionedComponentId}; use rib::WorkerFunctionsInRib; +use std::collections::HashMap; use std::fmt::Debug; use std::time::SystemTime; use tempfile::NamedTempFile; @@ -48,9 +51,11 @@ impl Component { data: &[u8], files: Vec, installed_plugins: Vec, + dynamic_linking: HashMap, owner: Owner, ) -> Result, ComponentProcessingError> { - let metadata = ComponentMetadata::analyse_component(data)?; + let mut metadata = ComponentMetadata::analyse_component(data)?; + metadata.dynamic_linking = dynamic_linking; let versioned_component_id = VersionedComponentId { component_id: component_id.clone(), diff --git a/golem-component-service-base/src/model/mod.rs b/golem-component-service-base/src/model/mod.rs index 33338ddd2..4a5d0a9cd 100644 --- a/golem-component-service-base/src/model/mod.rs +++ b/golem-component-service-base/src/model/mod.rs @@ -14,11 +14,15 @@ mod component; +use bincode::{Decode, Encode}; pub use component::*; +use golem_common::model::component_metadata::DynamicLinkedInstance; use golem_common::model::{ComponentFilePathWithPermissionsList, ComponentType}; use golem_service_base::poem::TempFileUpload; -use poem_openapi::types::multipart::Upload; -use poem_openapi::Multipart; +use poem_openapi::types::multipart::{JsonField, Upload}; +use poem_openapi::{Multipart, Object}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; #[derive(Multipart)] #[oai(rename_all = "camelCase")] @@ -27,4 +31,13 @@ pub struct UpdatePayload { pub component: Upload, pub files_permissions: Option, pub files: Option, + pub dynamic_linking: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, Object)] +#[oai(rename_all = "camelCase")] +#[serde(rename_all = "camelCase")] +#[derive(Default)] +pub struct DynamicLinking { + pub dynamic_linking: HashMap, } diff --git a/golem-component-service-base/src/service/component.rs b/golem-component-service-base/src/service/component.rs index ac324e06d..e54485a27 100644 --- a/golem-component-service-base/src/service/component.rs +++ b/golem-component-service-base/src/service/component.rs @@ -28,7 +28,9 @@ use golem_api_grpc::proto::golem::common::{ErrorBody, ErrorsBody}; use golem_api_grpc::proto::golem::component::v1::component_error; use golem_common::model::component::ComponentOwner; use golem_common::model::component_constraint::FunctionConstraintCollection; -use golem_common::model::component_metadata::{ComponentMetadata, ComponentProcessingError}; +use golem_common::model::component_metadata::{ + ComponentMetadata, ComponentProcessingError, DynamicLinkedInstance, +}; use golem_common::model::plugin::{ ComponentPluginInstallationTarget, PluginInstallation, PluginInstallationCreation, PluginInstallationUpdate, PluginScope, PluginTypeSpecificDefinition, @@ -304,6 +306,7 @@ pub trait ComponentService: Debug { data: Vec, files: Option, installed_plugins: Vec, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError>; @@ -316,6 +319,7 @@ pub trait ComponentService: Debug { data: Vec, files: Vec, installed_plugins: Vec, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError>; @@ -325,6 +329,7 @@ pub trait ComponentService: Debug { data: Vec, component_type: Option, files: Option, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError>; @@ -336,6 +341,7 @@ pub trait ComponentService: Debug { component_type: Option, // None signals that files should be reused from the previous version files: Option>, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError>; @@ -639,6 +645,7 @@ impl ComponentServiceDefault, uploaded_files: Vec, installed_plugins: Vec, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { let component = Component::new( @@ -648,6 +655,7 @@ impl ComponentServiceDefault ComponentServiceDefault, component_type: Option, files: Option>, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { - let metadata = ComponentMetadata::analyse_component(&data) + let mut metadata = ComponentMetadata::analyse_component(&data) .map_err(ComponentError::ComponentProcessingError)?; + metadata.dynamic_linking = dynamic_linking; let constraints = self .component_repo @@ -1004,6 +1014,7 @@ impl ComponentService data: Vec, files: Option, installed_plugins: Vec, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { info!(owner = %owner, "Create component"); @@ -1027,6 +1038,7 @@ impl ComponentService data, uploaded_files, installed_plugins, + dynamic_linking, owner, ) .await @@ -1040,6 +1052,7 @@ impl ComponentService data: Vec, files: Vec, installed_plugins: Vec, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { info!(owner = %owner, "Create component"); @@ -1074,6 +1087,7 @@ impl ComponentService data, files, installed_plugins, + dynamic_linking, owner, ) .await @@ -1085,6 +1099,7 @@ impl ComponentService data: Vec, component_type: Option, files: Option, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { info!(owner = %owner, "Update component"); @@ -1097,8 +1112,15 @@ impl ComponentService None => None, }; - self.update_unchecked(component_id, data, component_type, uploaded_files, owner) - .await + self.update_unchecked( + component_id, + data, + component_type, + uploaded_files, + dynamic_linking, + owner, + ) + .await } async fn update_internal( @@ -1107,6 +1129,7 @@ impl ComponentService data: Vec, component_type: Option, files: Option>, + dynamic_linking: HashMap, owner: &Owner, ) -> Result, ComponentError> { info!(owner = %owner, "Update component"); @@ -1130,8 +1153,15 @@ impl ComponentService } } - self.update_unchecked(component_id, data, component_type, files, owner) - .await + self.update_unchecked( + component_id, + data, + component_type, + files, + dynamic_linking, + owner, + ) + .await } async fn download( diff --git a/golem-component-service-base/tests/all/repo/mod.rs b/golem-component-service-base/tests/all/repo/mod.rs index 597132b40..9416bd2c4 100644 --- a/golem-component-service-base/tests/all/repo/mod.rs +++ b/golem-component-service-base/tests/all/repo/mod.rs @@ -152,6 +152,7 @@ async fn test_repo_component_id_unique( &data, vec![], vec![], + HashMap::new(), owner1.clone(), ) .unwrap(); @@ -200,6 +201,7 @@ async fn test_repo_component_name_unique_in_namespace( &data, vec![], vec![], + HashMap::new(), owner1.clone(), ) .unwrap(); @@ -210,6 +212,7 @@ async fn test_repo_component_name_unique_in_namespace( &data, vec![], vec![], + HashMap::new(), owner2.clone(), ) .unwrap(); @@ -263,6 +266,7 @@ async fn test_repo_component_delete( &data, vec![], vec![], + HashMap::new(), DefaultComponentOwner, ) .unwrap(); @@ -322,6 +326,7 @@ async fn test_repo_component_constraints( &data, vec![], vec![], + HashMap::new(), owner1.clone(), ) .unwrap(); @@ -415,6 +420,7 @@ async fn test_default_plugin_repo( &get_component_data("shopping-cart"), vec![], vec![], + HashMap::new(), owner.clone(), ) .unwrap(); @@ -425,6 +431,7 @@ async fn test_default_plugin_repo( &get_component_data("shopping-cart"), vec![], vec![], + HashMap::new(), owner.clone(), ) .unwrap(); @@ -558,6 +565,7 @@ async fn test_default_component_plugin_installation( &get_component_data("shopping-cart"), vec![], vec![], + HashMap::new(), component_owner.clone(), ) .unwrap(); diff --git a/golem-component-service-base/tests/all/service/mod.rs b/golem-component-service-base/tests/all/service/mod.rs index 440f69ae0..ed1ddf8ec 100644 --- a/golem-component-service-base/tests/all/service/mod.rs +++ b/golem-component-service-base/tests/all/service/mod.rs @@ -45,7 +45,7 @@ use golem_service_base::storage::blob::fs::FileSystemBlobStorage; use golem_service_base::storage::blob::BlobStorage; use golem_wasm_ast::analysis::analysed_type::{str, u64}; use rib::RegistryKey; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::Arc; use uuid::Uuid; @@ -148,6 +148,7 @@ async fn test_services( get_component_data("shopping-cart"), None, vec![], + HashMap::new(), &DefaultComponentOwner, ) .await @@ -161,6 +162,7 @@ async fn test_services( get_component_data("rust-echo"), None, vec![], + HashMap::new(), &DefaultComponentOwner, ) .await @@ -246,6 +248,7 @@ async fn test_services( get_component_data("shopping-cart"), None, None, + HashMap::new(), &DefaultComponentOwner, ) .await @@ -433,6 +436,7 @@ async fn test_initial_component_file_upload( }], }), vec![], + HashMap::new(), &DefaultComponentOwner, ) .await @@ -486,6 +490,7 @@ async fn test_initial_component_file_data_sharing( files: vec![], }), vec![], + HashMap::new(), &DefaultComponentOwner, ) .await @@ -506,6 +511,7 @@ async fn test_initial_component_file_data_sharing( permissions: ComponentFilePermissions::ReadWrite, }], }), + HashMap::new(), &DefaultComponentOwner, ) .await @@ -544,6 +550,7 @@ async fn test_component_constraint_incompatible_updates( get_component_data("shopping-cart"), None, vec![], + HashMap::new(), &DefaultComponentOwner, ) .await @@ -576,6 +583,7 @@ async fn test_component_constraint_incompatible_updates( get_component_data("shopping-cart"), None, None, + HashMap::new(), &DefaultComponentOwner, ) .await diff --git a/golem-component-service/src/api/component.rs b/golem-component-service/src/api/component.rs index 81c928eaa..83617d6a1 100644 --- a/golem-component-service/src/api/component.rs +++ b/golem-component-service/src/api/component.rs @@ -23,7 +23,7 @@ use golem_common::model::ComponentFilePathWithPermissionsList; use golem_common::model::{ComponentId, ComponentType, Empty, PluginInstallationId}; use golem_common::recorded_http_api_request; use golem_component_service_base::model::{ - InitialComponentFilesArchiveAndPermissions, UpdatePayload, + DynamicLinking, InitialComponentFilesArchiveAndPermissions, UpdatePayload, }; use golem_component_service_base::service::component::ComponentService; use golem_component_service_base::service::plugin::{PluginError, PluginService}; @@ -33,8 +33,9 @@ use golem_service_base::poem::TempFileUpload; use poem::Body; use poem_openapi::param::{Path, Query}; use poem_openapi::payload::{Binary, Json}; -use poem_openapi::types::multipart::Upload; +use poem_openapi::types::multipart::{JsonField, Upload}; use poem_openapi::*; +use std::collections::HashMap; use std::sync::Arc; use tracing::Instrument; @@ -80,6 +81,11 @@ impl ComponentApi { data, files, vec![], + payload + .dynamic_linking + .unwrap_or_default() + .0 + .dynamic_linking, &DefaultComponentOwner, ) .instrument(record.span.clone()) @@ -117,6 +123,7 @@ impl ComponentApi { data, component_type.0, None, + HashMap::new(), &DefaultComponentOwner, ) .instrument(record.span.clone()) @@ -161,6 +168,11 @@ impl ComponentApi { data, payload.component_type, files, + payload + .dynamic_linking + .unwrap_or_default() + .0 + .dynamic_linking, &DefaultComponentOwner, ) .instrument(record.span.clone()) @@ -502,4 +514,5 @@ pub struct UploadPayload { component: Upload, files_permissions: Option, files: Option, + dynamic_linking: Option>, } diff --git a/golem-component-service/src/grpcapi/component.rs b/golem-component-service/src/grpcapi/component.rs index 00ca6082c..21206f4e4 100644 --- a/golem-component-service/src/grpcapi/component.rs +++ b/golem-component-service/src/grpcapi/component.rs @@ -13,6 +13,7 @@ // limitations under the License. use async_trait::async_trait; +use std::collections::HashMap; use std::pin::Pin; use std::sync::Arc; use tracing::Instrument; @@ -47,6 +48,7 @@ use golem_api_grpc::proto::golem::component::{Component, PluginInstallation}; use golem_common::grpc::{proto_component_id_string, proto_plugin_installation_id_string}; use golem_common::model::component::DefaultComponentOwner; use golem_common::model::component_constraint::FunctionConstraintCollection; +use golem_common::model::component_metadata::DynamicLinkedInstance; use golem_common::model::plugin::{ DefaultPluginOwner, DefaultPluginScope, PluginInstallationCreation, PluginInstallationUpdate, }; @@ -185,6 +187,16 @@ impl ComponentGrpcApi { .map(|f| f.clone().try_into()) .collect::, _>>() .map_err(|e: String| bad_request_error(&format!("Failed reading files: {e}")))?; + let dynamic_linking: HashMap = HashMap::from_iter( + request + .dynamic_linking + .iter() + .map(|(k, v)| v.clone().try_into().map(|v| (k.clone(), v))) + .collect::, _>>() + .map_err(|e: String| { + bad_request_error(&format!("Invalid dynamic linking information: {e}")) + })?, + ); let result = self .component_service .create_internal( @@ -194,6 +206,7 @@ impl ComponentGrpcApi { data, files, vec![], + dynamic_linking, &DefaultComponentOwner, ) .await?; @@ -227,9 +240,27 @@ impl ComponentGrpcApi { None }; + let dynamic_linking: HashMap = HashMap::from_iter( + request + .dynamic_linking + .into_iter() + .map(|(k, v)| v.try_into().map(|v| (k, v))) + .collect::, _>>() + .map_err(|e: String| { + bad_request_error(&format!("Invalid dynamic linking information: {e}")) + })?, + ); + let result = self .component_service - .update_internal(&id, data, component_type, files, &DefaultComponentOwner) + .update_internal( + &id, + data, + component_type, + files, + dynamic_linking, + &DefaultComponentOwner, + ) .await?; Ok(result.into()) } diff --git a/golem-test-framework/src/components/component_service/filesystem.rs b/golem-test-framework/src/components/component_service/filesystem.rs index 81ba04997..dc7781d77 100644 --- a/golem-test-framework/src/components/component_service/filesystem.rs +++ b/golem-test-framework/src/components/component_service/filesystem.rs @@ -16,6 +16,7 @@ use crate::components::component_service::{AddComponentError, ComponentService}; use async_trait::async_trait; use golem_api_grpc::proto::golem::component::v1::component_service_client::ComponentServiceClient; use golem_api_grpc::proto::golem::component::v1::plugin_service_client::PluginServiceClient; +use golem_common::model::component_metadata::DynamicLinkedInstance; use golem_common::model::plugin::PluginInstallation; use golem_common::model::{ component_metadata::{LinearMemory, RawComponentMetadata}, @@ -23,6 +24,7 @@ use golem_common::model::{ }; use golem_wasm_ast::analysis::AnalysedExport; use serde::Serialize; +use std::collections::HashMap; use std::{ os::unix::fs::MetadataExt, path::{Path, PathBuf}, @@ -51,6 +53,7 @@ impl FileSystemComponentService { component_type: ComponentType, files: &[InitialComponentFile], skip_analysis: bool, + dynamic_linking: &HashMap, ) -> Result { let target_dir = &self.root; debug!("Local component store: {target_dir:?}"); @@ -101,6 +104,7 @@ impl FileSystemComponentService { memories, exports, plugin_installations: vec![], + dynamic_linking: dynamic_linking.clone(), }; metadata .write_to_file(&target_dir.join(format!("{component_id}-{component_version}.json"))) @@ -166,6 +170,7 @@ impl ComponentService for FileSystemComponentService { component_type, &[], true, + &HashMap::new(), ) .await .expect("Failed to add component") @@ -177,8 +182,16 @@ impl ComponentService for FileSystemComponentService { component_id: &ComponentId, component_type: ComponentType, ) -> Result<(), AddComponentError> { - self.write_component_to_filesystem(local_path, component_id, 0, component_type, &[], false) - .await?; + self.write_component_to_filesystem( + local_path, + component_id, + 0, + component_type, + &[], + false, + &HashMap::new(), + ) + .await?; Ok(()) } @@ -194,6 +207,7 @@ impl ComponentService for FileSystemComponentService { component_type, &[], false, + &HashMap::new(), ) .await } @@ -211,6 +225,7 @@ impl ComponentService for FileSystemComponentService { component_type, &[], false, + &HashMap::new(), ) .await } @@ -221,6 +236,7 @@ impl ComponentService for FileSystemComponentService { _name: &str, component_type: ComponentType, files: &[InitialComponentFile], + dynamic_linking: &HashMap, ) -> Result { self.write_component_to_filesystem( local_path, @@ -229,6 +245,7 @@ impl ComponentService for FileSystemComponentService { component_type, files, false, + dynamic_linking, ) .await } @@ -261,6 +278,7 @@ impl ComponentService for FileSystemComponentService { component_type, &[], false, + &HashMap::new(), ) .await .expect("Failed to write component to filesystem"); @@ -316,6 +334,7 @@ pub struct ComponentMetadata { pub component_type: ComponentType, pub files: Vec, pub plugin_installations: Vec, + pub dynamic_linking: HashMap, } impl ComponentMetadata { diff --git a/golem-test-framework/src/components/component_service/mod.rs b/golem-test-framework/src/components/component_service/mod.rs index ee22438f3..20373cb31 100644 --- a/golem-test-framework/src/components/component_service/mod.rs +++ b/golem-test-framework/src/components/component_service/mod.rs @@ -40,6 +40,7 @@ use crate::components::rdb::Rdb; use crate::components::{wait_for_startup_grpc, EnvVarBuilder, GolemEnvVars}; use golem_api_grpc::proto::golem::component::v1::component_service_client::ComponentServiceClient; use golem_api_grpc::proto::golem::component::v1::plugin_service_client::PluginServiceClient; +use golem_common::model::component_metadata::DynamicLinkedInstance; use golem_common::model::plugin::{DefaultPluginOwner, DefaultPluginScope, PluginDefinition}; use golem_common::model::{ComponentId, ComponentType, InitialComponentFile, PluginInstallationId}; @@ -172,7 +173,7 @@ pub trait ComponentService { name: &str, component_type: ComponentType, ) -> Result { - self.add_component_with_files(local_path, name, component_type, &[]) + self.add_component_with_files(local_path, name, component_type, &[], &HashMap::new()) .await } @@ -182,6 +183,7 @@ pub trait ComponentService { name: &str, component_type: ComponentType, files: &[InitialComponentFile], + dynamic_linking: &HashMap, ) -> Result { let mut client = self.client().await; let mut file = File::open(local_path).await.map_err(|_| { @@ -199,6 +201,11 @@ pub trait ComponentService { component_name: name.to_string(), component_type: Some(component_type as i32), files, + dynamic_linking: HashMap::from_iter( + dynamic_linking + .iter() + .map(|(k, v)| (k.clone(), v.clone().into())), + ), })), }]; @@ -265,8 +272,14 @@ pub trait ComponentService { local_path: &Path, component_type: ComponentType, ) -> u64 { - self.update_component_with_files(component_id, local_path, component_type, &None) - .await + self.update_component_with_files( + component_id, + local_path, + component_type, + &None, + &HashMap::new(), + ) + .await } async fn update_component_with_files( @@ -275,6 +288,7 @@ pub trait ComponentService { local_path: &Path, component_type: ComponentType, files: &Option>, + dynamic_linking: &HashMap, ) -> u64 { let mut client = self.client().await; let mut file = File::open(local_path) @@ -299,6 +313,11 @@ pub trait ComponentService { component_type: Some(component_type as i32), update_files, files, + dynamic_linking: HashMap::from_iter( + dynamic_linking + .iter() + .map(|(k, v)| (k.clone(), v.clone().into())), + ), }, )), }]; diff --git a/golem-test-framework/src/dsl/mod.rs b/golem-test-framework/src/dsl/mod.rs index 05154e861..947ab8566 100644 --- a/golem-test-framework/src/dsl/mod.rs +++ b/golem-test-framework/src/dsl/mod.rs @@ -34,6 +34,7 @@ use golem_api_grpc::proto::golem::worker::v1::{ use golem_api_grpc::proto::golem::worker::{ log_event, InvokeParameters, LogEvent, StdErrLog, StdOutLog, UpdateMode, }; +use golem_common::model::component_metadata::DynamicLinkedInstance; use golem_common::model::oplog::{ OplogIndex, TimestampedUpdateDescription, UpdateDescription, WorkerResourceId, }; @@ -79,6 +80,17 @@ pub trait TestDsl { component_type: ComponentType, files: &[InitialComponentFile], ) -> ComponentId; + async fn store_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId; + async fn store_unique_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId; + async fn update_component(&self, component_id: &ComponentId, name: &str) -> ComponentVersion; async fn update_component_with_files( @@ -306,7 +318,13 @@ impl TestDsl for T { let uuid = Uuid::new_v4(); let unique_name = format!("{name}-{uuid}"); self.component_service() - .add_component_with_files(&source_path, &unique_name, component_type, files) + .add_component_with_files( + &source_path, + &unique_name, + component_type, + files, + &HashMap::new(), + ) .await .expect("Failed to store component") } @@ -319,11 +337,59 @@ impl TestDsl for T { ) -> ComponentId { let source_path = self.component_directory().join(format!("{name}.wasm")); self.component_service() - .add_component_with_files(&source_path, name, component_type, files) + .add_component_with_files(&source_path, name, component_type, files, &HashMap::new()) + .await + .expect("Failed to store component with id {component_id}") + } + + async fn store_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId { + let source_path = self.component_directory().join(format!("{name}.wasm")); + let dynamic_linking = HashMap::from_iter( + dynamic_linking + .iter() + .map(|(k, v)| (k.to_string(), v.clone())), + ); + self.component_service() + .add_component_with_files( + &source_path, + name, + ComponentType::Durable, + &[], + &dynamic_linking, + ) .await .expect("Failed to store component with id {component_id}") } + async fn store_unique_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId { + let source_path = self.component_directory().join(format!("{name}.wasm")); + let uuid = Uuid::new_v4(); + let unique_name = format!("{name}-{uuid}"); + let dynamic_linking = HashMap::from_iter( + dynamic_linking + .iter() + .map(|(k, v)| (k.to_string(), v.clone())), + ); + self.component_service() + .add_component_with_files( + &source_path, + &unique_name, + ComponentType::Durable, + &[], + &dynamic_linking, + ) + .await + .expect("Failed to store component") + } + async fn add_initial_component_file( &self, account_id: &AccountId, @@ -355,7 +421,13 @@ impl TestDsl for T { ) -> ComponentVersion { let source_path = self.component_directory().join(format!("{name}.wasm")); self.component_service() - .update_component_with_files(component_id, &source_path, ComponentType::Durable, files) + .update_component_with_files( + component_id, + &source_path, + ComponentType::Durable, + files, + &HashMap::new(), + ) .await } @@ -1436,6 +1508,17 @@ pub trait TestDslUnsafe { component_type: ComponentType, files: &[InitialComponentFile], ) -> ComponentId; + async fn store_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId; + async fn store_unique_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId; + async fn update_component(&self, component_id: &ComponentId, name: &str) -> ComponentVersion; async fn update_component_with_files( &self, @@ -1620,6 +1703,23 @@ impl TestDslUnsafe for T { ::store_component_with_files(self, name, component_type, files).await } + async fn store_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId { + ::store_component_with_dynamic_linking(self, name, dynamic_linking).await + } + + async fn store_unique_component_with_dynamic_linking( + &self, + name: &str, + dynamic_linking: &[(&'static str, DynamicLinkedInstance)], + ) -> ComponentId { + ::store_unique_component_with_dynamic_linking(self, name, dynamic_linking) + .await + } + async fn update_component(&self, component_id: &ComponentId, name: &str) -> ComponentVersion { ::update_component(self, component_id, name).await } diff --git a/golem-worker-executor-base/src/durable_host/dynamic_linking/mod.rs b/golem-worker-executor-base/src/durable_host/dynamic_linking/mod.rs index 57d6296fd..b53b839af 100644 --- a/golem-worker-executor-base/src/durable_host/dynamic_linking/mod.rs +++ b/golem-worker-executor-base/src/durable_host/dynamic_linking/mod.rs @@ -13,7 +13,7 @@ use golem_wasm_rpc::{HostWasmRpc, Uri, Value, WasmRpcEntry, WitValue}; use itertools::Itertools; use rib::{ParsedFunctionName, ParsedFunctionReference}; use std::collections::HashMap; -use tracing::debug; +use tracing::Instrument; use wasmtime::component::types::{ComponentItem, Field}; use wasmtime::component::{Component, Linker, Resource, ResourceType, Type, Val}; use wasmtime::{AsContextMut, Engine, StoreContextMut}; @@ -33,49 +33,17 @@ impl DynamicLinking ) -> anyhow::Result<()> { let mut root = linker.root(); - // TODO > - let mut component_metadata = component_metadata.clone(); - component_metadata.dynamic_linking.insert( - "auction:auction-stub/stub-auction".to_string(), - DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { - target_interface_name: "auction:auction/api".to_string(), - }), - ); - component_metadata.dynamic_linking.insert( - "rpc:counters-stub/stub-counters".to_string(), - DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { - target_interface_name: "rpc:counters/api".to_string(), - }), - ); - component_metadata.dynamic_linking.insert( - "rpc:ephemeral-stub/stub-ephemeral".to_string(), - DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { - target_interface_name: "rpc:ephemeral/api".to_string(), - }), - ); - // TODO < - let component_type = component.component_type(); for (name, item) in component_type.imports(engine) { let name = name.to_string(); - debug!("Import {name}: {item:?}"); match item { - ComponentItem::ComponentFunc(_) => { - debug!("MUST LINK COMPONENT FUNC {name}"); - } - ComponentItem::CoreFunc(_) => { - debug!("MUST LINK CORE FUNC {name}"); - } - ComponentItem::Module(_) => { - debug!("MUST LINK MODULE {name}"); - } - ComponentItem::Component(_) => { - debug!("MUST LINK COMPONENT {name}"); - } + ComponentItem::ComponentFunc(_) => {} + ComponentItem::CoreFunc(_) => {} + ComponentItem::Module(_) => {} + ComponentItem::Component(_) => {} ComponentItem::ComponentInstance(ref inst) => { match component_metadata.dynamic_linking.get(&name.to_string()) { Some(DynamicLinkedInstance::WasmRpc(rpc_metadata)) => { - debug!("NAME == {name}"); let mut instance = root.instance(&name)?; let mut resources: HashMap<(String, String), Vec> = HashMap::new(); @@ -84,7 +52,6 @@ impl DynamicLinking for (inner_name, inner_item) in inst.exports(engine) { let name = name.to_owned(); let inner_name = inner_name.to_owned(); - debug!("Instance {name} export {inner_name}: {inner_item:?}"); match inner_item { ComponentItem::ComponentFunc(fun) => { @@ -94,7 +61,7 @@ impl DynamicLinking let function_name = ParsedFunctionName::parse(format!( "{name}.{{{inner_name}}}" )) - .map_err(|err| anyhow!(err))?; // TODO: proper error + .map_err(|err| anyhow!(format!("Unexpected linking error: {name}.{{{inner_name}}} is not a valid function name: {err}")))?; if let Some(resource_name) = function_name.function.resource_name() @@ -117,12 +84,8 @@ impl DynamicLinking } ComponentItem::CoreFunc(_) => {} ComponentItem::Module(_) => {} - ComponentItem::Component(component) => { - debug!("MUST LINK COMPONENT {inner_name} {component:?}"); - } - ComponentItem::ComponentInstance(instance) => { - debug!("MUST LINK COMPONENT INSTANCE {inner_name} {instance:?}"); - } + ComponentItem::Component(_) => {} + ComponentItem::ComponentInstance(_) => {} ComponentItem::Type(_) => {} ComponentItem::Resource(_resource) => { resources.entry((name, inner_name)).or_default(); @@ -148,7 +111,6 @@ impl DynamicLinking match resource_type { Some(DynamicRpcResource::InvokeResult) => { - debug!("LINKING FUTURE INVOKE RESULT {resource_name}"); instance.resource( &resource_name, ResourceType::host::(), @@ -157,7 +119,6 @@ impl DynamicLinking } Some(DynamicRpcResource::Stub) | Some(DynamicRpcResource::ResourceStub) => { - debug!("LINKING RESOURCE {resource_name}"); let interface_name_clone = rpc_metadata.target_interface_name.clone(); let resource_name_clone = resource_name.clone(); @@ -169,20 +130,23 @@ impl DynamicLinking let interface_name = interface_name_clone.clone(); let resource_name = resource_name_clone.clone(); - Box::new(async move { - Self::drop_linked_resource( - store, - rep, - &interface_name, - &resource_name, - ) - .await - }) + Box::new( + async move { + Self::drop_linked_resource( + store, + rep, + &interface_name, + &resource_name, + ) + .await + } + .in_current_span(), + ) }, )?; } None => { - debug!("NOT LINKING RESOURCE {resource_name}"); + // Unsupported resource } } } @@ -196,42 +160,36 @@ impl DynamicLinking &resource_types, )?; if let Some(call_type) = call_type { - let name2 = name.clone(); - let inner_name2 = function.name.function.function_name(); instance.func_new_async( - // TODO: instrument async closure &function.name.function.function_name(), move |store, params, results| { - let name = name2.clone(); - let inner_name = inner_name2.clone(); let param_types = function.params.clone(); let result_types = function.results.clone(); let call_type = call_type.clone(); - Box::new(async move { - Self::dynamic_function_call( - store, - &name, - &inner_name, - params, - ¶m_types, - results, - &result_types, - &call_type, - ) - .await?; - // TODO: failures here must be somehow handled - Ok(()) - }) + Box::new( + async move { + Self::dynamic_function_call( + store, + params, + ¶m_types, + results, + &result_types, + &call_type, + ) + .await?; + Ok(()) + } + .in_current_span(), + ) }, )?; - debug!("LINKED {name} export {}", function.name); } else { - debug!("NO CALL TYPE FOR {name} export {}", function.name); + // Unsupported function } } } None => { - debug!("NO DYNAMIC LINKING INFORMATION FOR {name}"); + // Instance not marked for dynamic linking } } } @@ -248,8 +206,6 @@ impl DynamicLinking impl DurableWorkerCtx { async fn dynamic_function_call( mut store: impl AsContextMut + Send, - interface_name: &str, - function_name: &str, params: &[Val], param_types: &[Type], results: &mut [Val], @@ -257,12 +213,6 @@ impl DurableWorkerCtx anyhow::Result<()> { let mut store = store.as_context_mut(); - debug!( - "Instance {interface_name} export {function_name} called XXX {} params {} results", - params.len(), - results.len() - ); - match call_type { DynamicRpcCall::GlobalStubConstructor => { // Simple stub interface constructor @@ -284,8 +234,8 @@ impl DurableWorkerCtx { // Resource stub constructor @@ -310,7 +260,6 @@ impl DurableWorkerCtx DurableWorkerCtx { // Simple stub interface method - debug!( - "{function_name} handle={:?}, rest={:?}", - params[0], - params.iter().skip(1).collect::>() - ); - let handle = match params[0] { Val::Resource(handle) => handle, _ => return Err(anyhow!("Invalid handle parameter")), }; let handle: Resource = handle.try_into_resource(&mut store)?; - { - let mut wasi = store.data_mut().as_wasi_view(); - let entry = wasi.table().get(&handle)?; - let payload = entry.payload.downcast_ref::().unwrap(); - debug!("CALLING {function_name} ON {}", payload.remote_worker_id()); - } let result = Self::remote_invoke_and_wait( - stub_function_name, target_function_name, params, param_types, @@ -399,30 +335,17 @@ impl DurableWorkerCtx { // Fire-and-forget stub interface method - debug!( - "FNF {function_name} handle={:?}, rest={:?}", - params[0], - params.iter().skip(1).collect::>() - ); - let handle = match params[0] { Val::Resource(handle) => handle, _ => return Err(anyhow!("Invalid handle parameter")), }; let handle: Resource = handle.try_into_resource(&mut store)?; - { - let mut wasi = store.data_mut().as_wasi_view(); - let entry = wasi.table().get(&handle)?; - let payload = entry.payload.downcast_ref::().unwrap(); - debug!("CALLING {function_name} ON {}", payload.remote_worker_id()); - } Self::remote_invoke( - stub_function_name, target_function_name, params, param_types, @@ -432,30 +355,17 @@ impl DurableWorkerCtx { // Async stub interface method - debug!( - "ASYNC {function_name} handle={:?}, rest={:?}", - params[0], - params.iter().skip(1).collect::>() - ); - let handle = match params[0] { Val::Resource(handle) => handle, _ => return Err(anyhow!("Invalid handle parameter")), }; let handle: Resource = handle.try_into_resource(&mut store)?; - { - let mut wasi = store.data_mut().as_wasi_view(); - let entry = wasi.table().get(&handle)?; - let payload = entry.payload.downcast_ref::().unwrap(); - debug!("CALLING {function_name} ON {}", payload.remote_worker_id()); - } let result = Self::remote_async_invoke_and_await( - stub_function_name, target_function_name, params, param_types, @@ -537,8 +447,6 @@ impl DurableWorkerCtx().unwrap(); - debug!("DROPPING RESOURCE {payload:?}"); - matches!(payload, WasmRpcEntryPayload::Resource { .. }) }; if must_drop { @@ -553,9 +461,7 @@ impl DurableWorkerCtx DurableWorkerCtx DurableWorkerCtx DurableWorkerCtx DurableWorkerCtx DurableWorkerCtx { enum DynamicRpcCall { GlobalStubConstructor, ResourceStubConstructor { - stub_constructor_name: ParsedFunctionName, target_constructor_name: ParsedFunctionName, }, BlockingFunctionCall { - stub_function_name: ParsedFunctionName, target_function_name: ParsedFunctionName, }, FireAndForgetFunctionCall { - stub_function_name: ParsedFunctionName, target_function_name: ParsedFunctionName, }, AsyncFunctionCall { - stub_function_name: ParsedFunctionName, target_function_name: ParsedFunctionName, }, FutureInvokeResultSubscribe, @@ -782,7 +663,6 @@ impl DynamicRpcCall { }; Ok(Some(DynamicRpcCall::ResourceStubConstructor { - stub_constructor_name: stub_name.clone(), target_constructor_name, })) } @@ -829,22 +709,16 @@ impl DynamicRpcCall { if blocking { Ok(Some(DynamicRpcCall::BlockingFunctionCall { - stub_function_name: stub_name.clone(), + target_function_name, + })) + } else if !result_types.is_empty() { + Ok(Some(DynamicRpcCall::AsyncFunctionCall { target_function_name, })) } else { - debug!("ASYNC FUNCTION RESULT TYPES: {result_types:?}"); - if !result_types.is_empty() { - Ok(Some(DynamicRpcCall::AsyncFunctionCall { - stub_function_name: stub_name.clone(), - target_function_name, - })) - } else { - Ok(Some(DynamicRpcCall::FireAndForgetFunctionCall { - stub_function_name: stub_name.clone(), - target_function_name, - })) - } + Ok(Some(DynamicRpcCall::FireAndForgetFunctionCall { + target_function_name, + })) } } None => Ok(None), diff --git a/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs b/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs index 2b83145dc..d0ad432dd 100644 --- a/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs +++ b/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs @@ -96,22 +96,7 @@ impl HostWasmRpc for DurableWorkerCtx { let payload = entry.payload.downcast_ref::().unwrap(); let remote_worker_id = payload.remote_worker_id().clone(); - // TODO: remove redundancy - if let WasmRpcEntryPayload::Resource { - resource_uri, - resource_id, - .. - } = payload - { - function_params.insert( - 0, - Value::Handle { - uri: resource_uri.value.to_string(), - resource_id: *resource_id, - } - .into(), - ); - } + Self::add_self_parameter_if_needed(&mut function_params, payload); let current_idempotency_key = self .get_current_idempotency_key() @@ -242,22 +227,7 @@ impl HostWasmRpc for DurableWorkerCtx { let payload = entry.payload.downcast_ref::().unwrap(); let remote_worker_id = payload.remote_worker_id().clone(); - // TODO: remove redundancy - if let WasmRpcEntryPayload::Resource { - resource_uri, - resource_id, - .. - } = payload - { - function_params.insert( - 0, - Value::Handle { - uri: resource_uri.value.to_string(), - resource_id: *resource_id, - } - .into(), - ); - } + Self::add_self_parameter_if_needed(&mut function_params, payload); let current_idempotency_key = self .get_current_idempotency_key() @@ -349,22 +319,7 @@ impl HostWasmRpc for DurableWorkerCtx { let payload = entry.payload.downcast_ref::().unwrap(); let remote_worker_id = payload.remote_worker_id().clone(); - // TODO: remove redundancy - if let WasmRpcEntryPayload::Resource { - resource_uri, - resource_id, - .. - } = payload - { - function_params.insert( - 0, - Value::Handle { - uri: resource_uri.value.to_string(), - resource_id: *resource_id, - } - .into(), - ); - } + Self::add_self_parameter_if_needed(&mut function_params, payload); let current_idempotency_key = self .get_current_idempotency_key() @@ -467,6 +422,29 @@ impl HostWasmRpc for DurableWorkerCtx { } } +impl DurableWorkerCtx { + fn add_self_parameter_if_needed( + function_params: &mut Vec, + payload: &WasmRpcEntryPayload, + ) { + if let WasmRpcEntryPayload::Resource { + resource_uri, + resource_id, + .. + } = payload + { + function_params.insert( + 0, + Value::Handle { + uri: resource_uri.value.to_string(), + resource_id: *resource_id, + } + .into(), + ); + } + } +} + impl From for golem_wasm_rpc::RpcError { fn from(value: RpcError) -> Self { match value { diff --git a/golem-worker-executor-base/tests/rust_rpc_stubless.rs b/golem-worker-executor-base/tests/rust_rpc_stubless.rs index c6d5405ef..a899be9a0 100644 --- a/golem-worker-executor-base/tests/rust_rpc_stubless.rs +++ b/golem-worker-executor-base/tests/rust_rpc_stubless.rs @@ -17,6 +17,7 @@ use test_r::{inherit_test_dep, test}; use crate::common::{start, TestContext}; use crate::{LastUniqueId, Tracing, WorkerExecutorTestDependencies}; use assert2::check; +use golem_common::model::component_metadata::{DynamicLinkedInstance, DynamicLinkedWasmRpc}; use golem_test_framework::dsl::{worker_error_message, TestDslUnsafe}; use golem_wasm_rpc::Value; use std::collections::HashMap; @@ -37,7 +38,17 @@ async fn auction_example_1( let context = TestContext::new(last_unique_id); let executor = start(deps, &context).await.unwrap(); - let registry_component_id = executor.store_component("auction_registry").await; + let registry_component_id = executor + .store_component_with_dynamic_linking( + "auction_registry", + &[( + "auction:auction-stub/stub-auction", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "auction:auction/api".to_string(), + }), + )], + ) + .await; let auction_component_id = executor.store_component("auction").await; let mut env = HashMap::new(); @@ -106,7 +117,17 @@ async fn auction_example_2( let context = TestContext::new(last_unique_id); let executor = start(deps, &context).await.unwrap(); - let registry_component_id = executor.store_component("auction_registry").await; + let registry_component_id = executor + .store_component_with_dynamic_linking( + "auction_registry", + &[( + "auction:auction-stub/stub-auction", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "auction:auction/api".to_string(), + }), + )], + ) + .await; let auction_component_id = executor.store_component("auction").await; let mut env = HashMap::new(); @@ -176,7 +197,25 @@ async fn counter_resource_test_1( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -214,7 +253,25 @@ async fn counter_resource_test_2( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -249,7 +306,25 @@ async fn counter_resource_test_2_with_restart( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -288,7 +363,25 @@ async fn counter_resource_test_3( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -323,7 +416,25 @@ async fn counter_resource_test_3_with_restart( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -362,7 +473,25 @@ async fn context_inheritance( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -448,7 +577,25 @@ async fn counter_resource_test_5( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -489,7 +636,25 @@ async fn counter_resource_test_5_with_restart( // using store_unique_component to avoid collision with counter_resource_test_5 let counters_component_id = executor.store_unique_component("counters").await; - let caller_component_id = executor.store_unique_component("caller").await; + let caller_component_id = executor + .store_unique_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -546,7 +711,25 @@ async fn wasm_rpc_bug_32_test( let executor = start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component("counters").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -589,7 +772,17 @@ async fn error_message_invalid_uri( let context = TestContext::new(last_unique_id); let executor = start(deps, &context).await.unwrap(); - let registry_component_id = executor.store_component("auction_registry").await; + let registry_component_id = executor + .store_component_with_dynamic_linking( + "auction_registry", + &[( + "auction:auction-stub/stub-auction", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "auction:auction/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -644,7 +837,17 @@ async fn error_message_non_existing_target_component( let context = TestContext::new(last_unique_id); let executor = start(deps, &context).await.unwrap(); - let registry_component_id = executor.store_component("auction_registry").await; + let registry_component_id = executor + .store_component_with_dynamic_linking( + "auction_registry", + &[( + "auction:auction-stub/stub-auction", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "auction:auction/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -696,7 +899,25 @@ async fn ephemeral_worker_invocation_via_rpc1( let executor = start(deps, &context).await.unwrap(); let ephemeral_component_id = executor.store_ephemeral_component("ephemeral").await; - let caller_component_id = executor.store_component("caller").await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + "caller", + &[ + ( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + ), + ( + "rpc:ephemeral-stub/stub-ephemeral", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:ephemeral/api".to_string(), + }), + ), + ], + ) + .await; let mut env = HashMap::new(); env.insert( diff --git a/golem-worker-executor-base/tests/ts_rpc1_stubless.rs b/golem-worker-executor-base/tests/ts_rpc1_stubless.rs index ba101a25c..1767d4f7a 100644 --- a/golem-worker-executor-base/tests/ts_rpc1_stubless.rs +++ b/golem-worker-executor-base/tests/ts_rpc1_stubless.rs @@ -16,6 +16,7 @@ use test_r::{inherit_test_dep, test}; use crate::{common, LastUniqueId, Tracing, WorkerExecutorTestDependencies}; use assert2::check; +use golem_common::model::component_metadata::{DynamicLinkedInstance, DynamicLinkedWasmRpc}; use golem_test_framework::dsl::TestDslUnsafe; use golem_wasm_rpc::Value; use std::collections::HashMap; @@ -38,7 +39,17 @@ async fn counter_resource_test_1( let executor = common::start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component(COUNTER_COMPONENT_NAME).await; - let caller_component_id = executor.store_component(CALLER_COMPONENT_NAME).await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + CALLER_COMPONENT_NAME, + &[( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -73,7 +84,17 @@ async fn counter_resource_test_1_with_restart( let executor = common::start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component(COUNTER_COMPONENT_NAME).await; - let caller_component_id = executor.store_component(CALLER_COMPONENT_NAME).await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + CALLER_COMPONENT_NAME, + &[( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -112,7 +133,17 @@ async fn context_inheritance( let executor = common::start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component(COUNTER_COMPONENT_NAME).await; - let caller_component_id = executor.store_component(CALLER_COMPONENT_NAME).await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + CALLER_COMPONENT_NAME, + &[( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( diff --git a/golem-worker-executor-base/tests/ts_rpc2_stubless.rs b/golem-worker-executor-base/tests/ts_rpc2_stubless.rs index 375431876..431f53044 100644 --- a/golem-worker-executor-base/tests/ts_rpc2_stubless.rs +++ b/golem-worker-executor-base/tests/ts_rpc2_stubless.rs @@ -16,6 +16,7 @@ use test_r::{inherit_test_dep, test}; use crate::{common, LastUniqueId, Tracing, WorkerExecutorTestDependencies}; use assert2::check; +use golem_common::model::component_metadata::{DynamicLinkedInstance, DynamicLinkedWasmRpc}; use golem_test_framework::dsl::TestDslUnsafe; use golem_wasm_rpc::Value; use std::collections::HashMap; @@ -38,7 +39,17 @@ async fn counter_resource_test_2( let executor = common::start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component(COUNTER_COMPONENT_NAME).await; - let caller_component_id = executor.store_component(CALLER_COMPONENT_NAME).await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + CALLER_COMPONENT_NAME, + &[( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( @@ -73,7 +84,17 @@ async fn counter_resource_test_2_with_restart( let executor = common::start(deps, &context).await.unwrap(); let counters_component_id = executor.store_component(COUNTER_COMPONENT_NAME).await; - let caller_component_id = executor.store_component(CALLER_COMPONENT_NAME).await; + let caller_component_id = executor + .store_component_with_dynamic_linking( + CALLER_COMPONENT_NAME, + &[( + "rpc:counters-stub/stub-counters", + DynamicLinkedInstance::WasmRpc(DynamicLinkedWasmRpc { + target_interface_name: "rpc:counters/api".to_string(), + }), + )], + ) + .await; let mut env = HashMap::new(); env.insert( diff --git a/openapi/golem-service.yaml b/openapi/golem-service.yaml index 131764102..c59df6e20 100644 --- a/openapi/golem-service.yaml +++ b/openapi/golem-service.yaml @@ -2317,6 +2317,8 @@ paths: files: type: string format: binary + dynamicLinking: + $ref: '#/components/schemas/DynamicLinking' required: - name - component @@ -2472,6 +2474,8 @@ paths: files: type: string format: binary + dynamicLinking: + $ref: '#/components/schemas/DynamicLinking' required: - component required: true @@ -6404,6 +6408,15 @@ components: type: string required: - targetInterfaceName + DynamicLinking: + type: object + properties: + dynamicLinking: + type: object + additionalProperties: + $ref: '#/components/schemas/DynamicLinkedInstance' + required: + - dynamicLinking InitialComponentFile: type: object properties: diff --git a/wasm-rpc/Cargo.toml b/wasm-rpc/Cargo.toml index 964c68a9e..b6cec474b 100644 --- a/wasm-rpc/Cargo.toml +++ b/wasm-rpc/Cargo.toml @@ -45,10 +45,10 @@ cargo_metadata = "0.19.1" [features] default = ["host"] host-bindings = [ + "dep:async-trait", "wasmtime" ] host = [ - "dep:async-trait", "arbitrary", "bincode", "host-bindings",