diff --git a/Cargo.lock b/Cargo.lock index 408a4055e8..daa7ef510d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3546,6 +3546,7 @@ dependencies = [ "async-trait", "bincode", "bytes 1.7.1", + "chrono", "criterion", "fastrand 2.1.0", "golem-api-grpc", @@ -3554,6 +3555,7 @@ dependencies = [ "golem-wasm-ast", "http 0.2.12", "prost", + "prost-types", "serde 1.0.207", "sqlx", "tap", @@ -3649,6 +3651,7 @@ dependencies = [ "num-traits 0.2.19", "poem-openapi", "proptest", + "prost-types", "rand", "serde 1.0.207", "serde_json", diff --git a/golem-api-grpc/proto/golem/component/component.proto b/golem-api-grpc/proto/golem/component/component.proto index 9dd5c06088..ab899b684c 100644 --- a/golem-api-grpc/proto/golem/component/component.proto +++ b/golem-api-grpc/proto/golem/component/component.proto @@ -5,6 +5,7 @@ package golem.component; import "golem/common/project_id.proto"; import "golem/component/component_metadata.proto"; import "golem/component/versioned_component_id.proto"; +import "google/protobuf/timestamp.proto"; message Component { VersionedComponentId versioned_component_id = 1; @@ -12,4 +13,5 @@ message Component { uint64 component_size = 5; ComponentMetadata metadata = 6; golem.common.ProjectId project_id = 7; + google.protobuf.Timestamp created_at = 8; } diff --git a/golem-cli/src/model/component.rs b/golem-cli/src/model/component.rs index 4a2ed9628f..40ad77d216 100644 --- a/golem-cli/src/model/component.rs +++ b/golem-cli/src/model/component.rs @@ -21,6 +21,7 @@ pub struct Component { pub component_size: u64, pub metadata: ComponentMetadata, pub project_id: Option, + pub created_at: Option>, } impl From for Component { @@ -30,6 +31,7 @@ impl From for Component { component_name, component_size, metadata, + created_at, } = value; Component { @@ -38,6 +40,7 @@ impl From for Component { component_size, metadata, project_id: None, + created_at, } } } diff --git a/golem-cli/src/model/invoke_result_view.rs b/golem-cli/src/model/invoke_result_view.rs index 6828696fb7..aee06053b4 100644 --- a/golem-cli/src/model/invoke_result_view.rs +++ b/golem-cli/src/model/invoke_result_view.rs @@ -97,6 +97,7 @@ impl InvokeResultView { #[cfg(test)] mod tests { + use chrono::Utc; use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; use golem_wasm_rpc::protobuf::TypeAnnotatedValue as RootTypeAnnotatedValue; use golem_wasm_rpc::protobuf::TypedTuple; @@ -156,6 +157,7 @@ mod tests { memories: vec![], }, project_id: None, + created_at: Some(Utc::now()), }; InvokeResultView::try_parse_or_json( diff --git a/golem-component-service-base/Cargo.toml b/golem-component-service-base/Cargo.toml index 46184f7e01..2912be0621 100644 --- a/golem-component-service-base/Cargo.toml +++ b/golem-component-service-base/Cargo.toml @@ -15,8 +15,10 @@ anyhow = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } bytes = { workspace = true } +chrono = { workspace = true } http_02 = { workspace = true } prost = { workspace = true } +prost-types = { workspace = true } serde = { workspace = true } sqlx = { workspace = true, features = [ "runtime-tokio", diff --git a/golem-component-service-base/src/model.rs b/golem-component-service-base/src/model.rs index 6a1f80aa3f..347db6549f 100644 --- a/golem-component-service-base/src/model.rs +++ b/golem-component-service-base/src/model.rs @@ -1,6 +1,7 @@ use golem_common::model::component_metadata::ComponentMetadata; use golem_service_base::model::{ComponentName, VersionedComponentId}; use serde::{Deserialize, Serialize}; +use std::time::SystemTime; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Component { @@ -9,6 +10,7 @@ pub struct Component { pub component_name: ComponentName, pub component_size: u64, pub metadata: ComponentMetadata, + pub created_at: chrono::DateTime, } impl Component { @@ -31,6 +33,7 @@ impl From> for golem_service_base::model::Compon component_name: value.component_name, component_size: value.component_size, metadata: value.metadata, + created_at: Some(value.created_at), } } } @@ -43,6 +46,9 @@ impl From> for golem_api_grpc::proto::golem::com component_size: value.component_size, metadata: Some(value.metadata.into()), project_id: None, + created_at: Some(prost_types::Timestamp::from(SystemTime::from( + value.created_at, + ))), } } } diff --git a/golem-component-service-base/src/repo/component.rs b/golem-component-service-base/src/repo/component.rs index 46a85eccea..1a4b331fca 100644 --- a/golem-component-service-base/src/repo/component.rs +++ b/golem-component-service-base/src/repo/component.rs @@ -35,6 +35,7 @@ pub struct ComponentRecord { pub size: i32, pub version: i64, pub metadata: Vec, + pub created_at: chrono::DateTime, } impl TryFrom for Component @@ -56,6 +57,7 @@ where component_size: value.size as u64, metadata, versioned_component_id, + created_at: value.created_at, }) } } @@ -84,6 +86,7 @@ where size: value.component_size as i32, version: value.versioned_component_id.version as i64, metadata: metadata.into(), + created_at: value.created_at, }) } } @@ -167,15 +170,16 @@ impl ComponentRepo for DbComponentRepo { sqlx::query( r#" INSERT INTO component_versions - (component_id, version, size, metadata) + (component_id, version, size, metadata, created_at) VALUES - ($1, $2, $3, $4) + ($1, $2, $3, $4, $5) "#, ) .bind(component.component_id) .bind(component.version) .bind(component.size) .bind(component.metadata.clone()) + .bind(component.created_at) .execute(&mut *transaction) .await?; @@ -193,7 +197,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 @@ -214,7 +219,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.namespace = $1 @@ -238,7 +244,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 @@ -264,7 +271,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 AND cv.version = $2 @@ -290,7 +298,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.namespace = $1 AND c.name = $2 @@ -384,15 +393,16 @@ impl ComponentRepo for DbComponentRepo { sqlx::query( r#" INSERT INTO component_versions - (component_id, version, size, metadata) + (component_id, version, size, metadata, created_at) VALUES - ($1, $2, $3, $4) + ($1, $2, $3, $4, $5) "#, ) .bind(component.component_id) .bind(component.version) .bind(component.size) .bind(component.metadata.clone()) + .bind(component.created_at) .execute(&mut *transaction) .await?; @@ -410,7 +420,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at::timestamptz AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 @@ -431,7 +442,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at::timestamptz AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.namespace = $1 @@ -455,7 +467,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at::timestamptz AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 @@ -481,7 +494,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at::timestamptz AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.component_id = $1 AND cv.version = $2 @@ -507,7 +521,8 @@ impl ComponentRepo for DbComponentRepo { c.component_id AS component_id, cv.version AS version, cv.size AS size, - cv.metadata AS metadata + cv.metadata AS metadata, + cv.created_at::timestamptz AS created_at FROM components c JOIN component_versions cv ON c.component_id = cv.component_id WHERE c.namespace = $1 AND c.name = $2 diff --git a/golem-component-service-base/src/service/component.rs b/golem-component-service-base/src/service/component.rs index 316185b890..673d2b3361 100644 --- a/golem-component-service-base/src/service/component.rs +++ b/golem-component-service-base/src/service/component.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use crate::service::component_compilation::ComponentCompilationService; use crate::service::component_processor::process_component; use async_trait::async_trait; +use chrono::Utc; use golem_common::model::component_metadata::ComponentProcessingError; use golem_common::model::ComponentId; use tap::TapFallible; @@ -82,6 +83,7 @@ where component_name: component_name.clone(), component_size: data.len() as u64, metadata, + created_at: Utc::now(), versioned_component_id, }) } @@ -235,7 +237,7 @@ where namespace: &Namespace, ) -> Result, ComponentError> { info!(namespace = %namespace, "Update component"); - + let created_at = Utc::now(); let metadata = process_component(&data).map_err(ComponentError::ComponentProcessingError)?; @@ -266,6 +268,7 @@ where let component = Component { component_size, metadata, + created_at, ..next_component }; let record = component @@ -607,6 +610,7 @@ impl ComponentService component_id: component_id.clone(), version: 0, }, + created_at: Utc::now(), }; Ok(fake_component) @@ -631,6 +635,7 @@ impl ComponentService component_id: component_id.clone(), version: 0, }, + created_at: Utc::now(), }; Ok(fake_component) diff --git a/golem-service-base/Cargo.toml b/golem-service-base/Cargo.toml index ad075a28d1..f90c2796cb 100644 --- a/golem-service-base/Cargo.toml +++ b/golem-service-base/Cargo.toml @@ -29,6 +29,7 @@ humantime-serde = { workspace = true } hyper = { workspace = true } num-traits = { workspace = true } poem-openapi = { workspace = true } +prost-types = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/golem-service-base/src/model.rs b/golem-service-base/src/model.rs index 96f1110148..298b5a2e14 100644 --- a/golem-service-base/src/model.rs +++ b/golem-service-base/src/model.rs @@ -20,6 +20,7 @@ use golem_common::model::{ use golem_wasm_rpc::protobuf::type_annotated_value::TypeAnnotatedValue; use poem_openapi::{Enum, NewType, Object, Union}; use serde::{Deserialize, Serialize}; +use std::time::SystemTime; use std::{collections::HashMap, fmt::Display, fmt::Formatter}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Object)] @@ -1429,13 +1430,13 @@ impl From for golem_api_grpc::proto::golem::worker::v1::worker_execu golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::UnexpectedOplogEntry(err.into()) } GolemError::RuntimeError(err) => { - golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::RuntimeError(err.into()) + golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::RuntimeError(err.into()) } GolemError::InvalidShardId(err) => { golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::InvalidShardId(err.into()) } GolemError::PreviousInvocationFailed(err) => { - golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::PreviousInvocationFailed(err.into()) + golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::PreviousInvocationFailed(err.into()) } GolemError::PreviousInvocationExited(err) => { golem_api_grpc::proto::golem::worker::v1::worker_execution_error::Error::PreviousInvocationExited(err.into()) @@ -1500,6 +1501,7 @@ pub struct Component { pub component_name: ComponentName, pub component_size: u64, pub metadata: ComponentMetadata, + pub created_at: Option>, } impl TryFrom for Component { @@ -1508,6 +1510,13 @@ impl TryFrom for Component { fn try_from( value: golem_api_grpc::proto::golem::component::Component, ) -> Result { + let created_at = match value.created_at { + Some(t) => { + let t = SystemTime::try_from(t).map_err(|_| "Failed to convert timestamp")?; + Some(t.into()) + } + None => None, + }; Ok(Self { versioned_component_id: value .versioned_component_id @@ -1516,6 +1525,7 @@ impl TryFrom for Component { component_name: ComponentName(value.component_name), component_size: value.component_size, metadata: value.metadata.ok_or("Missing metadata")?.try_into()?, + created_at, }) } } @@ -1528,6 +1538,9 @@ impl From for golem_api_grpc::proto::golem::component::Component { component_size: value.component_size, metadata: Some(value.metadata.into()), project_id: None, + created_at: value + .created_at + .map(|t| prost_types::Timestamp::from(SystemTime::from(t))), } } } diff --git a/golem-worker-service-base/src/service/component/default.rs b/golem-worker-service-base/src/service/component/default.rs index 719ab9a0ba..26773f9977 100644 --- a/golem-worker-service-base/src/service/component/default.rs +++ b/golem-worker-service-base/src/service/component/default.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use chrono::Utc; use http::Uri; use tonic::transport::Channel; @@ -215,6 +216,7 @@ impl ComponentServiceNoop { producers: vec![], memories: vec![], }, + created_at: Some(Utc::now()), } } } diff --git a/golem-worker-service-base/tests/services_tests.rs b/golem-worker-service-base/tests/services_tests.rs index e30450ed32..4e9dfd4432 100644 --- a/golem-worker-service-base/tests/services_tests.rs +++ b/golem-worker-service-base/tests/services_tests.rs @@ -28,6 +28,7 @@ mod tests { HttpApiDefinitionValidator, RouteValidationError, }; + use chrono::Utc; use std::sync::Arc; use testcontainers::clients::Cli; use testcontainers::{Container, RunnableImage}; @@ -140,6 +141,7 @@ mod tests { producers: vec![], memories: vec![], }, + created_at: Some(Utc::now()), } } diff --git a/openapi/golem-service.yaml b/openapi/golem-service.yaml index 80c664ec61..d8a0d652a6 100644 --- a/openapi/golem-service.yaml +++ b/openapi/golem-service.yaml @@ -3794,6 +3794,9 @@ components: format: uint64 metadata: $ref: '#/components/schemas/ComponentMetadata' + createdAt: + type: string + format: date-time required: - versionedComponentId - componentName