• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //
3 // Copyright 2023 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18 
19 #include "src/cpp/ext/csm/metadata_exchange.h"
20 
21 #include <grpc/slice.h>
22 #include <grpc/support/port_platform.h>
23 #include <stddef.h>
24 
25 #include <algorithm>
26 #include <array>
27 #include <cstdint>
28 #include <unordered_map>
29 
30 #include "absl/log/check.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/escaping.h"
33 #include "absl/strings/str_split.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/strings/strip.h"
36 #include "absl/types/optional.h"
37 #include "absl/types/variant.h"
38 #include "opentelemetry/sdk/resource/semantic_conventions.h"
39 #include "src/core/lib/iomgr/error.h"
40 #include "src/core/lib/slice/slice_internal.h"
41 #include "src/core/telemetry/call_tracer.h"
42 #include "src/core/util/env.h"
43 #include "src/cpp/ext/otel/key_value_iterable.h"
44 #include "upb/base/string_view.h"
45 
46 namespace grpc {
47 namespace internal {
48 
49 using OptionalLabelKey =
50     grpc_core::ClientCallTracer::CallAttemptTracer::OptionalLabelKey;
51 
52 namespace {
53 
54 // The keys that will be used in the Metadata Exchange between local and remote.
55 constexpr absl::string_view kMetadataExchangeTypeKey = "type";
56 constexpr absl::string_view kMetadataExchangeWorkloadNameKey = "workload_name";
57 constexpr absl::string_view kMetadataExchangeNamespaceNameKey =
58     "namespace_name";
59 constexpr absl::string_view kMetadataExchangeClusterNameKey = "cluster_name";
60 constexpr absl::string_view kMetadataExchangeLocationKey = "location";
61 constexpr absl::string_view kMetadataExchangeProjectIdKey = "project_id";
62 constexpr absl::string_view kMetadataExchangeCanonicalServiceKey =
63     "canonical_service";
64 // The keys that will be used for the local attributes when recording metrics.
65 constexpr absl::string_view kCanonicalServiceAttribute =
66     "csm.workload_canonical_service";
67 constexpr absl::string_view kMeshIdAttribute = "csm.mesh_id";
68 // The keys that will be used for the peer attributes when recording metrics.
69 constexpr absl::string_view kPeerTypeAttribute = "csm.remote_workload_type";
70 constexpr absl::string_view kPeerWorkloadNameAttribute =
71     "csm.remote_workload_name";
72 constexpr absl::string_view kPeerNamespaceNameAttribute =
73     "csm.remote_workload_namespace_name";
74 constexpr absl::string_view kPeerClusterNameAttribute =
75     "csm.remote_workload_cluster_name";
76 constexpr absl::string_view kPeerLocationAttribute =
77     "csm.remote_workload_location";
78 constexpr absl::string_view kPeerProjectIdAttribute =
79     "csm.remote_workload_project_id";
80 constexpr absl::string_view kPeerCanonicalServiceAttribute =
81     "csm.remote_workload_canonical_service";
82 // Type values used by Google Cloud Resource Detector
83 constexpr absl::string_view kGkeType = "gcp_kubernetes_engine";
84 constexpr absl::string_view kGceType = "gcp_compute_engine";
85 
86 // A helper method that decodes the remote metadata \a slice as a protobuf
87 // Struct allocated on \a arena.
DecodeMetadata(grpc_core::Slice slice,upb_Arena * arena)88 google_protobuf_Struct* DecodeMetadata(grpc_core::Slice slice,
89                                        upb_Arena* arena) {
90   // Treat an empty slice as an invalid metadata value.
91   if (slice.empty()) {
92     return nullptr;
93   }
94   // Decode the slice.
95   std::string decoded_metadata;
96   bool metadata_decoded =
97       absl::Base64Unescape(slice.as_string_view(), &decoded_metadata);
98   if (metadata_decoded) {
99     return google_protobuf_Struct_parse(decoded_metadata.c_str(),
100                                         decoded_metadata.size(), arena);
101   }
102   return nullptr;
103 }
104 
StringToGcpResourceType(absl::string_view type)105 MeshLabelsIterable::GcpResourceType StringToGcpResourceType(
106     absl::string_view type) {
107   if (type == kGkeType) {
108     return MeshLabelsIterable::GcpResourceType::kGke;
109   } else if (type == kGceType) {
110     return MeshLabelsIterable::GcpResourceType::kGce;
111   }
112   return MeshLabelsIterable::GcpResourceType::kUnknown;
113 }
114 
AbslStrToUpbStr(absl::string_view str)115 upb_StringView AbslStrToUpbStr(absl::string_view str) {
116   return upb_StringView_FromDataAndSize(str.data(), str.size());
117 }
118 
UpbStrToAbslStr(upb_StringView str)119 absl::string_view UpbStrToAbslStr(upb_StringView str) {
120   return absl::string_view(str.data, str.size);
121 }
122 
AddStringKeyValueToStructProto(google_protobuf_Struct * struct_pb,absl::string_view key,absl::string_view value,upb_Arena * arena)123 void AddStringKeyValueToStructProto(google_protobuf_Struct* struct_pb,
124                                     absl::string_view key,
125                                     absl::string_view value, upb_Arena* arena) {
126   google_protobuf_Value* value_pb = google_protobuf_Value_new(arena);
127   google_protobuf_Value_set_string_value(value_pb, AbslStrToUpbStr(value));
128   google_protobuf_Struct_fields_set(struct_pb, AbslStrToUpbStr(key), value_pb,
129                                     arena);
130 }
131 
GetStringValueFromAttributeMap(const opentelemetry::sdk::common::AttributeMap & map,absl::string_view key)132 absl::string_view GetStringValueFromAttributeMap(
133     const opentelemetry::sdk::common::AttributeMap& map,
134     absl::string_view key) {
135   const auto& attributes = map.GetAttributes();
136   const auto it = attributes.find(std::string(key));
137   if (it == attributes.end()) {
138     return "unknown";
139   }
140   const auto* string_value = absl::get_if<std::string>(&it->second);
141   if (string_value == nullptr) {
142     return "unknown";
143   }
144   return *string_value;
145 }
146 
GetStringValueFromUpbStruct(google_protobuf_Struct * struct_pb,absl::string_view key,upb_Arena * arena)147 absl::string_view GetStringValueFromUpbStruct(google_protobuf_Struct* struct_pb,
148                                               absl::string_view key,
149                                               upb_Arena* arena) {
150   if (struct_pb == nullptr) {
151     return "unknown";
152   }
153   google_protobuf_Value* value_pb = google_protobuf_Value_new(arena);
154   bool present = google_protobuf_Struct_fields_get(
155       struct_pb, AbslStrToUpbStr(key), &value_pb);
156   if (present) {
157     if (google_protobuf_Value_has_string_value(value_pb)) {
158       return UpbStrToAbslStr(google_protobuf_Value_string_value(value_pb));
159     }
160   }
161   return "unknown";
162 }
163 
164 struct RemoteAttribute {
165   absl::string_view otel_attribute;
166   absl::string_view metadata_attribute;
167 };
168 
169 constexpr std::array<RemoteAttribute, 2> kFixedAttributes = {
170     RemoteAttribute{kPeerTypeAttribute, kMetadataExchangeTypeKey},
171     RemoteAttribute{kPeerCanonicalServiceAttribute,
172                     kMetadataExchangeCanonicalServiceKey},
173 };
174 
175 constexpr std::array<RemoteAttribute, 5> kGkeAttributeList = {
176     RemoteAttribute{kPeerWorkloadNameAttribute,
177                     kMetadataExchangeWorkloadNameKey},
178     RemoteAttribute{kPeerNamespaceNameAttribute,
179                     kMetadataExchangeNamespaceNameKey},
180     RemoteAttribute{kPeerClusterNameAttribute, kMetadataExchangeClusterNameKey},
181     RemoteAttribute{kPeerLocationAttribute, kMetadataExchangeLocationKey},
182     RemoteAttribute{kPeerProjectIdAttribute, kMetadataExchangeProjectIdKey},
183 };
184 
185 constexpr std::array<RemoteAttribute, 3> kGceAttributeList = {
186     RemoteAttribute{kPeerWorkloadNameAttribute,
187                     kMetadataExchangeWorkloadNameKey},
188     RemoteAttribute{kPeerLocationAttribute, kMetadataExchangeLocationKey},
189     RemoteAttribute{kPeerProjectIdAttribute, kMetadataExchangeProjectIdKey},
190 };
191 
GetAttributesForType(MeshLabelsIterable::GcpResourceType remote_type)192 absl::Span<const RemoteAttribute> GetAttributesForType(
193     MeshLabelsIterable::GcpResourceType remote_type) {
194   switch (remote_type) {
195     case MeshLabelsIterable::GcpResourceType::kGke:
196       return kGkeAttributeList;
197     case MeshLabelsIterable::GcpResourceType::kGce:
198       return kGceAttributeList;
199     default:
200       return {};
201   }
202 }
203 
204 absl::optional<std::pair<absl::string_view, absl::string_view>>
NextFromAttributeList(absl::Span<const RemoteAttribute> attributes,size_t start_index,size_t curr,google_protobuf_Struct * decoded_metadata,upb_Arena * arena)205 NextFromAttributeList(absl::Span<const RemoteAttribute> attributes,
206                       size_t start_index, size_t curr,
207                       google_protobuf_Struct* decoded_metadata,
208                       upb_Arena* arena) {
209   DCHECK_GE(curr, start_index);
210   const size_t index = curr - start_index;
211   if (index >= attributes.size()) return absl::nullopt;
212   const auto& attribute = attributes[index];
213   return std::make_pair(
214       attribute.otel_attribute,
215       GetStringValueFromUpbStruct(decoded_metadata,
216                                   attribute.metadata_attribute, arena));
217 }
218 
219 }  // namespace
220 
221 //
222 // MeshLabelsIterable
223 //
224 
MeshLabelsIterable(const std::vector<std::pair<absl::string_view,std::string>> & local_labels,grpc_core::Slice remote_metadata)225 MeshLabelsIterable::MeshLabelsIterable(
226     const std::vector<std::pair<absl::string_view, std::string>>& local_labels,
227     grpc_core::Slice remote_metadata)
228     : struct_pb_(DecodeMetadata(std::move(remote_metadata), arena_.ptr())),
229       local_labels_(local_labels),
230       remote_type_(StringToGcpResourceType(GetStringValueFromUpbStruct(
231           struct_pb_, kMetadataExchangeTypeKey, arena_.ptr()))) {}
232 
233 absl::optional<std::pair<absl::string_view, absl::string_view>>
Next()234 MeshLabelsIterable::Next() {
235   size_t local_labels_size = local_labels_.size();
236   if (pos_ < local_labels_size) {
237     return local_labels_[pos_++];
238   }
239   const size_t fixed_attribute_end =
240       local_labels_size + kFixedAttributes.size();
241   if (pos_ < fixed_attribute_end) {
242     return NextFromAttributeList(kFixedAttributes, local_labels_size, pos_++,
243                                  struct_pb_, arena_.ptr());
244   }
245   return NextFromAttributeList(GetAttributesForType(remote_type_),
246                                fixed_attribute_end, pos_++, struct_pb_,
247                                arena_.ptr());
248 }
249 
Size() const250 size_t MeshLabelsIterable::Size() const {
251   return local_labels_.size() + kFixedAttributes.size() +
252          GetAttributesForType(remote_type_).size();
253 }
254 
255 //
256 // ServiceMeshLabelsInjector
257 //
258 
ServiceMeshLabelsInjector(const opentelemetry::sdk::common::AttributeMap & map)259 ServiceMeshLabelsInjector::ServiceMeshLabelsInjector(
260     const opentelemetry::sdk::common::AttributeMap& map) {
261   upb::Arena arena;
262   auto* metadata = google_protobuf_Struct_new(arena.ptr());
263   // Assume kubernetes for now
264   absl::string_view type_value = GetStringValueFromAttributeMap(
265       map, opentelemetry::sdk::resource::SemanticConventions::kCloudPlatform);
266   std::string workload_name_value =
267       grpc_core::GetEnv("CSM_WORKLOAD_NAME").value_or("unknown");
268   absl::string_view namespace_value = GetStringValueFromAttributeMap(
269       map,
270       opentelemetry::sdk::resource::SemanticConventions::kK8sNamespaceName);
271   absl::string_view cluster_name_value = GetStringValueFromAttributeMap(
272       map, opentelemetry::sdk::resource::SemanticConventions::kK8sClusterName);
273   absl::string_view location_value = GetStringValueFromAttributeMap(
274       map, opentelemetry::sdk::resource::SemanticConventions::
275                kCloudAvailabilityZone);  // if zonal
276   if (location_value == "unknown") {
277     location_value = GetStringValueFromAttributeMap(
278         map, opentelemetry::sdk::resource::SemanticConventions::
279                  kCloudRegion);  // if regional
280   }
281   absl::string_view project_id_value = GetStringValueFromAttributeMap(
282       map, opentelemetry::sdk::resource::SemanticConventions::kCloudAccountId);
283   std::string canonical_service_value =
284       grpc_core::GetEnv("CSM_CANONICAL_SERVICE_NAME").value_or("unknown");
285   // Create metadata to be sent over wire.
286   AddStringKeyValueToStructProto(metadata, kMetadataExchangeTypeKey, type_value,
287                                  arena.ptr());
288   AddStringKeyValueToStructProto(metadata, kMetadataExchangeCanonicalServiceKey,
289                                  canonical_service_value, arena.ptr());
290   if (type_value == kGkeType) {
291     AddStringKeyValueToStructProto(metadata, kMetadataExchangeWorkloadNameKey,
292                                    workload_name_value, arena.ptr());
293     AddStringKeyValueToStructProto(metadata, kMetadataExchangeNamespaceNameKey,
294                                    namespace_value, arena.ptr());
295     AddStringKeyValueToStructProto(metadata, kMetadataExchangeClusterNameKey,
296                                    cluster_name_value, arena.ptr());
297     AddStringKeyValueToStructProto(metadata, kMetadataExchangeLocationKey,
298                                    location_value, arena.ptr());
299     AddStringKeyValueToStructProto(metadata, kMetadataExchangeProjectIdKey,
300                                    project_id_value, arena.ptr());
301   } else if (type_value == kGceType) {
302     AddStringKeyValueToStructProto(metadata, kMetadataExchangeWorkloadNameKey,
303                                    workload_name_value, arena.ptr());
304     AddStringKeyValueToStructProto(metadata, kMetadataExchangeLocationKey,
305                                    location_value, arena.ptr());
306     AddStringKeyValueToStructProto(metadata, kMetadataExchangeProjectIdKey,
307                                    project_id_value, arena.ptr());
308   }
309 
310   size_t output_length;
311   char* output =
312       google_protobuf_Struct_serialize(metadata, arena.ptr(), &output_length);
313   serialized_labels_to_send_ = grpc_core::Slice::FromCopiedString(
314       absl::Base64Escape(absl::string_view(output, output_length)));
315   // Fill up local labels map. The rest we get from the detected Resource and
316   // from the peer.
317   local_labels_.emplace_back(kCanonicalServiceAttribute,
318                              canonical_service_value);
319   local_labels_.emplace_back(
320       kMeshIdAttribute, grpc_core::GetEnv("CSM_MESH_ID").value_or("unknown"));
321 }
322 
GetLabels(grpc_metadata_batch * incoming_initial_metadata) const323 std::unique_ptr<LabelsIterable> ServiceMeshLabelsInjector::GetLabels(
324     grpc_metadata_batch* incoming_initial_metadata) const {
325   auto peer_metadata =
326       incoming_initial_metadata->Take(grpc_core::XEnvoyPeerMetadata());
327   return std::make_unique<MeshLabelsIterable>(
328       local_labels_, peer_metadata.has_value() ? *std::move(peer_metadata)
329                                                : grpc_core::Slice());
330 }
331 
AddLabels(grpc_metadata_batch * outgoing_initial_metadata,LabelsIterable * labels_from_incoming_metadata) const332 void ServiceMeshLabelsInjector::AddLabels(
333     grpc_metadata_batch* outgoing_initial_metadata,
334     LabelsIterable* labels_from_incoming_metadata) const {
335   // On the server, if the labels from incoming metadata did not have a
336   // non-empty base64 encoded "x-envoy-peer-metadata", do not perform metadata
337   // exchange.
338   if (labels_from_incoming_metadata != nullptr &&
339       !static_cast<MeshLabelsIterable*>(labels_from_incoming_metadata)
340            ->GotRemoteLabels()) {
341     return;
342   }
343   outgoing_initial_metadata->Set(grpc_core::XEnvoyPeerMetadata(),
344                                  serialized_labels_to_send_.Ref());
345 }
346 
AddOptionalLabels(bool is_client,absl::Span<const grpc_core::RefCountedStringValue> optional_labels,opentelemetry::nostd::function_ref<bool (opentelemetry::nostd::string_view,opentelemetry::common::AttributeValue)> callback) const347 bool ServiceMeshLabelsInjector::AddOptionalLabels(
348     bool is_client,
349     absl::Span<const grpc_core::RefCountedStringValue> optional_labels,
350     opentelemetry::nostd::function_ref<
351         bool(opentelemetry::nostd::string_view,
352              opentelemetry::common::AttributeValue)>
353         callback) const {
354   if (!is_client) {
355     // Currently the CSM optional labels are only set on client.
356     return true;
357   }
358   // Performs JSON label name format to CSM Observability Metric spec format
359   // conversion.
360   absl::string_view service_name =
361       optional_labels[static_cast<size_t>(
362                           grpc_core::ClientCallTracer::CallAttemptTracer::
363                               OptionalLabelKey::kXdsServiceName)]
364           .as_string_view();
365   absl::string_view service_namespace =
366       optional_labels[static_cast<size_t>(
367                           grpc_core::ClientCallTracer::CallAttemptTracer::
368                               OptionalLabelKey::kXdsServiceNamespace)]
369           .as_string_view();
370   return callback("csm.service_name",
371                   service_name.empty()
372                       ? "unknown"
373                       : AbslStrViewToOpenTelemetryStrView(service_name)) &&
374          callback("csm.service_namespace_name",
375                   service_namespace.empty()
376                       ? "unknown"
377                       : AbslStrViewToOpenTelemetryStrView(service_namespace));
378 }
379 
380 }  // namespace internal
381 }  // namespace grpc
382