1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * Copyright 2013 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 "internal.h"
19 #include "clconfig.h"
20 #include <list>
21 #include <algorithm>
22 #include "trace.h"
23
24 #define LOGARGS(mon, lvlbase) mon->settings, "confmon", LCB_LOG_##lvlbase, __FILE__, __LINE__
25 #define LOG(mon, lvlbase, msg) lcb_log(mon->settings, "confmon", LCB_LOG_##lvlbase, __FILE__, __LINE__, msg)
26
27 using namespace lcb::clconfig;
28
next_active(Provider * cur)29 Provider* Confmon::next_active(Provider *cur) {
30 ProviderList::iterator ii = std::find(
31 active_providers.begin(), active_providers.end(), cur);
32
33 if (ii == active_providers.end() || (++ii) == active_providers.end()) {
34 return NULL;
35 } else {
36 return *ii;
37 }
38 }
39
first_active()40 Provider* Confmon::first_active()
41 {
42 if (active_providers.empty()) {
43 return NULL;
44 } else {
45 return active_providers.front();
46 }
47 }
48
49 static const char *
provider_string(Method type)50 provider_string(Method type) {
51 if (type == CLCONFIG_HTTP) { return "HTTP"; }
52 if (type == CLCONFIG_CCCP) { return "CCCP"; }
53 if (type == CLCONFIG_FILE) { return "FILE"; }
54 if (type == CLCONFIG_MCRAW) { return "MCRAW"; }
55 if (type == CLCONFIG_CLADMIN) { return "CLADMIN"; }
56 return "";
57 }
58
Confmon(lcb_settings * settings_,lcbio_pTABLE iot_,lcb_t instance_)59 Confmon::Confmon(lcb_settings *settings_, lcbio_pTABLE iot_, lcb_t instance_)
60 : cur_provider(NULL), config(NULL), settings(settings_), last_error(LCB_SUCCESS), iot(iot_), as_start(iot_, this),
61 as_stop(iot_, this), state(0), last_stop_us(0), instance(instance_), active_provider_list_id(0)
62 {
63
64 lcbio_table_ref(iot);
65 lcb_settings_ref(settings);
66
67 all_providers[CLCONFIG_FILE] = new_file_provider(this);
68 all_providers[CLCONFIG_CCCP] = new_cccp_provider(this);
69 all_providers[CLCONFIG_HTTP] = new_http_provider(this);
70 all_providers[CLCONFIG_MCRAW] = new_mcraw_provider(this);
71 all_providers[CLCONFIG_CLADMIN] = new_cladmin_provider(this);
72
73 for (size_t ii = 0; ii < CLCONFIG_MAX; ii++) {
74 all_providers[ii]->parent = this;
75 }
76 }
77
prepare()78 void Confmon::prepare() {
79 ++this->active_provider_list_id;
80 active_providers.clear();
81 lcb_log(LOGARGS(this, DEBUG), "Preparing providers (this may be called multiple times)");
82
83 for (size_t ii = 0; ii < CLCONFIG_MAX; ii++) {
84 Provider *cur = all_providers[ii];
85 if (cur) {
86 if (cur->enabled) {
87 active_providers.push_back(cur);
88 lcb_log(LOGARGS(this, DEBUG), "Provider %s is ENABLED", provider_string(cur->type));
89 } else if (cur->pause()) {
90 lcb_log(LOGARGS(this, DEBUG), "Provider %s is DISABLED", provider_string(cur->type));
91 }
92 }
93 }
94
95 lcb_assert(!active_providers.empty());
96 cur_provider = first_active();
97 }
98
~Confmon()99 Confmon::~Confmon() {
100 as_start.release();
101 as_stop.release();
102
103 if (config) {
104 config->decref();
105 config = NULL;
106 }
107
108 for (size_t ii = 0; ii < CLCONFIG_MAX; ii++) {
109 Provider *provider = all_providers[ii];
110 if (provider == NULL) {
111 continue;
112 }
113 delete provider;
114 all_providers[ii] = NULL;
115 }
116
117 lcbio_table_unref(iot);
118 lcb_settings_unref(settings);
119 }
120
do_set_next(ConfigInfo * new_config,bool notify_miss)121 int Confmon::do_set_next(ConfigInfo *new_config, bool notify_miss)
122 {
123 unsigned ii;
124
125 if (config && new_config == config) {
126 return 0;
127 }
128 if (config) {
129 lcbvb_CHANGETYPE chstatus = LCBVB_NO_CHANGES;
130 lcbvb_CONFIGDIFF *diff = lcbvb_compare(config->vbc, new_config->vbc);
131
132 if (!diff) {
133 lcb_log(LOGARGS(this, DEBUG), "Couldn't create vbucket diff");
134 return 0;
135 }
136
137 chstatus = lcbvb_get_changetype(diff);
138 lcbvb_free_diff(diff);
139
140 if (chstatus == 0 || config->compare(*new_config) >= 0) {
141 const lcbvb_CONFIG *ca, *cb;
142
143 ca = config->vbc;
144 cb = new_config->vbc;
145
146 lcb_log(LOGARGS(this, TRACE), "Not applying configuration received via %s. No changes detected. A.rev=%d, B.rev=%d", provider_string(new_config->get_origin()), ca->revid, cb->revid);
147 if (notify_miss) {
148 invoke_listeners(CLCONFIG_EVENT_GOT_ANY_CONFIG, new_config);
149 }
150 return 0;
151 }
152 }
153
154 lcb_log(LOGARGS(this, INFO), "Setting new configuration. Received via %s", provider_string(new_config->get_origin()));
155 TRACE_NEW_CONFIG(instance, new_config);
156
157 if (config) {
158 /** DECREF the old one */
159 config->decref();
160 }
161
162 for (ii = 0; ii < CLCONFIG_MAX; ii++) {
163 Provider *cur = all_providers[ii];
164 if (cur && cur->enabled) {
165 cur->config_updated(new_config->vbc);
166 }
167 }
168
169 new_config->incref();
170 config = new_config;
171 stop();
172
173 invoke_listeners(CLCONFIG_EVENT_GOT_NEW_CONFIG, new_config);
174
175 return 1;
176 }
177
provider_failed(Provider * provider,lcb_error_t reason)178 void Confmon::provider_failed(Provider *provider, lcb_error_t reason) {
179 lcb_log(LOGARGS(this, INFO), "Provider '%s' failed", provider_string(provider->type));
180
181 if (provider != cur_provider) {
182 lcb_log(LOGARGS(this, TRACE), "Ignoring failure. Current=%p (%s)", (void*)cur_provider, provider_string(cur_provider->type));
183 return;
184 }
185 if (!is_refreshing()) {
186 lcb_log(LOGARGS(this, DEBUG), "Ignoring failure. Refresh not active");
187 }
188
189 if (reason != LCB_SUCCESS) {
190 if (settings->detailed_neterr && last_error != LCB_SUCCESS) {
191 /* Filter out any artificial 'connect error' or 'network error' codes */
192 if (reason != LCB_CONNECT_ERROR && reason != LCB_NETWORK_ERROR) {
193 last_error = reason;
194 }
195 } else {
196 last_error = reason;
197 }
198 if (reason == LCB_AUTH_ERROR) {
199 goto GT_ERROR;
200 }
201 }
202
203 if (settings->conntype == LCB_TYPE_CLUSTER && provider->type == CLCONFIG_HTTP) {
204 Provider *cladmin = get_provider(CLCONFIG_CLADMIN);
205 if (!cladmin->enabled) {
206 cladmin->enable();
207 cladmin->configure_nodes(*provider->get_nodes());
208 active_providers.push_back(cladmin);
209 lcb_log(LOGARGS(this, DEBUG), "Static configuration provider has been enabled");
210 }
211 }
212
213 cur_provider = next_active(cur_provider);
214 if (cur_provider) {
215 uint32_t interval = 0;
216 if (config) {
217 /* Not first */
218 interval = settings->grace_next_provider;
219 }
220 lcb_log(LOGARGS(this, DEBUG), "Will try next provider in %uus", interval);
221 state |= CONFMON_S_ITERGRACE;
222 as_start.rearm(interval);
223 return;
224 } else {
225 LOG(this, TRACE, "Maximum provider reached. Resetting index");
226 }
227
228 GT_ERROR:
229 invoke_listeners(CLCONFIG_EVENT_PROVIDERS_CYCLED, NULL);
230 cur_provider = first_active();
231 stop();
232 }
233
provider_got_config(Provider *,ConfigInfo * config_)234 void Confmon::provider_got_config(Provider *, ConfigInfo *config_) {
235 do_set_next(config_, true);
236 stop();
237 }
238
do_next_provider()239 void Confmon::do_next_provider()
240 {
241 state &= ~CONFMON_S_ITERGRACE;
242 size_t previous_active_provider_list_id = this->active_provider_list_id;
243 ProviderList::const_iterator ii = active_providers.begin();
244 while (ii != active_providers.end()) {
245 if (previous_active_provider_list_id != this->active_provider_list_id) {
246 ii = active_providers.begin();
247 previous_active_provider_list_id = this->active_provider_list_id;
248 }
249
250 Provider *cached_provider = *ii;
251 ++ii;
252 if (!cached_provider) {
253 continue;
254 }
255 ConfigInfo *info = cached_provider->get_cached();
256 if (!info) {
257 continue;
258 }
259
260 if (do_set_next(info, false)) {
261 LOG(this, DEBUG, "Using cached configuration");
262 }
263 }
264
265 lcb_log(LOGARGS(this, TRACE), "Attempting to retrieve cluster map via %s", provider_string(cur_provider->type));
266
267 cur_provider->refresh();
268 }
269
start()270 void Confmon::start() {
271 lcb_U32 tmonext = 0;
272 as_stop.cancel();
273 if (is_refreshing()) {
274 LOG(this, DEBUG, "Cluster map refresh already in progress");
275 return;
276 }
277
278 LOG(this, TRACE, "Refreshing current cluster map");
279 lcb_assert(cur_provider);
280 state = CONFMON_S_ACTIVE|CONFMON_S_ITERGRACE;
281
282 if (last_stop_us > 0) {
283 lcb_U32 diff = LCB_NS2US(gethrtime()) - last_stop_us;
284 if (diff <= settings->grace_next_cycle) {
285 tmonext = settings->grace_next_cycle - diff;
286 }
287 }
288
289 as_start.rearm(tmonext);
290 }
291
stop_real()292 void Confmon::stop_real() {
293 ProviderList::const_iterator ii;
294 for (ii = active_providers.begin(); ii != active_providers.end(); ++ii) {
295 (*ii)->pause();
296 }
297
298 last_stop_us = LCB_NS2US(gethrtime());
299 invoke_listeners(CLCONFIG_EVENT_MONITOR_STOPPED, NULL);
300 }
301
stop()302 void Confmon::stop() {
303 if (!is_refreshing()) {
304 return;
305 }
306 as_start.cancel();
307 as_stop.cancel();
308 state = CONFMON_S_INACTIVE;
309 }
310
Provider(Confmon * parent_,Method type_)311 Provider::Provider(Confmon *parent_, Method type_)
312 : type(type_), enabled(false), parent(parent_) {
313 }
314
~Provider()315 Provider::~Provider() {
316 parent = NULL;
317 }
318
~ConfigInfo()319 ConfigInfo::~ConfigInfo() {
320 if (vbc) {
321 lcbvb_destroy(vbc);
322 }
323 }
324
compare(const ConfigInfo & other)325 int ConfigInfo::compare(const ConfigInfo& other) {
326 /** First check if both have revisions */
327 int rev_a, rev_b;
328 rev_a = lcbvb_get_revision(this->vbc);
329 rev_b = lcbvb_get_revision(other.vbc);
330 if (rev_a >= 0 && rev_b >= 0) {
331 return rev_a - rev_b;
332 }
333
334 if (this->cmpclock == other.cmpclock) {
335 return 0;
336
337 } else if (this->cmpclock < other.cmpclock) {
338 return -1;
339 }
340
341 return 1;
342 }
343
ConfigInfo(lcbvb_CONFIG * config_,Method origin_)344 ConfigInfo::ConfigInfo(lcbvb_CONFIG *config_, Method origin_)
345 : vbc(config_), cmpclock(gethrtime()), refcount(1), origin(origin_) {
346 }
347
add_listener(Listener * lsn)348 void Confmon::add_listener(Listener *lsn) {
349 listeners.push_back(lsn);
350 }
351
remove_listener(Listener * lsn)352 void Confmon::remove_listener(Listener *lsn) {
353 listeners.remove(lsn);
354 }
355
invoke_listeners(EventType event,ConfigInfo * info)356 void Confmon::invoke_listeners(EventType event, ConfigInfo *info) {
357 ListenerList::iterator ii = listeners.begin();
358 while (ii != listeners.end()) {
359 ListenerList::iterator cur = ii++;
360 (*cur)->clconfig_lsn(event, info);
361 }
362 }
363
364
set_active(Method type,bool enabled)365 void Confmon::set_active(Method type, bool enabled)
366 {
367 Provider *provider = all_providers[type];
368 if (provider->enabled == enabled) {
369 return;
370 } else {
371 provider->enabled = enabled;
372 }
373 prepare();
374 }
375
dump(FILE * fp)376 void Confmon::dump(FILE *fp) {
377 fprintf(fp, "CONFMON=%p\n", (void*)this);
378 fprintf(fp, "STATE= (0x%x)", state);
379 if (state & CONFMON_S_ACTIVE) {
380 fprintf(fp, "ACTIVE|");
381 }
382 if (state == CONFMON_S_INACTIVE) {
383 fprintf(fp, "INACTIVE/IDLE");
384 }
385 if (state & CONFMON_S_ITERGRACE) {
386 fprintf(fp, "ITERGRACE");
387 }
388 fprintf(fp, "\n");
389 fprintf(fp, "LAST ERROR: 0x%x\n", last_error);
390
391
392 for (size_t ii = 0; ii < CLCONFIG_MAX; ii++) {
393 Provider *cur = all_providers[ii];
394 if (!cur) {
395 continue;
396 }
397
398 fprintf(fp, "** PROVIDER: 0x%x (%s) %p\n", cur->type, provider_string(cur->type), (void*)cur);
399 fprintf(fp, "** ENABLED: %s\n", cur->enabled ? "YES" : "NO");
400 fprintf(fp, "** CURRENT: %s\n", cur == cur_provider ? "YES" : "NO");
401 cur->dump(fp);
402 fprintf(fp, "\n");
403 }
404 }
405