diff --git a/extensions/common/BUILD b/extensions/common/BUILD index 2bc1771291d..5ac05f8a578 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/types:optional", "@envoy//envoy/common:hashable_interface", + "@envoy//envoy/local_info:local_info_interface", "@envoy//envoy/registry", "@envoy//envoy/stream_info:filter_state_interface", "@envoy//source/common/common:hash_lib", diff --git a/extensions/common/metadata_object.cc b/extensions/common/metadata_object.cc index e3067fa6b31..c39c79431a7 100644 --- a/extensions/common/metadata_object.cc +++ b/extensions/common/metadata_object.cc @@ -14,6 +14,7 @@ #include "extensions/common/metadata_object.h" +#include "envoy/config/core/v3/base.pb.h" #include "envoy/registry/registry.h" #include "source/common/common/hash.h" #include "source/common/protobuf/utility.h" @@ -37,6 +38,8 @@ static absl::flat_hash_map ALL_METADATA_FIELDS {WorkloadNameToken, BaggageToken::WorkloadName}, {WorkloadTypeToken, BaggageToken::WorkloadType}, {InstanceNameToken, BaggageToken::InstanceName}, + {RegionToken, BaggageToken::LocalityRegion}, + {ZoneToken, BaggageToken::LocalityZone}, }; // This maps baggage keys into baggage tokens. We use it to decode baggage keys @@ -53,6 +56,7 @@ static absl::flat_hash_map ALL_BAGGAGE_TOKENS = {CronjobNameBaggageToken, BaggageToken::WorkloadName}, {JobNameBaggageToken, BaggageToken::WorkloadName}, {InstanceNameBaggageToken, BaggageToken::InstanceName}, + }; static absl::flat_hash_map ALL_WORKLOAD_TOKENS = { @@ -95,6 +99,8 @@ std::string WorkloadMetadataObject::baggage() const { {Istio::Common::AppNameToken, Istio::Common::AppNameBaggageToken}, {Istio::Common::AppVersionToken, Istio::Common::AppVersionBaggageToken}, {Istio::Common::InstanceNameToken, Istio::Common::InstanceNameBaggageToken}, + {Istio::Common::RegionToken, Istio::Common::LocalityRegionBaggageToken}, + {Istio::Common::ZoneToken, Istio::Common::LocalityZoneBaggageToken}, }; for (const auto& [field_name, baggage_key] : field_to_baggage) { @@ -141,6 +147,12 @@ Envoy::ProtobufTypes::MessagePtr WorkloadMetadataObject::serializeAsProto() cons if (!identity_.empty()) { (*message->mutable_fields())[IdentityToken].set_string_value(identity_); } + if (!locality_region_.empty()) { + (*message->mutable_fields())[RegionToken].set_string_value(locality_region_); + } + if (!locality_zone_.empty()) { + (*message->mutable_fields())[ZoneToken].set_string_value(locality_zone_); + } if (!labels_.empty()) { auto* labels = (*message->mutable_fields())[LabelsToken].mutable_struct_value(); @@ -183,6 +195,12 @@ WorkloadMetadataObject::serializeAsPairs() const { if (!app_version_.empty()) { parts.push_back({AppVersionToken, app_version_}); } + if (!locality_region_.empty()) { + parts.push_back({RegionToken, locality_region_}); + } + if (!locality_zone_.empty()) { + parts.push_back({ZoneToken, locality_zone_}); + } if (!labels_.empty()) { for (const auto& l : labels_) { parts.push_back({absl::StrCat("labels[]", l.first), absl::string_view(l.second)}); @@ -263,6 +281,12 @@ google::protobuf::Struct convertWorkloadMetadataToStruct(const WorkloadMetadataO if (const auto owner = obj.owner(); owner.has_value()) { (*metadata.mutable_fields())[OwnerMetadataField].set_string_value(*owner); } + if (!obj.locality_region_.empty()) { + (*metadata.mutable_fields())[RegionMetadataField].set_string_value(obj.locality_region_); + } + if (!obj.locality_zone_.empty()) { + (*metadata.mutable_fields())[ZoneMetadataField].set_string_value(obj.locality_zone_); + } return metadata; } @@ -275,8 +299,15 @@ convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata) { std::unique_ptr convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, const absl::flat_hash_set& additional_labels) { + return convertStructToWorkloadMetadata(metadata, additional_labels, {}); +} + +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels, + const absl::optional locality) { absl::string_view instance, namespace_name, owner, workload, cluster, canonical_name, - canonical_revision, app_name, app_version; + canonical_revision, app_name, app_version, region, zone; std::vector> labels; for (const auto& it : metadata.fields()) { if (it.first == InstanceMetadataField) { @@ -307,9 +338,19 @@ convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, } } } - auto obj = std::make_unique(instance, cluster, namespace_name, workload, - canonical_name, canonical_revision, app_name, - app_version, parseOwner(owner, workload), ""); + std::string locality_region = std::string(region); + std::string locality_zone = std::string(zone); + if (locality.has_value()) { + if (!locality->region().empty() && locality_region.empty()) { + locality_region = locality->region(); + } + if (!locality->zone().empty() && locality_zone.empty()) { + locality_zone = locality->zone(); + } + } + auto obj = std::make_unique( + instance, cluster, namespace_name, workload, canonical_name, canonical_revision, app_name, + app_version, parseOwner(owner, workload), "", locality_region, locality_zone); obj->setLabels(labels); return obj; } @@ -321,7 +362,8 @@ convertEndpointMetadata(const std::string& endpoint_encoding) { return {}; } return absl::make_optional("", parts[4], parts[1], parts[0], parts[2], - parts[3], "", "", WorkloadType::Unknown, ""); + parts[3], "", "", WorkloadType::Unknown, "", + "", ""); } std::string serializeToStringDeterministic(const google::protobuf::Struct& metadata) { @@ -363,6 +405,10 @@ WorkloadMetadataObject::getField(absl::string_view field_name) const { return "unknown"; case BaggageToken::InstanceName: return instance_name_; + case BaggageToken::LocalityRegion: + return locality_region_; + case BaggageToken::LocalityZone: + return locality_zone_; } } return {}; @@ -383,6 +429,8 @@ convertBaggageToWorkloadMetadata(absl::string_view data, absl::string_view ident absl::string_view canonical_revision; absl::string_view app_name; absl::string_view app_version; + absl::string_view region; + absl::string_view zone; WorkloadType workload_type = WorkloadType::Unknown; std::vector properties = absl::StrSplit(data, ','); for (absl::string_view property : properties) { @@ -419,14 +467,20 @@ convertBaggageToWorkloadMetadata(absl::string_view data, absl::string_view ident case BaggageToken::InstanceName: instance = parts.second; break; + case BaggageToken::LocalityRegion: + region = parts.second; + break; + case BaggageToken::LocalityZone: + zone = parts.second; + break; default: break; } } } - return std::make_unique(instance, cluster, namespace_name, workload, - canonical_name, canonical_revision, app_name, - app_version, workload_type, identity); + return std::make_unique( + instance, cluster, namespace_name, workload, canonical_name, canonical_revision, app_name, + app_version, workload_type, identity, region, zone); } } // namespace Common diff --git a/extensions/common/metadata_object.h b/extensions/common/metadata_object.h index 7253fdd539a..4356aba838f 100644 --- a/extensions/common/metadata_object.h +++ b/extensions/common/metadata_object.h @@ -15,6 +15,7 @@ #pragma once #include "envoy/common/hashable.h" +#include "envoy/config/core/v3/base.pb.h" #include "envoy/stream_info/filter_state.h" #include "source/common/protobuf/protobuf.h" @@ -65,6 +66,8 @@ enum class BaggageToken { WorkloadName, WorkloadType, InstanceName, + LocalityZone, + LocalityRegion }; // Field names accessible from WorkloadMetadataObject. @@ -79,6 +82,8 @@ constexpr absl::string_view WorkloadTypeToken = "type"; constexpr absl::string_view InstanceNameToken = "name"; constexpr absl::string_view LabelsToken = "labels"; constexpr absl::string_view IdentityToken = "identity"; +constexpr absl::string_view RegionToken = "region"; +constexpr absl::string_view ZoneToken = "availability_zone"; // Field names used to translate baggage content into // WorkloadMetadataObject information. @@ -93,6 +98,8 @@ constexpr absl::string_view PodNameBaggageToken = "k8s.pod.name"; constexpr absl::string_view CronjobNameBaggageToken = "k8s.cronjob.name"; constexpr absl::string_view JobNameBaggageToken = "k8s.job.name"; constexpr absl::string_view InstanceNameBaggageToken = "k8s.instance.name"; +constexpr absl::string_view LocalityRegionBaggageToken = "cloud.region"; +constexpr absl::string_view LocalityZoneBaggageToken = "cloud.availability_zone"; constexpr absl::string_view InstanceMetadataField = "NAME"; constexpr absl::string_view NamespaceMetadataField = "NAMESPACE"; @@ -100,6 +107,8 @@ constexpr absl::string_view ClusterMetadataField = "CLUSTER_ID"; constexpr absl::string_view OwnerMetadataField = "OWNER"; constexpr absl::string_view WorkloadMetadataField = "WORKLOAD_NAME"; constexpr absl::string_view LabelsMetadataField = "LABELS"; +constexpr absl::string_view RegionMetadataField = "REGION"; +constexpr absl::string_view ZoneMetadataField = "AVAILABILITY_ZONE"; class WorkloadMetadataObject : public Envoy::StreamInfo::FilterState::Object, public Envoy::Hashable { @@ -109,11 +118,13 @@ class WorkloadMetadataObject : public Envoy::StreamInfo::FilterState::Object, absl::string_view canonical_name, absl::string_view canonical_revision, absl::string_view app_name, absl::string_view app_version, WorkloadType workload_type, - absl::string_view identity) + absl::string_view identity, absl::string_view region, + absl::string_view zone) : instance_name_(instance_name), cluster_name_(cluster_name), namespace_name_(namespace_name), workload_name_(workload_name), canonical_name_(canonical_name), canonical_revision_(canonical_revision), app_name_(app_name), app_version_(app_version), - workload_type_(workload_type), identity_(identity) {} + workload_type_(workload_type), identity_(identity), locality_region_(region), + locality_zone_(zone) {} absl::optional hash() const override; Envoy::ProtobufTypes::MessagePtr serializeAsProto() const override; @@ -137,6 +148,8 @@ class WorkloadMetadataObject : public Envoy::StreamInfo::FilterState::Object, const std::string app_version_; const WorkloadType workload_type_; const std::string identity_; + const std::string locality_region_; + const std::string locality_zone_; std::vector> labels_; }; @@ -157,6 +170,11 @@ std::unique_ptr convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, const absl::flat_hash_set& additional_labels); +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels, + const absl::optional locality); + // Convert endpoint metadata string to a metadata object. // Telemetry metadata is compressed into a semicolon separated string: // workload-name;namespace;canonical-service-name;canonical-service-revision;cluster-id. diff --git a/extensions/common/metadata_object_test.cc b/extensions/common/metadata_object_test.cc index 188defa777f..82542a0d597 100644 --- a/extensions/common/metadata_object_test.cc +++ b/extensions/common/metadata_object_test.cc @@ -27,16 +27,16 @@ using ::testing::NiceMock; TEST(WorkloadMetadataObjectTest, Baggage) { WorkloadMetadataObject deploy("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", WorkloadType::Deployment, ""); + "v1alpha3", "", "", WorkloadType::Deployment, "", "", ""); WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", WorkloadType::Pod, ""); + "v1alpha3", "", "", WorkloadType::Pod, "", "", ""); WorkloadMetadataObject cronjob("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "foo-app", "v1", WorkloadType::CronJob, ""); + "v1alpha3", "foo-app", "v1", WorkloadType::CronJob, "", "", ""); WorkloadMetadataObject job("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", WorkloadType::Job, ""); + "v1alpha3", "", "", WorkloadType::Job, "", "", ""); EXPECT_EQ(deploy.serializeAsString(), absl::StrCat("type=deployment,workload=foo,name=pod-foo-1234,cluster=my-cluster,", @@ -67,7 +67,7 @@ void checkStructConversion(const Envoy::StreamInfo::FilterState::Object& data) { TEST(WorkloadMetadataObjectTest, ConversionWithLabels) { WorkloadMetadataObject deploy("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", WorkloadType::Deployment, ""); + "v1alpha3", "", "", WorkloadType::Deployment, "", "", ""); deploy.setLabels({{"label1", "value1"}, {"label2", "value2"}}); auto pb = convertWorkloadMetadataToStruct(deploy); auto obj1 = convertStructToWorkloadMetadata(pb, {"label1", "label2"}); diff --git a/source/extensions/common/workload_discovery/api.cc b/source/extensions/common/workload_discovery/api.cc index 86ffcd24609..28940840342 100644 --- a/source/extensions/common/workload_discovery/api.cc +++ b/source/extensions/common/workload_discovery/api.cc @@ -69,7 +69,8 @@ Istio::Common::WorkloadMetadataObject convert(const istio::workload::Workload& w return Istio::Common::WorkloadMetadataObject( workload.name(), workload.cluster_id(), ns, workload.workload_name(), workload.canonical_name(), workload.canonical_revision(), workload.canonical_name(), - workload.canonical_revision(), workload_type, identity); + workload.canonical_revision(), workload_type, identity, workload.locality().region(), + workload.locality().zone()); } } // namespace diff --git a/source/extensions/filters/http/istio_stats/istio_stats.cc b/source/extensions/filters/http/istio_stats/istio_stats.cc index 9bedf042928..4027a602051 100644 --- a/source/extensions/filters/http/istio_stats/istio_stats.cc +++ b/source/extensions/filters/http/istio_stats/istio_stats.cc @@ -154,7 +154,8 @@ peerInfo(Reporter reporter, const StreamInfo::FilterState& filter_state) { extractString(obj, Istio::Common::AppNameToken), extractString(obj, Istio::Common::AppVersionToken), Istio::Common::fromSuffix(extractString(obj, Istio::Common::WorkloadTypeToken)), - extractString(obj, Istio::Common::IdentityToken)); + extractString(obj, Istio::Common::IdentityToken), + extractString(obj, Istio::Common::RegionToken), extractString(obj, Istio::Common::ZoneToken)); return peer_info; } diff --git a/source/extensions/filters/http/peer_metadata/filter.cc b/source/extensions/filters/http/peer_metadata/filter.cc index 80981b9bbee..8ca85071734 100644 --- a/source/extensions/filters/http/peer_metadata/filter.cc +++ b/source/extensions/filters/http/peer_metadata/filter.cc @@ -228,8 +228,9 @@ BaggagePropagationMethod::BaggagePropagationMethod( std::string BaggagePropagationMethod::computeBaggageValue( Server::Configuration::ServerFactoryContext& factory_context) const { - const auto obj = - Istio::Common::convertStructToWorkloadMetadata(factory_context.localInfo().node().metadata()); + const auto obj = Istio::Common::convertStructToWorkloadMetadata( + factory_context.localInfo().node().metadata(), {}, + factory_context.localInfo().node().locality()); return obj->baggage(); } diff --git a/source/extensions/filters/http/peer_metadata/filter_test.cc b/source/extensions/filters/http/peer_metadata/filter_test.cc index 7824c9a3d9b..446d2f14c9e 100644 --- a/source/extensions/filters/http/peer_metadata/filter_test.cc +++ b/source/extensions/filters/http/peer_metadata/filter_test.cc @@ -133,7 +133,8 @@ TEST_F(PeerMetadataTest, DownstreamXDSNone) { TEST_F(PeerMetadataTest, DownstreamXDS) { const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -155,7 +156,8 @@ TEST_F(PeerMetadataTest, DownstreamXDS) { TEST_F(PeerMetadataTest, UpstreamXDS) { const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -191,7 +193,8 @@ TEST_F(PeerMetadataTest, UpstreamXDSInternal) { *host_metadata); const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -260,7 +263,8 @@ TEST_F(PeerMetadataTest, DownstreamFallbackFirst) { TEST_F(PeerMetadataTest, DownstreamFallbackSecond) { const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -343,7 +347,8 @@ TEST_F(PeerMetadataTest, UpstreamFallbackFirst) { TEST_F(PeerMetadataTest, UpstreamFallbackSecond) { const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -365,7 +370,8 @@ TEST_F(PeerMetadataTest, UpstreamFallbackSecond) { TEST_F(PeerMetadataTest, UpstreamFallbackFirstXDS) { const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", - "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, ""); + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -584,6 +590,9 @@ class BaggagePropagationMethodTest : public testing::Test { version: v2.1 service.istio.io/canonical-name: sample-service service.istio.io/canonical-revision: stable + locality: + zone: us-east4-b + region: us-east4 )EOF", context_.server_factory_context_.local_info_.node_); } @@ -614,8 +623,11 @@ TEST_F(BaggagePropagationMethodTest, DownstreamBaggageInjection) { EXPECT_TRUE(absl::StrContains(baggage_value, "service.version=stable")); EXPECT_TRUE(absl::StrContains(baggage_value, "app.name=sample-app")); EXPECT_TRUE(absl::StrContains(baggage_value, "app.version=v2.1")); - EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.pod.name=sample-workload")); + EXPECT_TRUE( + absl::StrContains(baggage_value, "k8s.pod.name=sample-workload")); // workload type is pod EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.instance.name=sample-instance")); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.region=us-east4")); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.availability_zone=us-east4-b")); } TEST_F(BaggagePropagationMethodTest, UpstreamBaggageInjection) { @@ -818,7 +830,7 @@ TEST_F(PeerMetadataTest, DownstreamBaggageFallbackSecond) { // No baggage header, so XDS should be called as fallback const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-namespace", "foo", "foo-service", "v1alpha3", "", "", - Istio::Common::WorkloadType::Pod, ""); + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -846,7 +858,7 @@ TEST_F(PeerMetadataTest, UpstreamBaggageFallbackFirst) { // WDS information is also present, and this is the one tha tshould be used. const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-upstream", "foo", "foo-service", "v1alpha3", "", "", - Istio::Common::WorkloadType::Pod, ""); + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { @@ -871,7 +883,7 @@ TEST_F(PeerMetadataTest, UpstreamBaggageFallbackSecond) { // but workload discovery should pick up the details. const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-upstream", "foo", "foo-service", "v1alpha3", "", "", - Istio::Common::WorkloadType::Pod, ""); + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); EXPECT_CALL(*metadata_provider_, GetMetadata(_)) .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) -> std::optional { diff --git a/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc b/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc index 997d32e182c..64875406185 100644 --- a/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc @@ -188,8 +188,9 @@ void MetadataExchangeFilter::writeNodeMetadata() { } ENVOY_LOG(trace, "Writing metadata to the connection."); Protobuf::Struct data; - const auto obj = Istio::Common::convertStructToWorkloadMetadata(local_info_.node().metadata(), - config_->additional_labels_); + const auto obj = Istio::Common::convertStructToWorkloadMetadata( + local_info_.node().metadata(), config_->additional_labels_, local_info_.node().locality()); + *(*data.mutable_fields())[ExchangeMetadataHeader].mutable_struct_value() = Istio::Common::convertWorkloadMetadataToStruct(*obj); std::string metadata_id = getMetadataId();