1
#include "source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h"
2

            
3
#include "envoy/upstream/cluster_manager.h"
4

            
5
#include "source/common/common/assert.h"
6

            
7
namespace Envoy {
8
namespace Extensions {
9
namespace Config {
10
namespace Validators {
11

            
12
void MinimumClustersValidator::validate(
13
8
    const Server::Instance&, const std::vector<Envoy::Config::DecodedResourcePtr>& resources) {
14
8
  absl::flat_hash_set<std::string> next_cluster_names(resources.size());
15
8
  for (const auto& resource : resources) {
16
8
    const envoy::config::cluster::v3::Cluster& cluster =
17
8
        dynamic_cast<const envoy::config::cluster::v3::Cluster&>(resource->resource());
18

            
19
    // If the cluster was already added in the current update, it won't be added twice.
20
8
    next_cluster_names.insert(cluster.name());
21
8
  }
22

            
23
  // After applying the update, the clusters names will be the same as in
24
  // next_cluster_names.
25
8
  if (next_cluster_names.size() < min_clusters_num_) {
26
2
    throw EnvoyException("CDS update attempts to reduce clusters below configured minimum.");
27
2
  }
28
8
}
29

            
30
void MinimumClustersValidator::validate(
31
    const Server::Instance& server,
32
    const std::vector<Envoy::Config::DecodedResourcePtr>& added_resources,
33
19
    const Protobuf::RepeatedPtrField<std::string>& removed_resources) {
34
19
  const Upstream::ClusterManager& cm = server.clusterManager();
35
  // If the number of clusters after removing all of the clusters in the removed_resources list is
36
  // above the threshold, then it is surely a valid config.
37
19
  const Upstream::ClusterManager::ClusterInfoMaps& cur_clusters = cm.clusters();
38
19
  const uint32_t cur_clusters_num = cur_clusters.added_via_api_clusters_num_;
39
19
  const uint32_t removed_resources_size = static_cast<uint32_t>(removed_resources.size());
40
19
  if ((cur_clusters_num >= removed_resources_size) &&
41
19
      (cur_clusters_num - removed_resources_size >= min_clusters_num_)) {
42
9
    return;
43
9
  }
44

            
45
  // It could be that the removed clusters gets us below the threshold, simulate what happens if
46
  // the current clusters list is updated.
47
10
  uint32_t newly_added_clusters_num = 0;
48
10
  absl::flat_hash_set<std::string> added_cluster_names(added_resources.size());
49
10
  for (const auto& resource : added_resources) {
50
7
    const envoy::config::cluster::v3::Cluster& cluster =
51
7
        dynamic_cast<const envoy::config::cluster::v3::Cluster&>(resource->resource());
52

            
53
    // If the cluster was already added in the current update, skip this cluster.
54
7
    if (!added_cluster_names.insert(cluster.name()).second) {
55
1
      continue;
56
1
    }
57
    // If the cluster is new, count it.
58
6
    if (!cur_clusters.hasCluster(cluster.name())) {
59
6
      ++newly_added_clusters_num;
60
6
    }
61
6
  }
62

            
63
  // Count the clusters that need to be removed.
64
10
  uint32_t removed_clusters_num = 0;
65
10
  for (const auto& removed_cluster : removed_resources) {
66
7
    Upstream::ClusterConstOptRef cluster = cur_clusters.getCluster(removed_cluster);
67
    // Only clusters that were added via api can be removed.
68
7
    if (cluster.has_value() && cluster->get().info()->addedViaApi()) {
69
5
      ++removed_clusters_num;
70
5
    }
71
7
  }
72

            
73
  // Prevent integer overflow.
74
10
  ASSERT(cur_clusters_num >= removed_clusters_num);
75
10
  const uint64_t new_clusters_num =
76
10
      static_cast<uint64_t>(cur_clusters_num) + newly_added_clusters_num - removed_clusters_num;
77
10
  if (new_clusters_num < min_clusters_num_) {
78
6
    throw EnvoyException("CDS update attempts to reduce clusters below configured minimum.");
79
6
  }
80
10
}
81

            
82
} // namespace Validators
83
} // namespace Config
84
} // namespace Extensions
85
} // namespace Envoy