|
18 | 18 |
|
19 | 19 | #include <algorithm> |
20 | 20 | #include <boost/algorithm/string/join.hpp> |
| 21 | +#include <boost/algorithm/string/split.hpp> |
21 | 22 | #include <boost/asio/ip/host_name.hpp> |
22 | 23 | #include <boost/network/protocol/http/client.hpp> |
23 | 24 | #include <boost/range/iterator_range.hpp> |
@@ -106,6 +107,86 @@ class KubernetesReader::NonRetriableError |
106 | 107 | NonRetriableError(const std::string& what) : QueryException(what) {} |
107 | 108 | }; |
108 | 109 |
|
| 110 | +const std::pair<std::string, std::string> TypeAndVersion( |
| 111 | + const std::string api_version, const std::string kind) { |
| 112 | + const int group_name_len = api_version.find('/'); |
| 113 | + if (group_name_len == std::string::npos) { |
| 114 | + return std::make_pair("io.k8s." + kind, api_version); |
| 115 | + } else { |
| 116 | + std::vector<std::string> slash_split; |
| 117 | + boost::algorithm::split( |
| 118 | + slash_split, api_version, boost::algorithm::is_any_of("/")); |
| 119 | + const std::string group_name = slash_split[0]; |
| 120 | + const std::string version = slash_split[1]; |
| 121 | + return std::make_pair("io.k8s." + group_name + "." + kind, version); |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +const std::string KubernetesReader::FullResourceName( |
| 126 | + const std::string& self_link) const { |
| 127 | + std::vector<std::string> slash_split; |
| 128 | + boost::algorithm::split( |
| 129 | + slash_split, self_link, boost::algorithm::is_any_of("/")); |
| 130 | + |
| 131 | + std::vector<std::string> link_components; |
| 132 | + if(slash_split[1] == "api") { |
| 133 | + // Core resources, start with "/api/<version>/..." |
| 134 | + link_components.assign(slash_split.begin() + 3, slash_split.end()); |
| 135 | + } else { |
| 136 | + // Non-core resources, start with "/apis/<group-name>/<version>/..." |
| 137 | + const std::string group_name = slash_split[2]; |
| 138 | + link_components.push_back(group_name); |
| 139 | + link_components.insert(link_components.end(), |
| 140 | + slash_split.begin() + 4, slash_split.end()); |
| 141 | + |
| 142 | + } |
| 143 | + const std::string relative_link = |
| 144 | + boost::algorithm::join(link_components, "/"); |
| 145 | + const std::string cluster_full_name = ClusterFullName(); |
| 146 | + return cluster_full_name + "/k8s/" + relative_link; |
| 147 | +} |
| 148 | + |
| 149 | +MetadataUpdater::ResourceMetadata KubernetesReader::GetResourceMetadata( |
| 150 | + const json::Object* resource, Timestamp collected_at, bool is_deleted) const |
| 151 | + throw(json::Exception) { |
| 152 | + const std::string cluster_location = environment_.KubernetesClusterLocation(); |
| 153 | + |
| 154 | + const std::string kind = resource->Get<json::String>("kind"); |
| 155 | + const std::string api_version = resource->Get<json::String>("apiVersion"); |
| 156 | + const json::Object* metadata = resource->Get<json::Object>("metadata"); |
| 157 | + const std::string self_link = metadata->Get<json::String>("selfLink"); |
| 158 | + const std::pair<std::string, std::string> type_and_version = |
| 159 | + TypeAndVersion(api_version, kind); |
| 160 | + const std::string type = type_and_version.first; |
| 161 | + const std::string version = type_and_version.second; |
| 162 | + |
| 163 | + const std::string schema = |
| 164 | + format::Substitute(std::string(kKubernetesSchemaNameFormat), |
| 165 | + {{"type", type}, {"version", version}}); |
| 166 | + const std::string created_str = |
| 167 | + metadata->Get<json::String>("creationTimestamp"); |
| 168 | + Timestamp created_at = time::rfc3339::FromString(created_str); |
| 169 | + const std::string resource_full_name = FullResourceName(self_link); |
| 170 | + |
| 171 | + if (config_.VerboseLogging()) { |
| 172 | + LOG(INFO) << "Raw resource metadata for full name: " << resource_full_name |
| 173 | + << ": " << *resource; |
| 174 | + } |
| 175 | + |
| 176 | + const MonitoredResource dummy_mr("", {}); |
| 177 | + return MetadataUpdater::ResourceMetadata( |
| 178 | + std::vector<std::string>{}, dummy_mr, |
| 179 | + resource_full_name, |
| 180 | +#ifdef ENABLE_KUBERNETES_METADATA |
| 181 | + MetadataStore::Metadata(type, cluster_location, version, |
| 182 | + schema, is_deleted, created_at, collected_at, |
| 183 | + resource->Clone()) |
| 184 | +#else |
| 185 | + MetadataStore::Metadata::IGNORED() |
| 186 | +#endif |
| 187 | + ); |
| 188 | +} |
| 189 | + |
109 | 190 | KubernetesReader::KubernetesReader(const Configuration& config, |
110 | 191 | HealthChecker* health_checker) |
111 | 192 | : config_(config), environment_(config), health_checker_(health_checker) {} |
@@ -791,6 +872,41 @@ void KubernetesReader::ValidateDynamicConfiguration() const |
791 | 872 | } |
792 | 873 | } |
793 | 874 |
|
| 875 | +void KubernetesReader::ResourceCallback( |
| 876 | + MetadataUpdater::UpdateCallback callback, |
| 877 | + const json::Object* resource, Timestamp collected_at, bool is_deleted) const |
| 878 | + throw(json::Exception) { |
| 879 | + std::vector<MetadataUpdater::ResourceMetadata> result_vector; |
| 880 | + result_vector.emplace_back( |
| 881 | + GetResourceMetadata(resource, collected_at, is_deleted)); |
| 882 | + callback(std::move(result_vector)); |
| 883 | +} |
| 884 | + |
| 885 | +void KubernetesReader::WatchResources( |
| 886 | + const std::string& api_path, const std::string& name, |
| 887 | + MetadataUpdater::UpdateCallback callback) const { |
| 888 | + LOG(INFO) << "Watch thread (" << name << ") started"; |
| 889 | + |
| 890 | + try { |
| 891 | + // TODO: There seems to be a Kubernetes API bug with watch=true. |
| 892 | + WatchMaster( |
| 893 | + name, api_path, |
| 894 | + [=](const json::Object* resource, Timestamp collected_at, |
| 895 | + bool is_deleted) { |
| 896 | + ResourceCallback(callback, resource, collected_at, is_deleted); |
| 897 | + }); |
| 898 | + } catch (const json::Exception& e) { |
| 899 | + LOG(ERROR) << e.what(); |
| 900 | + LOG(ERROR) << "No more " << name << " metadata will be collected"; |
| 901 | + } catch (const KubernetesReader::QueryException& e) { |
| 902 | + LOG(ERROR) << "No more " << name << " metadata will be collected"; |
| 903 | + } |
| 904 | + if (health_checker_) { |
| 905 | + health_checker_->SetUnhealthy("kubernetes_node_thread"); |
| 906 | + } |
| 907 | + LOG(INFO) << "Watch thread (" << name << ") exiting"; |
| 908 | +} |
| 909 | + |
794 | 910 | void KubernetesReader::PodCallback( |
795 | 911 | MetadataUpdater::UpdateCallback callback, |
796 | 912 | const json::Object* pod, Timestamp collected_at, bool is_deleted) const |
@@ -912,6 +1028,12 @@ void KubernetesUpdater::StartUpdater() { |
912 | 1028 | pod_watch_thread_ = std::thread([=]() { |
913 | 1029 | reader_.WatchPods(watched_node, cb); |
914 | 1030 | }); |
| 1031 | + service_watch_thread_ = std::thread([=]() { |
| 1032 | + reader_.WatchResources("/api/v1/services", "Service", cb); |
| 1033 | + }); |
| 1034 | + endpoints_watch_thread_ = std::thread([=]() { |
| 1035 | + reader_.WatchResources("/api/v1/endpoints", "Endpoints", cb); |
| 1036 | + }); |
915 | 1037 | } else { |
916 | 1038 | // Only try to poll if watch is disabled. |
917 | 1039 | PollingMetadataUpdater::StartUpdater(); |
|
0 commit comments