couchbase 3.4.1 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +2 -0
  4. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +4 -0
  5. data/ext/couchbase/core/cluster_options.hxx +0 -1
  6. data/ext/couchbase/core/config_profile.cxx +23 -1
  7. data/ext/couchbase/core/config_profile.hxx +2 -12
  8. data/ext/couchbase/core/impl/analytics.cxx +236 -0
  9. data/ext/couchbase/core/impl/cluster.cxx +0 -1
  10. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +5 -3
  11. data/ext/couchbase/core/impl/query.cxx +5 -5
  12. data/ext/couchbase/core/io/dns_client.cxx +225 -0
  13. data/ext/couchbase/core/io/dns_client.hxx +19 -188
  14. data/ext/couchbase/core/transactions/active_transaction_record.hxx +2 -2
  15. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +3 -0
  16. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +1 -1
  17. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +12 -12
  18. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +7 -1
  19. data/ext/couchbase/core/transactions/transaction_context.cxx +1 -0
  20. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +144 -155
  21. data/ext/couchbase/core/utils/connection_string.cxx +10 -3
  22. data/ext/couchbase/core/utils/connection_string.hxx +3 -3
  23. data/ext/couchbase/couchbase/analytics_error_context.hxx +143 -0
  24. data/ext/couchbase/couchbase/analytics_meta_data.hxx +155 -0
  25. data/ext/couchbase/couchbase/analytics_metrics.hxx +163 -0
  26. data/ext/couchbase/couchbase/analytics_options.hxx +359 -0
  27. data/ext/couchbase/couchbase/analytics_result.hxx +102 -0
  28. data/ext/couchbase/couchbase/analytics_scan_consistency.hxx +46 -0
  29. data/ext/couchbase/couchbase/analytics_status.hxx +41 -0
  30. data/ext/couchbase/couchbase/analytics_warning.hxx +85 -0
  31. data/ext/couchbase/couchbase/cluster.hxx +33 -0
  32. data/ext/couchbase/couchbase/fmt/analytics_status.hxx +76 -0
  33. data/ext/couchbase/couchbase/query_options.hxx +0 -1
  34. data/ext/couchbase/couchbase/scope.hxx +33 -0
  35. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +1 -1
  36. data/ext/couchbase/test/CMakeLists.txt +1 -2
  37. data/ext/couchbase/test/test_helper.hxx +1 -1
  38. data/ext/couchbase/test/test_integration_analytics.cxx +289 -13
  39. data/ext/couchbase/test/test_integration_crud.cxx +8 -1
  40. data/ext/couchbase/test/test_integration_examples.cxx +41 -0
  41. data/ext/couchbase/test/test_integration_management.cxx +15 -3
  42. data/ext/couchbase/test/test_integration_search.cxx +601 -0
  43. data/ext/couchbase/test/test_transaction_transaction_simple.cxx +73 -0
  44. data/ext/couchbase/test/test_unit_config_profiles.cxx +12 -12
  45. data/ext/couchbase/test/test_unit_connection_string.cxx +35 -0
  46. data/ext/couchbase/third_party/snappy/CMakeLists.txt +150 -27
  47. data/ext/couchbase/third_party/snappy/cmake/config.h.in +28 -24
  48. data/ext/couchbase/third_party/snappy/snappy-internal.h +189 -25
  49. data/ext/couchbase/third_party/snappy/snappy-sinksource.cc +26 -9
  50. data/ext/couchbase/third_party/snappy/snappy-sinksource.h +11 -11
  51. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.cc +1 -1
  52. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.h +227 -308
  53. data/ext/couchbase/third_party/snappy/snappy-stubs-public.h.in +0 -11
  54. data/ext/couchbase/third_party/snappy/snappy.cc +1176 -410
  55. data/ext/couchbase/third_party/snappy/snappy.h +19 -4
  56. data/ext/couchbase.cxx +27 -6
  57. data/ext/revisions.rb +3 -3
  58. data/lib/couchbase/cluster.rb +13 -9
  59. data/lib/couchbase/cluster_registry.rb +7 -2
  60. data/lib/couchbase/configuration.rb +3 -4
  61. data/lib/couchbase/options.rb +85 -2
  62. data/lib/couchbase/search_options.rb +158 -240
  63. data/lib/couchbase/version.rb +1 -1
  64. metadata +17 -6
  65. data/ext/couchbase/core/CMakeLists.txt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 738d96522993c221d75dc83546dc6c9f5447309fbbe4969f6fb92719b0427e78
4
- data.tar.gz: 5189c37096e7193f5756050a9713bd7d8f75acd8949e5afe2402fc1834ed538f
3
+ metadata.gz: 78899f9fd7614787a46a54ca185e869cc93a7714ff0a1ef4c38feef98d7d54be
4
+ data.tar.gz: f42e90dc0e70b2f690f9c4f7dffe3cd9675db7d8f9025bb5db315b6e59407199
5
5
  SHA512:
6
- metadata.gz: 85632d1eea3c7139c246f80fadbe8135aacd4b550fdd49a0e33a98342846d39d7d1a7b45abd2d03f1ecbecb9fbdb3401d5ef16b2c52e4bfa3c08edcebfa976e3
7
- data.tar.gz: 34202c48810d9ebc8c9930a1f860acafa9a97b4de8304a22e56fa811a9462f02a857d277fb3abf1572226244ed05aeaafd61d23689a74aad6cdae83c33669fca
6
+ metadata.gz: 804b0a4c05754a5349b19f4b06ee1017f62790e2f5f50696639d9cfc1b785d4f43d688cb05c325f5e10cb1e36fca1e4775c93f91cbf10ad600a5f4755a091a28
7
+ data.tar.gz: cbb64fe3e1c2f5925584032cd69bb626e87c9ff61c7735a570a7e291287874076e366e2282e15d432c18832b9853c749d806cab28aa65716dc91ab835910bd6b
data/README.md CHANGED
@@ -23,7 +23,7 @@ The library has been tested with MRI 3.0, 3.1 and 3.2. Supported platforms are L
23
23
  Add this line to your application's Gemfile:
24
24
 
25
25
  ```ruby
26
- gem "couchbase", "3.4.1"
26
+ gem "couchbase", "3.4.2"
27
27
  ```
28
28
 
29
29
  And then execute:
@@ -139,7 +139,7 @@ Now the API reference is accessible using a web browser (where `VERSION` is the
139
139
 
140
140
  The gem is available as open source under the terms of the [Apache2 License](https://opensource.org/licenses/Apache-2.0).
141
141
 
142
- Copyright 2011-2021 Couchbase, Inc.
142
+ Copyright 2011-Present Couchbase, Inc.
143
143
 
144
144
  Licensed under the Apache License, Version 2.0 (the "License");
145
145
  you may not use this file except in compliance with the License.
@@ -255,6 +255,7 @@ set(couchbase_cxx_client_FILES
255
255
  core/utils/mutation_token.cxx
256
256
  core/utils/split_string.cxx
257
257
  core/utils/url_codec.cxx
258
+ core/impl/analytics.cxx
258
259
  core/impl/analytics_error_category.cxx
259
260
  core/impl/append.cxx
260
261
  core/impl/best_effort_retry_strategy.cxx
@@ -321,6 +322,7 @@ set(couchbase_cxx_client_FILES
321
322
  core/impl/upsert.cxx
322
323
  core/impl/view_error_category.cxx
323
324
  core/impl/watch_query_indexes.cxx
325
+ core/io/dns_client.cxx
324
326
  core/io/dns_config.cxx
325
327
  core/io/http_parser.cxx
326
328
  core/io/mcbp_message.cxx
@@ -18,6 +18,10 @@ set(PEGTL_BUILD_TESTS OFF CACHE BOOL "" FORCE)
18
18
  set(PEGTL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
19
19
  add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/snappy)
20
20
  set_target_properties(snappy PROPERTIES POSITION_INDEPENDENT_CODE ON)
21
+ if(NOT MSVC)
22
+ # https://github.com/google/snappy/pull/156
23
+ target_compile_options(snappy PRIVATE -Wno-sign-compare)
24
+ endif()
21
25
 
22
26
  set(HDR_LOG_REQUIRED OFF CACHE BOOL "" FORCE)
23
27
  set(HDR_HISTOGRAM_BUILD_SHARED OFF CACHE BOOL "" FORCE)
@@ -53,7 +53,6 @@ struct cluster_options {
53
53
  std::chrono::milliseconds analytics_timeout = timeout_defaults::analytics_timeout;
54
54
  std::chrono::milliseconds search_timeout = timeout_defaults::search_timeout;
55
55
  std::chrono::milliseconds management_timeout = timeout_defaults::management_timeout;
56
- std::chrono::milliseconds dns_srv_timeout = timeout_defaults::dns_srv_timeout;
57
56
 
58
57
  bool enable_tls{ false };
59
58
  std::string trust_certificate{};
@@ -22,4 +22,26 @@ couchbase::core::known_profiles()
22
22
  {
23
23
  static couchbase::core::config_profiles profiles{};
24
24
  return profiles;
25
- }
25
+ }
26
+
27
+ void
28
+ couchbase::core::development_profile::apply(couchbase::core::cluster_options& opts)
29
+ {
30
+ opts.key_value_timeout = std::chrono::seconds(20);
31
+ opts.key_value_durable_timeout = std::chrono::seconds(20);
32
+ opts.connect_timeout = std::chrono::seconds(20);
33
+ opts.view_timeout = std::chrono::minutes(2);
34
+ opts.query_timeout = std::chrono::minutes(2);
35
+ opts.analytics_timeout = std::chrono::minutes(2);
36
+ opts.search_timeout = std::chrono::minutes(2);
37
+ opts.management_timeout = std::chrono::minutes(2);
38
+
39
+ // C++SDK specific
40
+ opts.dns_config = couchbase::core::io::dns::dns_config{
41
+ opts.dns_config.nameserver(),
42
+ opts.dns_config.port(),
43
+ std::chrono::seconds(20), // timeout to make DNS-SRV query
44
+ };
45
+ opts.resolve_timeout = std::chrono::seconds(20); // timeout to resolve hostnames
46
+ opts.bootstrap_timeout = std::chrono::minutes(2); // overall timeout to bootstrap
47
+ }
@@ -36,17 +36,7 @@ class config_profile
36
36
  class development_profile : public config_profile
37
37
  {
38
38
  public:
39
- void apply(couchbase::core::cluster_options& opts) override
40
- {
41
- opts.key_value_timeout = std::chrono::milliseconds(20000); // 20 sec kv timeout.
42
- opts.key_value_durable_timeout = std::chrono::milliseconds(20000); // 20 sec durable kv timeout.
43
- opts.connect_timeout = std::chrono::milliseconds(20000); // 20 sec connect timeout.
44
- opts.view_timeout = std::chrono::milliseconds(120000); // 2 minute view timeout
45
- opts.query_timeout = std::chrono::milliseconds(120000); // 2 minute query timeout
46
- opts.analytics_timeout = std::chrono::milliseconds(120000); // 2 minute analytics timeout
47
- opts.search_timeout = std::chrono::milliseconds(120000); // 2 minute search timeout
48
- opts.management_timeout = std::chrono::milliseconds(120000); // 2 minute management timeout
49
- }
39
+ void apply(couchbase::core::cluster_options& opts) override;
50
40
  };
51
41
 
52
42
  // this class just registers the known profiles defined above, and allows access to them.
@@ -89,4 +79,4 @@ class config_profiles
89
79
  config_profiles&
90
80
  known_profiles();
91
81
 
92
- } // namespace couchbase::core
82
+ } // namespace couchbase::core
@@ -0,0 +1,236 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-Present Couchbase, Inc.
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
+ #include <couchbase/cluster.hxx>
19
+ #include <couchbase/error_codes.hxx>
20
+
21
+ #include "core/cluster.hxx"
22
+ #include "core/operations/document_analytics.hxx"
23
+
24
+ namespace couchbase
25
+ {
26
+ static analytics_error_context
27
+ build_context(core::operations::analytics_response& resp)
28
+ {
29
+ return {
30
+ resp.ctx.ec,
31
+ resp.ctx.last_dispatched_to,
32
+ resp.ctx.last_dispatched_from,
33
+ resp.ctx.retry_attempts,
34
+ std::move(resp.ctx.retry_reasons),
35
+ resp.ctx.first_error_code,
36
+ std::move(resp.ctx.first_error_message),
37
+ std::move(resp.ctx.client_context_id),
38
+ std::move(resp.ctx.statement),
39
+ std::move(resp.ctx.parameters),
40
+ std::move(resp.ctx.method),
41
+ std::move(resp.ctx.path),
42
+ resp.ctx.http_status,
43
+ std::move(resp.ctx.http_body),
44
+ std::move(resp.ctx.hostname),
45
+ resp.ctx.port,
46
+ };
47
+ }
48
+
49
+ static analytics_status
50
+ map_status(core::operations::analytics_response::analytics_status status)
51
+ {
52
+ switch (status) {
53
+ case core::operations::analytics_response::running:
54
+ return analytics_status::running;
55
+ case core::operations::analytics_response::success:
56
+ return analytics_status::success;
57
+ case core::operations::analytics_response::errors:
58
+ return analytics_status::errors;
59
+ case core::operations::analytics_response::completed:
60
+ return analytics_status::completed;
61
+ case core::operations::analytics_response::stopped:
62
+ return analytics_status::stopped;
63
+ case core::operations::analytics_response::timedout:
64
+ return analytics_status::timeout;
65
+ case core::operations::analytics_response::closed:
66
+ return analytics_status::closed;
67
+ case core::operations::analytics_response::fatal:
68
+ return analytics_status::fatal;
69
+ case core::operations::analytics_response::aborted:
70
+ return analytics_status::aborted;
71
+ case core::operations::analytics_response::unknown:
72
+ return analytics_status::unknown;
73
+ }
74
+ return analytics_status::unknown;
75
+ }
76
+
77
+ static std::optional<couchbase::core::analytics_scan_consistency>
78
+ map_scan_consistency(std::optional<couchbase::analytics_scan_consistency> consistency)
79
+ {
80
+ if (consistency.has_value()) {
81
+ switch (consistency.value()) {
82
+ case analytics_scan_consistency::not_bounded:
83
+ return couchbase::core::analytics_scan_consistency::not_bounded;
84
+ case analytics_scan_consistency::request_plus:
85
+ return couchbase::core::analytics_scan_consistency::request_plus;
86
+ }
87
+ }
88
+ return {};
89
+ }
90
+
91
+ static std::vector<codec::binary>
92
+ map_rows(const core::operations::analytics_response& resp)
93
+ {
94
+ std::vector<codec::binary> rows;
95
+ rows.reserve(resp.rows.size());
96
+ for (const auto& row : resp.rows) {
97
+ rows.emplace_back(core::utils::to_binary(row));
98
+ }
99
+ return rows;
100
+ }
101
+
102
+ static std::vector<analytics_warning>
103
+ map_warnings(core::operations::analytics_response& resp)
104
+ {
105
+ if (resp.meta.warnings.empty()) {
106
+ return {};
107
+ }
108
+ std::vector<analytics_warning> warnings;
109
+ warnings.reserve(resp.meta.warnings.size());
110
+ for (auto& warning : resp.meta.warnings) {
111
+ warnings.emplace_back(warning.code, std::move(warning.message));
112
+ }
113
+ return warnings;
114
+ }
115
+
116
+ static analytics_metrics
117
+ map_metrics(const core::operations::analytics_response& resp)
118
+ {
119
+ return analytics_metrics{
120
+ resp.meta.metrics.elapsed_time, resp.meta.metrics.execution_time, resp.meta.metrics.result_count,
121
+ resp.meta.metrics.result_size, resp.meta.metrics.processed_objects, resp.meta.metrics.error_count,
122
+ resp.meta.metrics.warning_count,
123
+ };
124
+ }
125
+
126
+ static std::optional<std::vector<std::byte>>
127
+ map_signature(core::operations::analytics_response& resp)
128
+ {
129
+ if (!resp.meta.signature) {
130
+ return {};
131
+ }
132
+ return core::utils::to_binary(resp.meta.signature.value());
133
+ }
134
+
135
+ static analytics_result
136
+ build_result(core::operations::analytics_response& resp)
137
+ {
138
+ return {
139
+ analytics_meta_data{
140
+ std::move(resp.meta.request_id),
141
+ std::move(resp.meta.client_context_id),
142
+ map_status(resp.meta.status),
143
+ map_warnings(resp),
144
+ map_metrics(resp),
145
+ map_signature(resp),
146
+ },
147
+ map_rows(resp),
148
+ };
149
+ }
150
+
151
+ static core::operations::analytics_request
152
+ build_analytics_request(std::string statement,
153
+ analytics_options::built options,
154
+ std::optional<std::string> bucket_name,
155
+ std::optional<std::string> scope_name)
156
+ {
157
+ core::operations::analytics_request request{
158
+ std::move(statement),
159
+ options.readonly,
160
+ options.priority,
161
+ std::move(bucket_name),
162
+ std::move(scope_name),
163
+ {},
164
+ map_scan_consistency(options.scan_consistency),
165
+ {},
166
+ {},
167
+ {},
168
+ {},
169
+ std::move(options.client_context_id),
170
+ options.timeout,
171
+ };
172
+ if (!options.raw.empty()) {
173
+ for (auto& [name, value] : options.raw) {
174
+ request.raw[name] = std::move(value);
175
+ }
176
+ }
177
+ if (!options.positional_parameters.empty()) {
178
+ for (auto& value : options.positional_parameters) {
179
+ request.positional_parameters.emplace_back(std::move(value));
180
+ }
181
+ }
182
+ if (!options.named_parameters.empty()) {
183
+ for (auto& [name, value] : options.named_parameters) {
184
+ request.named_parameters[name] = std::move(value);
185
+ }
186
+ }
187
+ return request;
188
+ }
189
+
190
+ void
191
+ cluster::analytics_query(std::string statement, const analytics_options& options, analytics_handler&& handler) const
192
+ {
193
+ auto request = build_analytics_request(std::move(statement), options.build(), {}, {});
194
+
195
+ core_->execute(std::move(request), [handler = std::move(handler)](core::operations::analytics_response resp) mutable {
196
+ auto r = std::move(resp);
197
+ return handler(build_context(r), build_result(r));
198
+ });
199
+ }
200
+
201
+ auto
202
+ cluster::analytics_query(std::string statement, const analytics_options& options) const
203
+ -> std::future<std::pair<analytics_error_context, analytics_result>>
204
+ {
205
+ auto barrier = std::make_shared<std::promise<std::pair<analytics_error_context, analytics_result>>>();
206
+ auto future = barrier->get_future();
207
+ analytics_query(std::move(statement), options, [barrier](auto ctx, auto result) {
208
+ barrier->set_value({ std::move(ctx), std::move(result) });
209
+ });
210
+ return future;
211
+ }
212
+
213
+ void
214
+ scope::analytics_query(std::string statement, const analytics_options& options, analytics_handler&& handler) const
215
+ {
216
+ auto request = build_analytics_request(std::move(statement), options.build(), bucket_name_, name_);
217
+
218
+ core_->execute(std::move(request), [handler = std::move(handler)](core::operations::analytics_response resp) mutable {
219
+ auto r = std::move(resp);
220
+ return handler(build_context(r), build_result(r));
221
+ });
222
+ }
223
+
224
+ auto
225
+ scope::analytics_query(std::string statement, const analytics_options& options) const
226
+ -> std::future<std::pair<analytics_error_context, analytics_result>>
227
+ {
228
+ auto barrier = std::make_shared<std::promise<std::pair<analytics_error_context, analytics_result>>>();
229
+ auto future = barrier->get_future();
230
+ analytics_query(std::move(statement), options, [barrier](auto ctx, auto result) {
231
+ barrier->set_value({ std::move(ctx), std::move(result) });
232
+ });
233
+ return future;
234
+ }
235
+
236
+ } // namespace couchbase
@@ -95,7 +95,6 @@ options_to_origin(const std::string& connection_string, const couchbase::cluster
95
95
  if (opts.dns.nameserver) {
96
96
  user_options.dns_config =
97
97
  io::dns::dns_config(opts.dns.nameserver.value(), opts.dns.port.value_or(io::dns::dns_config::default_port), opts.dns.timeout);
98
- user_options.dns_srv_timeout = opts.dns.timeout;
99
98
  }
100
99
  user_options.enable_clustermap_notification = opts.behavior.enable_clustermap_notification;
101
100
  user_options.show_queries = opts.behavior.show_queries;
@@ -16,11 +16,13 @@
16
16
  */
17
17
 
18
18
  #include "dns_srv_tracker.hxx"
19
- #include "../logger/logger.hxx"
20
- #include "../topology/configuration.hxx"
19
+
20
+ #include "core/logger/logger.hxx"
21
+ #include "core/utils/join_strings.hxx"
21
22
 
22
23
  #include <asio/bind_executor.hpp>
23
24
  #include <asio/io_context.hpp>
25
+ #include <asio/post.hpp>
24
26
 
25
27
  #include <memory>
26
28
 
@@ -44,7 +46,7 @@ dns_srv_tracker::get_srv_nodes(utils::movable_function<void(origin::node_list, s
44
46
  address_,
45
47
  service_,
46
48
  config_,
47
- [self = shared_from_this(), callback = std::move(callback)](couchbase::core::io::dns::dns_client::dns_srv_response&& resp) mutable {
49
+ [self = shared_from_this(), callback = std::move(callback)](couchbase::core::io::dns::dns_srv_response&& resp) mutable {
48
50
  origin::node_list nodes;
49
51
  if (resp.ec) {
50
52
  CB_LOG_WARNING("failed to fetch DNS SRV records for \"{}\" ({}), assuming that cluster is listening this address",
@@ -152,10 +152,10 @@ build_result(operations::query_response& resp)
152
152
  }
153
153
 
154
154
  static core::operations::query_request
155
- build_query_request(query_options::built options)
155
+ build_query_request(std::string statement, query_options::built options)
156
156
  {
157
157
  operations::query_request request{
158
- "",
158
+ std::move(statement),
159
159
  options.adhoc,
160
160
  options.metrics,
161
161
  options.readonly,
@@ -222,8 +222,9 @@ build_transaction_query_result(operations::query_response resp, std::error_code
222
222
  core::operations::query_request
223
223
  build_transaction_query_request(query_options::built opts)
224
224
  {
225
- return build_query_request(opts);
225
+ return build_query_request("", opts);
226
226
  }
227
+
227
228
  void
228
229
  initiate_query_operation(std::shared_ptr<couchbase::core::cluster> core,
229
230
  std::string statement,
@@ -231,8 +232,7 @@ initiate_query_operation(std::shared_ptr<couchbase::core::cluster> core,
231
232
  query_options::built options,
232
233
  query_handler&& handler)
233
234
  {
234
- auto request = build_query_request(options);
235
- request.statement = std::move(statement);
235
+ auto request = build_query_request(std::move(statement), options);
236
236
  if (query_context) {
237
237
  request.query_context = std::move(query_context);
238
238
  }
@@ -0,0 +1,225 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-Present Couchbase, Inc.
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
+ #include "dns_client.hxx"
19
+
20
+ #include "core/logger/logger.hxx"
21
+ #include "core/utils/join_strings.hxx"
22
+ #include "dns_codec.hxx"
23
+ #include "dns_config.hxx"
24
+
25
+ #include <couchbase/error_codes.hxx>
26
+
27
+ #include <asio/ip/tcp.hpp>
28
+ #include <asio/read.hpp>
29
+ #include <asio/write.hpp>
30
+
31
+ #include <memory>
32
+ #include <sstream>
33
+
34
+ namespace couchbase::core::io::dns
35
+ {
36
+ class dns_srv_command : public std::enable_shared_from_this<dns_srv_command>
37
+ {
38
+ public:
39
+ dns_srv_command(asio::io_context& ctx,
40
+ const std::string& name,
41
+ const std::string& service,
42
+ const asio::ip::address& address,
43
+ std::uint16_t port,
44
+ utils::movable_function<void(couchbase::core::io::dns::dns_srv_response&& resp)> handler)
45
+ : deadline_(ctx)
46
+ , udp_deadline_(ctx)
47
+ , udp_(ctx)
48
+ , tcp_(ctx)
49
+ , address_(address)
50
+ , port_(port)
51
+ , handler_(std::move(handler))
52
+ {
53
+ static std::string protocol{ "_tcp" };
54
+ dns_message request{};
55
+ question_record qr;
56
+ qr.klass = resource_class::in;
57
+ qr.type = resource_type::srv;
58
+ qr.name.labels.push_back(service);
59
+ qr.name.labels.push_back(protocol);
60
+ std::string label;
61
+ std::istringstream name_stream(name);
62
+ while (std::getline(name_stream, label, '.')) {
63
+ qr.name.labels.push_back(label);
64
+ }
65
+ request.questions.emplace_back(qr);
66
+ send_buf_ = dns_codec::encode(request);
67
+ }
68
+
69
+ void execute(std::chrono::milliseconds total_timeout, std::chrono::milliseconds udp_timeout)
70
+ {
71
+ asio::ip::udp::endpoint endpoint(address_, port_);
72
+ udp_.open(endpoint.protocol());
73
+ udp_.async_send_to(
74
+ asio::buffer(send_buf_), endpoint, [self = shared_from_this()](std::error_code ec1, std::size_t /* bytes_transferred */) mutable {
75
+ if (ec1) {
76
+ self->udp_deadline_.cancel();
77
+ CB_LOG_DEBUG("DNS UDP write operation has got error {}, retrying with TCP", ec1.message());
78
+ return self->retry_with_tcp();
79
+ }
80
+
81
+ self->recv_buf_.resize(512);
82
+ self->udp_.async_receive_from(
83
+ asio::buffer(self->recv_buf_), self->udp_sender_, [self](std::error_code ec2, std::size_t bytes_transferred) mutable {
84
+ self->udp_deadline_.cancel();
85
+ if (ec2) {
86
+ CB_LOG_DEBUG("DNS UDP read operation has got error {}, retrying with TCP", ec2.message());
87
+ return self->retry_with_tcp();
88
+ }
89
+ self->recv_buf_.resize(bytes_transferred);
90
+ const dns_message message = dns_codec::decode(self->recv_buf_);
91
+ if (message.header.flags.tc == truncation::yes) {
92
+ self->udp_.close();
93
+ CB_LOG_DEBUG("DNS UDP read operation returned truncated response, retrying with TCP");
94
+ return self->retry_with_tcp();
95
+ }
96
+ self->deadline_.cancel();
97
+ dns_srv_response resp{ ec2 };
98
+ resp.targets.reserve(message.answers.size());
99
+ for (const auto& answer : message.answers) {
100
+ resp.targets.emplace_back(dns_srv_response::address{ utils::join_strings(answer.target.labels, "."), answer.port });
101
+ }
102
+ CB_LOG_DEBUG("DNS UDP returned {} records", resp.targets.size());
103
+ return self->handler_(std::move(resp));
104
+ });
105
+ });
106
+ udp_deadline_.expires_after(udp_timeout);
107
+ deadline_.async_wait([self = shared_from_this()](std::error_code ec) {
108
+ if (ec == asio::error::operation_aborted) {
109
+ return;
110
+ }
111
+ CB_LOG_DEBUG("DNS UDP deadline has been reached, cancelling UDP operation and fall back to TCP");
112
+ self->udp_.cancel();
113
+ return self->retry_with_tcp();
114
+ });
115
+
116
+ deadline_.expires_after(total_timeout);
117
+ deadline_.async_wait([self = shared_from_this()](std::error_code ec) {
118
+ if (ec == asio::error::operation_aborted) {
119
+ return;
120
+ }
121
+ CB_LOG_DEBUG("DNS deadline has been reached, cancelling in-flight operations (tcp.is_open={})", self->tcp_.is_open());
122
+ self->udp_.cancel();
123
+ if (self->tcp_.is_open()) {
124
+ self->tcp_.cancel();
125
+ }
126
+ });
127
+ }
128
+
129
+ private:
130
+ void retry_with_tcp()
131
+ {
132
+ if (bool expected_state{ false }; !retrying_with_tcp_.compare_exchange_strong(expected_state, true)) {
133
+ return;
134
+ }
135
+
136
+ const asio::ip::tcp::no_delay no_delay(true);
137
+ std::error_code ignore_ec;
138
+ tcp_.set_option(no_delay, ignore_ec);
139
+ const asio::ip::tcp::endpoint endpoint(address_, port_);
140
+ tcp_.async_connect(endpoint, [self = shared_from_this()](std::error_code ec1) mutable {
141
+ if (ec1) {
142
+ self->deadline_.cancel();
143
+ CB_LOG_DEBUG("DNS TCP connection has been aborted, {}", ec1.message());
144
+ return self->handler_({ ec1 });
145
+ }
146
+ auto send_size = static_cast<std::uint16_t>(self->send_buf_.size());
147
+ self->send_buf_.insert(self->send_buf_.begin(), static_cast<std::uint8_t>(send_size & 0xffU));
148
+ self->send_buf_.insert(self->send_buf_.begin(), static_cast<std::uint8_t>(send_size >> 8U));
149
+ asio::async_write(
150
+ self->tcp_, asio::buffer(self->send_buf_), [self](std::error_code ec2, std::size_t /* bytes_transferred */) mutable {
151
+ if (ec2) {
152
+ CB_LOG_DEBUG("DNS TCP write operation has been aborted, {}", ec2.message());
153
+ self->deadline_.cancel();
154
+ if (ec2 == asio::error::operation_aborted) {
155
+ ec2 = errc::common::unambiguous_timeout;
156
+ }
157
+ return self->handler_({ ec2 });
158
+ }
159
+ asio::async_read(self->tcp_,
160
+ asio::buffer(&self->recv_buf_size_, sizeof(std::uint16_t)),
161
+ [self](std::error_code ec3, std::size_t /* bytes_transferred */) mutable {
162
+ if (ec3) {
163
+ CB_LOG_DEBUG("DNS TCP buf size read operation has been aborted, {}", ec3.message());
164
+ self->deadline_.cancel();
165
+ return self->handler_({ ec3 });
166
+ }
167
+ self->recv_buf_size_ = utils::byte_swap(self->recv_buf_size_);
168
+ self->recv_buf_.resize(self->recv_buf_size_);
169
+ CB_LOG_DEBUG("DNS TCP schedule read of {} bytes", self->recv_buf_size_);
170
+ asio::async_read(self->tcp_,
171
+ asio::buffer(self->recv_buf_),
172
+ [self](std::error_code ec4, std::size_t bytes_transferred) mutable {
173
+ self->deadline_.cancel();
174
+ if (ec4) {
175
+ CB_LOG_DEBUG("DNS TCP read operation has been aborted, {}", ec4.message());
176
+ return self->handler_({ ec4 });
177
+ }
178
+ self->recv_buf_.resize(bytes_transferred);
179
+ const dns_message message = dns_codec::decode(self->recv_buf_);
180
+ dns_srv_response resp{ ec4 };
181
+ resp.targets.reserve(message.answers.size());
182
+ for (const auto& answer : message.answers) {
183
+ resp.targets.emplace_back(dns_srv_response::address{
184
+ utils::join_strings(answer.target.labels, "."), answer.port });
185
+ }
186
+ CB_LOG_DEBUG("DNS TCP returned {} records", resp.targets.size());
187
+ return self->handler_(std::move(resp));
188
+ });
189
+ });
190
+ });
191
+ });
192
+ }
193
+
194
+ asio::steady_timer deadline_;
195
+ asio::steady_timer udp_deadline_;
196
+ asio::ip::udp::socket udp_;
197
+ asio::ip::udp::endpoint udp_sender_{};
198
+ asio::ip::tcp::socket tcp_;
199
+
200
+ asio::ip::address address_;
201
+ std::uint16_t port_;
202
+ utils::movable_function<void(couchbase::core::io::dns::dns_srv_response&& resp)> handler_;
203
+
204
+ std::vector<std::uint8_t> send_buf_{};
205
+ std::uint16_t recv_buf_size_{ 0 };
206
+ std::vector<std::uint8_t> recv_buf_{};
207
+
208
+ std::atomic_bool retrying_with_tcp_{ false };
209
+ };
210
+
211
+ void
212
+ dns_client::query_srv(const std::string& name,
213
+ const std::string& service,
214
+ const dns_config& config,
215
+ utils::movable_function<void(dns_srv_response&&)>&& handler)
216
+ {
217
+ std::error_code ec;
218
+ auto address = asio::ip::address::from_string(config.nameserver(), ec);
219
+ if (ec) {
220
+ return handler({ ec });
221
+ }
222
+ auto cmd = std::make_shared<dns_srv_command>(ctx_, name, service, address, config.port(), std::move(handler));
223
+ return cmd->execute(config.timeout(), config.timeout() / 2);
224
+ }
225
+ } // namespace couchbase::core::io::dns