1
#include "source/common/orca/orca_parser.h"
2

            
3
#include <cmath>
4
#include <cstddef>
5
#include <string>
6
#include <utility>
7
#include <vector>
8

            
9
#include "envoy/common/exception.h"
10
#include "envoy/http/header_map.h"
11

            
12
#include "source/common/common/base64.h"
13
#include "source/common/common/fmt.h"
14
#include "source/common/http/header_utility.h"
15
#include "source/common/protobuf/utility.h"
16

            
17
#include "absl/container/flat_hash_set.h"
18
#include "absl/status/status.h"
19
#include "absl/strings/match.h"
20
#include "absl/strings/numbers.h"
21
#include "absl/strings/str_cat.h"
22
#include "absl/strings/str_split.h"
23
#include "absl/strings/string_view.h"
24
#include "absl/strings/strip.h"
25

            
26
using ::Envoy::Http::HeaderMap;
27
using xds::data::orca::v3::OrcaLoadReport;
28

            
29
namespace Envoy {
30
namespace Orca {
31

            
32
namespace {
33

            
34
1146
const Http::LowerCaseString& endpointLoadMetricsHeader() {
35
1146
  CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, kEndpointLoadMetricsHeader);
36
1146
}
37

            
38
3841
const Http::LowerCaseString& endpointLoadMetricsHeaderBin() {
39
3841
  CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, kEndpointLoadMetricsHeaderBin);
40
3841
}
41

            
42
absl::Status tryCopyNamedMetricToOrcaLoadReport(absl::string_view metric_name, double metric_value,
43
7
                                                OrcaLoadReport& orca_load_report) {
44
7
  if (metric_name.empty()) {
45
1
    return absl::InvalidArgumentError("named metric key is empty.");
46
1
  }
47

            
48
6
  orca_load_report.mutable_named_metrics()->insert({std::string(metric_name), metric_value});
49
6
  return absl::OkStatus();
50
7
}
51

            
52
12
std::vector<absl::string_view> parseCommaDelimitedHeader(const absl::string_view entry) {
53
12
  std::vector<absl::string_view> values;
54
12
  std::vector<absl::string_view> tokens =
55
12
      Envoy::Http::HeaderUtility::parseCommaDelimitedHeader(entry);
56
12
  values.insert(values.end(), tokens.begin(), tokens.end());
57
12
  return values;
58
12
}
59

            
60
absl::Status tryCopyMetricToOrcaLoadReport(absl::string_view metric_name,
61
                                           absl::string_view metric_value,
62
28
                                           OrcaLoadReport& orca_load_report) {
63
28
  if (metric_name.empty()) {
64
    return absl::InvalidArgumentError("metric names cannot be empty strings");
65
  }
66

            
67
28
  if (metric_value.empty()) {
68
1
    return absl::InvalidArgumentError("metric values cannot be empty strings");
69
1
  }
70

            
71
27
  double value;
72
27
  if (!absl::SimpleAtod(metric_value, &value)) {
73
1
    return absl::InvalidArgumentError(fmt::format(
74
1
        "unable to parse custom backend load metric value({}): {}", metric_name, metric_value));
75
1
  }
76

            
77
26
  if (std::isnan(value)) {
78
1
    return absl::InvalidArgumentError(
79
1
        fmt::format("custom backend load metric value({}) cannot be NaN.", metric_name));
80
1
  }
81

            
82
25
  if (std::isinf(value)) {
83
1
    return absl::InvalidArgumentError(
84
1
        fmt::format("custom backend load metric value({}) cannot be infinity.", metric_name));
85
1
  }
86

            
87
  // Check for negative values for all metrics.
88
24
  if (value < 0) {
89
1
    return absl::InvalidArgumentError(
90
1
        fmt::format("custom backend load metric value({}) cannot be negative.", metric_name));
91
1
  }
92

            
93
23
  if (absl::StartsWith(metric_name, kUtilizationPrefix)) {
94
3
    absl::string_view metric_name_without_prefix =
95
3
        absl::StripPrefix(metric_name, kUtilizationPrefix);
96
3
    if (metric_name_without_prefix.empty()) {
97
1
      return absl::InvalidArgumentError("utilization metric key is empty.");
98
1
    }
99

            
100
2
    orca_load_report.mutable_utilization()->insert(
101
2
        {std::string(metric_name_without_prefix), value});
102
2
    return absl::OkStatus();
103
3
  }
104

            
105
20
  if (absl::StartsWith(metric_name, kNamedMetricsFieldPrefix)) {
106
7
    auto metric_name_without_prefix = absl::StripPrefix(metric_name, kNamedMetricsFieldPrefix);
107
7
    return tryCopyNamedMetricToOrcaLoadReport(metric_name_without_prefix, value, orca_load_report);
108
7
  }
109

            
110
13
  if (metric_name == kCpuUtilizationField) {
111
4
    orca_load_report.set_cpu_utilization(value);
112
9
  } else if (metric_name == kMemUtilizationField) {
113
2
    orca_load_report.set_mem_utilization(value);
114
7
  } else if (metric_name == kApplicationUtilizationField) {
115
2
    orca_load_report.set_application_utilization(value);
116
5
  } else if (metric_name == kEpsField) {
117
2
    orca_load_report.set_eps(value);
118
3
  } else if (metric_name == kRpsFractionalField) {
119
2
    orca_load_report.set_rps_fractional(value);
120
2
  } else {
121
1
    return absl::InvalidArgumentError(absl::StrCat("unsupported metric name: ", metric_name));
122
1
  }
123
12
  return absl::OkStatus();
124
13
}
125

            
126
absl::Status tryParseNativeHttpEncoded(const absl::string_view header,
127
12
                                       OrcaLoadReport& orca_load_report) {
128
12
  const std::vector<absl::string_view> values = parseCommaDelimitedHeader(header);
129

            
130
  // Check for duplicate metric names here because OrcaLoadReport fields are not
131
  // marked as optional and therefore don't differentiate between unset and
132
  // default values.
133
12
  absl::flat_hash_set<absl::string_view> metric_names;
134
30
  for (const auto value : values) {
135
30
    std::pair<absl::string_view, absl::string_view> entry =
136
30
        absl::StrSplit(value, absl::MaxSplits(absl::ByAnyChar("=:"), 1), absl::SkipWhitespace());
137
30
    if (metric_names.contains(entry.first)) {
138
2
      return absl::AlreadyExistsError(
139
2
          absl::StrCat(kEndpointLoadMetricsHeader, " contains duplicate metric: ", entry.first));
140
2
    }
141
28
    RETURN_IF_NOT_OK(tryCopyMetricToOrcaLoadReport(entry.first, entry.second, orca_load_report));
142
20
    metric_names.insert(entry.first);
143
20
  }
144
2
  return absl::OkStatus();
145
12
}
146

            
147
absl::Status tryParseSerializedBinary(const absl::string_view header,
148
2696
                                      OrcaLoadReport& orca_load_report) {
149
2696
  if (header.empty()) {
150
1
    return absl::InvalidArgumentError("ORCA binary header value is empty");
151
1
  }
152
2695
  const std::string decoded_value = Envoy::Base64::decode(header);
153
2695
  if (decoded_value.empty()) {
154
1
    return absl::InvalidArgumentError(
155
1
        fmt::format("unable to decode ORCA binary header value: {}", header));
156
1
  }
157
2694
  if (!orca_load_report.ParseFromString(decoded_value)) {
158
2
    return absl::InvalidArgumentError(
159
2
        fmt::format("unable to parse binaryheader to OrcaLoadReport: {}", header));
160
2
  }
161
2692
  return absl::OkStatus();
162
2694
}
163

            
164
} // namespace
165

            
166
3841
absl::StatusOr<OrcaLoadReport> parseOrcaLoadReportHeaders(const HeaderMap& headers) {
167
3841
  OrcaLoadReport load_report;
168

            
169
  // Binary protobuf format. Legacy header from gRPC implementation.
170
3841
  if (const auto header_bin = headers.get(endpointLoadMetricsHeaderBin()); !header_bin.empty()) {
171
2695
    const auto header_value = header_bin[0]->value().getStringView();
172
2695
    RETURN_IF_NOT_OK(tryParseSerializedBinary(header_value, load_report));
173
3811
  } else if (const auto header = headers.get(endpointLoadMetricsHeader()); !header.empty()) {
174
20
    absl::string_view header_value = header[0]->value().getStringView();
175

            
176
20
    if (absl::StartsWith(header_value, kHeaderFormatPrefixBin)) {
177
      // Binary protobuf format.
178
1
      RETURN_IF_NOT_OK(tryParseSerializedBinary(header_value.substr(kHeaderFormatPrefixBin.size()),
179
1
                                                load_report));
180
19
    } else if (absl::StartsWith(header_value, kHeaderFormatPrefixText)) {
181
      // Native HTTP format.
182
12
      RETURN_IF_NOT_OK(tryParseNativeHttpEncoded(
183
12
          header_value.substr(kHeaderFormatPrefixText.size()), load_report));
184
7
    } else if (absl::StartsWith(header_value, kHeaderFormatPrefixJson)) {
185
      // JSON format.
186
4
#if defined(ENVOY_ENABLE_FULL_PROTOS) && defined(ENVOY_ENABLE_YAML)
187
4
      bool has_unknown_field = false;
188
4
      RETURN_IF_ERROR(Envoy::MessageUtil::loadFromJsonNoThrow(
189
4
          header_value.substr(kHeaderFormatPrefixJson.size()), load_report, has_unknown_field));
190
#else
191
      IS_ENVOY_BUG("JSON formatted ORCA header support not implemented for this build");
192
#endif // !ENVOY_ENABLE_FULL_PROTOS || !ENVOY_ENABLE_YAML
193
4
    } else {
194
      // Unknown format. Get the first 5 characters or the prefix before the first space to
195
      // generate the error message.
196
3
      absl::string_view prefix = header_value.substr(0, std::min<size_t>(5, header_value.size()));
197
3
      prefix = prefix.substr(0, prefix.find_first_of(' '));
198

            
199
3
      return absl::InvalidArgumentError(fmt::format("unsupported ORCA header format: {}", prefix));
200
3
    }
201
1144
  } else {
202
1126
    return absl::NotFoundError("no ORCA data sent from the backend");
203
1126
  }
204

            
205
2695
  return load_report;
206
3841
}
207

            
208
} // namespace Orca
209
} // namespace Envoy