1
#include "source/extensions/clusters/logical_dns/logical_dns_cluster.h"
2

            
3
#include <chrono>
4
#include <list>
5
#include <memory>
6
#include <string>
7
#include <vector>
8

            
9
#include "envoy/common/exception.h"
10
#include "envoy/config/cluster/v3/cluster.pb.h"
11
#include "envoy/config/core/v3/address.pb.h"
12
#include "envoy/config/endpoint/v3/endpoint.pb.h"
13
#include "envoy/extensions/clusters/dns/v3/dns_cluster.pb.h"
14
#include "envoy/stats/scope.h"
15

            
16
#include "source/common/common/dns_utils.h"
17
#include "source/common/common/fmt.h"
18
#include "source/common/config/utility.h"
19
#include "source/common/network/address_impl.h"
20
#include "source/common/network/dns_resolver/dns_factory_util.h"
21
#include "source/common/network/utility.h"
22
#include "source/common/protobuf/protobuf.h"
23
#include "source/common/protobuf/utility.h"
24
#include "source/extensions/clusters/common/dns_cluster_backcompat.h"
25

            
26
namespace Envoy {
27
namespace Upstream {
28

            
29
namespace {
30
envoy::config::endpoint::v3::ClusterLoadAssignment
31
146
convertPriority(const envoy::config::endpoint::v3::ClusterLoadAssignment& load_assignment) {
32
146
  envoy::config::endpoint::v3::ClusterLoadAssignment converted;
33
146
  converted.MergeFrom(load_assignment);
34

            
35
  // We convert the priority set by the configuration back to zero. This helps
36
  // ensure that we don't blow up later on when using zone aware routing due
37
  // to a check that all priorities are zero.
38
  //
39
  // Since LOGICAL_DNS is limited to exactly one host declared per load_assignment
40
  // (checked in the ctor in this file), we can safely just rewrite the priority
41
  // to zero.
42
146
  for (auto& endpoint : *converted.mutable_endpoints()) {
43
146
    endpoint.set_priority(0);
44
146
  }
45

            
46
146
  return converted;
47
146
}
48
} // namespace
49

            
50
absl::StatusOr<std::unique_ptr<LogicalDnsCluster>>
51
LogicalDnsCluster::create(const envoy::config::cluster::v3::Cluster& cluster,
52
                          const envoy::extensions::clusters::dns::v3::DnsCluster& dns_cluster,
53
                          ClusterFactoryContext& context,
54
156
                          Network::DnsResolverSharedPtr dns_resolver) {
55
156
  const auto& load_assignment = cluster.load_assignment();
56
156
  const auto& locality_lb_endpoints = load_assignment.endpoints();
57
156
  if (locality_lb_endpoints.size() != 1 || locality_lb_endpoints[0].lb_endpoints().size() != 1) {
58
7
    if (cluster.has_load_assignment()) {
59
6
      return absl::InvalidArgumentError(
60
6
          "LOGICAL_DNS clusters must have a single locality_lb_endpoint and a single lb_endpoint");
61
6
    } else {
62
1
      return absl::InvalidArgumentError("LOGICAL_DNS clusters must have a single host");
63
1
    }
64
7
  }
65

            
66
149
  const envoy::config::core::v3::SocketAddress& socket_address =
67
149
      locality_lb_endpoints[0].lb_endpoints()[0].endpoint().address().socket_address();
68
149
  if (!socket_address.resolver_name().empty()) {
69
2
    return absl::InvalidArgumentError(
70
2
        "LOGICAL_DNS clusters must NOT have a custom resolver name set");
71
2
  }
72

            
73
147
  absl::Status creation_status = absl::OkStatus();
74
147
  std::unique_ptr<LogicalDnsCluster> ret;
75

            
76
147
  ret = std::unique_ptr<LogicalDnsCluster>(new LogicalDnsCluster(
77
147
      cluster, dns_cluster, context, std::move(dns_resolver), creation_status));
78
147
  RETURN_IF_NOT_OK(creation_status);
79
147
  return ret;
80
147
}
81

            
82
LogicalDnsCluster::LogicalDnsCluster(
83
    const envoy::config::cluster::v3::Cluster& cluster,
84
    const envoy::extensions::clusters::dns::v3::DnsCluster& dns_cluster,
85
    ClusterFactoryContext& context, Network::DnsResolverSharedPtr dns_resolver,
86
    absl::Status& creation_status)
87
147
    : ClusterImplBase(cluster, context, creation_status), dns_resolver_(dns_resolver),
88
147
      dns_refresh_rate_ms_(std::chrono::milliseconds(
89
147
          PROTOBUF_GET_MS_OR_DEFAULT(dns_cluster, dns_refresh_rate, 5000))),
90
      dns_jitter_ms_(
91
147
          std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(dns_cluster, dns_jitter, 0))),
92
147
      respect_dns_ttl_(dns_cluster.respect_dns_ttl()),
93
      dns_lookup_family_(
94
147
          Envoy::DnsUtils::getDnsLookupFamilyFromEnum(dns_cluster.dns_lookup_family())),
95
147
      resolve_timer_(context.serverFactoryContext().mainThreadDispatcher().createTimer(
96
169
          [this]() -> void { startResolve(); })),
97
147
      local_info_(context.serverFactoryContext().localInfo()),
98
147
      load_assignment_(convertPriority(cluster.load_assignment())) {
99
147
  failure_backoff_strategy_ = Config::Utility::prepareDnsRefreshStrategy(
100
147
      dns_cluster, dns_refresh_rate_ms_.count(),
101
147
      context.serverFactoryContext().api().randomGenerator());
102

            
103
147
  const envoy::config::core::v3::SocketAddress& socket_address =
104
147
      lbEndpoint().endpoint().address().socket_address();
105

            
106
  // Checked by factory;
107
147
  ASSERT(socket_address.resolver_name().empty());
108
147
  dns_address_ = socket_address.address();
109
147
  dns_port_ = socket_address.port_value();
110

            
111
147
  if (lbEndpoint().endpoint().hostname().empty()) {
112
145
    hostname_ = dns_address_;
113
145
  } else {
114
2
    hostname_ = lbEndpoint().endpoint().hostname();
115
2
  }
116
147
}
117

            
118
146
void LogicalDnsCluster::startPreInit() {
119
146
  startResolve();
120
146
  if (!wait_for_warm_on_init_) {
121
4
    onPreInitComplete();
122
4
  }
123
146
}
124

            
125
146
LogicalDnsCluster::~LogicalDnsCluster() {
126
146
  if (active_dns_query_) {
127
67
    active_dns_query_->cancel(Network::ActiveDnsQuery::CancelReason::QueryAbandoned);
128
67
  }
129
146
}
130

            
131
179
void LogicalDnsCluster::startResolve() {
132
179
  ENVOY_LOG(trace, "starting async DNS resolution for {}", dns_address_);
133
179
  info_->configUpdateStats().update_attempt_.inc();
134

            
135
179
  active_dns_query_ = dns_resolver_->resolve(
136
179
      dns_address_, dns_lookup_family_,
137
179
      [this](Network::DnsResolver::ResolutionStatus status, absl::string_view details,
138
185
             std::list<Network::DnsResponse>&& response) -> void {
139
120
        active_dns_query_ = nullptr;
140
120
        ENVOY_LOG(trace, "async DNS resolution complete for {} details {}", dns_address_, details);
141

            
142
120
        std::chrono::milliseconds final_refresh_rate = dns_refresh_rate_ms_;
143

            
144
        // If the DNS resolver successfully resolved with an empty response list, the logical DNS
145
        // cluster does not update. This ensures that a potentially previously resolved address does
146
        // not stabilize back to 0 hosts.
147
120
        if (status == Network::DnsResolver::ResolutionStatus::Completed && !response.empty()) {
148
112
          info_->configUpdateStats().update_success_.inc();
149
112
          const auto addrinfo = response.front().addrInfo();
150
          // TODO(mattklein123): Move port handling into the DNS interface.
151
112
          ASSERT(addrinfo.address_ != nullptr);
152
112
          Network::Address::InstanceConstSharedPtr new_address =
153
112
              Network::Utility::getAddressWithPort(*(response.front().addrInfo().address_),
154
112
                                                   dns_port_);
155
112
          auto address_list = DnsUtils::generateAddressList(response, dns_port_);
156

            
157
112
          if (!logical_host_) {
158
82
            logical_host_ = THROW_OR_RETURN_VALUE(
159
82
                LogicalHost::create(info_, hostname_, new_address, address_list,
160
82
                                    localityLbEndpoint(), lbEndpoint(), nullptr),
161
82
                std::unique_ptr<LogicalHost>);
162

            
163
82
            const auto& locality_lb_endpoint = localityLbEndpoint();
164
82
            PriorityStateManager priority_state_manager(*this, local_info_, nullptr);
165
82
            priority_state_manager.initializePriorityFor(locality_lb_endpoint);
166
82
            priority_state_manager.registerHostForPriority(logical_host_, locality_lb_endpoint);
167

            
168
82
            const uint32_t priority = locality_lb_endpoint.priority();
169
82
            priority_state_manager.updateClusterPrioritySet(
170
82
                priority, std::move(priority_state_manager.priorityState()[priority].first),
171
82
                absl::nullopt, absl::nullopt, absl::nullopt, absl::nullopt, absl::nullopt);
172
82
          }
173

            
174
112
          if (!current_resolved_address_ ||
175
112
              (*new_address != *current_resolved_address_ ||
176
110
               DnsUtils::listChanged(address_list, current_resolved_address_list_))) {
177
110
            current_resolved_address_ = new_address;
178
110
            current_resolved_address_list_ = address_list;
179

            
180
            // Make sure that we have an updated address for admin display, health
181
            // checking, and creating real host connections.
182
110
            logical_host_->setNewAddresses(new_address, address_list, lbEndpoint());
183
110
          } else {
184
2
            info_->configUpdateStats().update_no_rebuild_.inc();
185
2
          }
186

            
187
          // reset failure backoff strategy because there was a success.
188
112
          failure_backoff_strategy_->reset();
189

            
190
112
          if (respect_dns_ttl_ && addrinfo.ttl_ != std::chrono::seconds(0)) {
191
1
            final_refresh_rate = addrinfo.ttl_;
192
1
          }
193
112
          if (dns_jitter_ms_.count() != 0) {
194
            // Note that `random_.random()` returns a uint64 while
195
            // `dns_jitter_ms_.count()` returns a signed long that gets cast into a uint64.
196
            // Thus, the modulo of the two will be a positive as long as
197
            // `dns_jitter_ms_.count()` is positive.
198
            // It is important that this be positive, otherwise `final_refresh_rate` could be
199
            // negative causing Envoy to crash.
200
2
            final_refresh_rate +=
201
2
                std::chrono::milliseconds(random_.random() % dns_jitter_ms_.count());
202
2
          }
203
112
          ENVOY_LOG(debug, "DNS refresh rate reset for {}, refresh rate {} ms", dns_address_,
204
112
                    final_refresh_rate.count());
205
112
        } else {
206
8
          info_->configUpdateStats().update_failure_.inc();
207
8
          final_refresh_rate =
208
8
              std::chrono::milliseconds(failure_backoff_strategy_->nextBackOffMs());
209
8
          ENVOY_LOG(debug, "DNS refresh rate reset for {}, (failure) refresh rate {} ms",
210
8
                    dns_address_, final_refresh_rate.count());
211
8
        }
212

            
213
120
        onPreInitComplete();
214
120
        resolve_timer_->enableTimer(final_refresh_rate);
215
120
      });
216
179
}
217

            
218
} // namespace Upstream
219
} // namespace Envoy