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