1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2014-2020 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#define LCB_BOOTSTRAP_DEFINE_STRUCT 1
19#include "internal.h"
20#include "defer.h"
21
22#define LOGARGS(instance, lvl) instance->settings, "bootstrap", LCB_LOG_##lvl, __FILE__, __LINE__
23
24using lcb::clconfig::ConfigInfo;
25using lcb::clconfig::EventType;
26using namespace lcb;
27
28/**
29 * This function is where the configuration actually takes place. We ensure
30 * in other functions that this is only ever called directly from an event
31 * loop stack frame (or one of the small mini functions here) so that we
32 * don't accidentally end up destroying resources underneath us.
33 */
34void Bootstrap::config_callback(EventType event, ConfigInfo *info)
35{
36    using namespace lcb::clconfig;
37    lcb_INSTANCE *instance = parent;
38
39    if (event != CLCONFIG_EVENT_GOT_NEW_CONFIG) {
40        if (event == CLCONFIG_EVENT_PROVIDERS_CYCLED && !LCBT_VBCONFIG(instance)) {
41            if (parent->confmon->get_last_error() == LCB_ERR_CONNECTION_REFUSED) {
42                initial_error(LCB_ERR_NO_MATCHING_SERVER,
43                              "Unable to bootstrap, check ports and cluster encryption setting");
44            } else {
45                initial_error(LCB_ERR_NO_MATCHING_SERVER, "No more bootstrap providers remain");
46            }
47        }
48        return;
49    }
50
51    instance->last_error = LCB_SUCCESS;
52
53    /** Ensure we're not called directly twice again */
54    if (state < S_INITIAL_TRIGGERED) {
55        state = S_INITIAL_TRIGGERED;
56    }
57
58    tm.cancel();
59
60    if (info->get_origin() != CLCONFIG_FILE) {
61        /* Set the timestamp for the current config to control throttling,
62         * but only if it's not an initial file-based config. See CCBC-482 */
63        last_refresh = gethrtime();
64        errcounter = 0;
65    }
66
67    if (info->get_origin() == CLCONFIG_CCCP) {
68        /* Disable HTTP provider if we've received something via CCCP */
69
70        if (instance->cur_configinfo == nullptr || instance->cur_configinfo->get_origin() != CLCONFIG_HTTP) {
71            /* Never disable HTTP if it's still being used */
72            instance->confmon->set_active(CLCONFIG_HTTP, false);
73        }
74    }
75
76    if (instance->settings->conntype == LCB_TYPE_CLUSTER && info->get_origin() == CLCONFIG_CLADMIN) {
77        /* Disable HTTP provider for management operations, and fallback to static */
78        if (instance->cur_configinfo == nullptr || instance->cur_configinfo->get_origin() != CLCONFIG_HTTP) {
79            instance->confmon->set_active(CLCONFIG_HTTP, false);
80        }
81    }
82
83    if (instance->cur_configinfo) {
84        if (!(LCBVB_CCAPS(LCBT_VBCONFIG(instance)) & LCBVB_CCAP_N1QL_ENHANCED_PREPARED_STATEMENTS) &&
85            (LCBVB_CCAPS(info->vbc) & LCBVB_CCAP_N1QL_ENHANCED_PREPARED_STATEMENTS)) {
86            lcb_n1qlcache_clear(instance->n1ql_cache);
87        }
88    }
89    lcb_update_vbconfig(instance, info);
90
91    if (state < S_BOOTSTRAPPED) {
92        state = S_BOOTSTRAPPED;
93        lcb_aspend_del(&instance->pendops, LCB_PENDTYPE_COUNTER, nullptr);
94
95        lcb_log(LOGARGS(instance, INFO), "Selected network configuration: \"%s\"", LCBT_SETTING(instance, network));
96        if (instance->settings->conntype == LCB_TYPE_BUCKET) {
97            if (LCBVB_DISTTYPE(LCBT_VBCONFIG(instance)) == LCBVB_DIST_KETAMA &&
98                instance->cur_configinfo->get_origin() != CLCONFIG_MCRAW) {
99                lcb_log(LOGARGS(instance, INFO), "Reverting to HTTP Config for memcached buckets");
100                instance->settings->bc_http_stream_time = -1;
101                instance->confmon->set_active(CLCONFIG_HTTP, true);
102                instance->confmon->set_active(CLCONFIG_CCCP, false);
103            }
104
105            if ((LCBVB_CAPS(LCBT_VBCONFIG(instance)) & LCBVB_CAP_COLLECTIONS) == 0) {
106                LCBT_SETTING(parent, use_collections) = 0;
107            }
108
109            if (LCBVB_CAPS(LCBT_VBCONFIG(instance)) & LCBVB_CAP_DURABLE_WRITE) {
110                LCBT_SETTING(parent, enable_durable_write) = 1;
111            } else {
112                LCBT_SETTING(parent, enable_durable_write) = 0;
113            }
114
115            /* infer bucket type using distribution and capabilities set */
116            switch (LCBVB_DISTTYPE(LCBT_VBCONFIG(instance))) {
117                case LCBVB_DIST_VBUCKET:
118                    if (LCBVB_CAPS(LCBT_VBCONFIG(instance)) & LCBVB_CAP_COUCHAPI) {
119                        instance->btype = LCB_BTYPE_COUCHBASE;
120                    } else {
121                        instance->btype = LCB_BTYPE_EPHEMERAL;
122                    }
123                    break;
124                case LCBVB_DIST_KETAMA:
125                    instance->btype = LCB_BTYPE_MEMCACHED;
126                    break;
127                case LCBVB_DIST_UNKNOWN:
128                    instance->btype = LCB_BTYPE_UNSPEC;
129                    break;
130            }
131        }
132        if (instance->callbacks.bootstrap) {
133            instance->callbacks.bootstrap(instance, LCB_SUCCESS);
134            instance->callbacks.bootstrap = nullptr;
135        }
136        if (instance->callbacks.open && LCBT_VBCONFIG(instance)->bname) {
137            instance->callbacks.open(instance, LCB_SUCCESS);
138            instance->callbacks.open = nullptr;
139        }
140        lcb::execute_deferred_operations(instance);
141
142        // See if we can enable background polling.
143        check_bgpoll();
144    }
145
146    lcb_maybe_breakout(instance);
147}
148
149const char *provider_string(clconfig::Method type);
150
151void Bootstrap::clconfig_lsn(EventType e, ConfigInfo *i)
152{
153    if (state == S_INITIAL_PRE) {
154        config_callback(e, i);
155    } else if (e == clconfig::CLCONFIG_EVENT_GOT_NEW_CONFIG) {
156        lcb_log(
157            LOGARGS(parent, INFO),
158            "Got new config (source=%s, bucket=%.*s, epoch=%" PRId64 ", rev=%" PRId64 "). Will refresh asynchronously",
159            provider_string(i->get_origin()), (int)i->vbc->bname_len, i->vbc->bname, i->vbc->revepoch, i->vbc->revid);
160        tm.signal();
161    }
162}
163
164void Bootstrap::check_bgpoll()
165{
166    if (parent->cur_configinfo == nullptr || parent->cur_configinfo->get_origin() != lcb::clconfig::CLCONFIG_CCCP ||
167        LCBT_SETTING(parent, config_poll_interval) == 0) {
168        tmpoll.cancel();
169    } else {
170        tmpoll.rearm(LCBT_SETTING(parent, config_poll_interval));
171    }
172}
173
174void Bootstrap::bgpoll()
175{
176    lcb_log(LOGARGS(parent, TRACE), "Background-polling for new configuration");
177    bootstrap(BS_REFRESH_ALWAYS);
178    check_bgpoll();
179}
180
181/**
182 * This it the initial bootstrap timeout handler. This timeout pins down the
183 * instance. It is only scheduled during the initial bootstrap and is only
184 * triggered if the initial bootstrap fails to configure in time.
185 */
186void Bootstrap::timer_dispatch()
187{
188    if (state > S_INITIAL_PRE) {
189        auto *config = parent->confmon->get_config();
190        if (config != nullptr) {
191            config_callback(clconfig::CLCONFIG_EVENT_GOT_NEW_CONFIG, config);
192        }
193    } else {
194        // Not yet bootstrapped!
195        initial_error(LCB_ERR_TIMEOUT, "Failed to bootstrap in time");
196    }
197}
198
199void Bootstrap::initial_error(lcb_STATUS err, const char *errinfo)
200{
201    parent->last_error = parent->confmon->get_last_error();
202    if (parent->last_error == LCB_SUCCESS) {
203        parent->last_error = err;
204    }
205    lcb_log(LOGARGS(parent, ERR), "Failed to bootstrap client=%p. Error=%s (Last=%s), Message=\"%s\"", (void *)parent,
206            lcb_strerror_short(err), lcb_strerror_short(parent->last_error), errinfo);
207    tm.cancel();
208
209    if (parent->callbacks.bootstrap) {
210        parent->callbacks.bootstrap(parent, parent->last_error);
211        parent->callbacks.bootstrap = nullptr;
212    }
213    if (parent->callbacks.open) {
214        parent->callbacks.open(parent, parent->last_error);
215        parent->callbacks.open = nullptr;
216    }
217
218    lcb_aspend_del(&parent->pendops, LCB_PENDTYPE_COUNTER, nullptr);
219    lcb_maybe_breakout(parent);
220}
221
222Bootstrap::Bootstrap(lcb_INSTANCE *instance)
223    : parent(instance), tm(parent->iotable, this), tmpoll(parent->iotable, this), last_refresh(0), errcounter(0),
224      state(S_INITIAL_PRE)
225{
226    parent->confmon->add_listener(this);
227}
228
229lcb_STATUS Bootstrap::bootstrap(unsigned options)
230{
231    hrtime_t now = gethrtime();
232
233    if (options == BS_REFRESH_OPEN_BUCKET) {
234        clconfig::Provider *http = parent->confmon->get_provider(clconfig::CLCONFIG_HTTP);
235        if (http) {
236            lcb_log(LOGARGS(parent, INFO), "Re-enable HTTP config provider to bootstrap \"%s\"",
237                    parent->settings->bucket);
238            http->enable();
239        }
240        if (parent->confmon->is_refreshing()) {
241            parent->confmon->stop();
242        }
243        parent->confmon->active_provider_list_id = 0;
244        parent->confmon->prepare();
245        state = S_INITIAL_PRE;
246        tm.rearm(LCBT_SETTING(parent, config_timeout));
247        lcb_aspend_add(&parent->pendops, LCB_PENDTYPE_COUNTER, nullptr);
248    } else if (parent->confmon->is_refreshing()) {
249        return LCB_SUCCESS;
250    }
251
252    if (options & BS_REFRESH_THROTTLE) {
253        /* Refresh throttle requested. This is not true if options == ALWAYS */
254        hrtime_t next_ts;
255        unsigned errthresh = LCBT_SETTING(parent, weird_things_threshold);
256
257        if (options & BS_REFRESH_INCRERR) {
258            errcounter++;
259        }
260        next_ts = last_refresh;
261        next_ts += LCB_US2NS(LCBT_SETTING(parent, weird_things_delay));
262        if (now < next_ts && errcounter < errthresh) {
263            lcb_log(
264                LOGARGS(parent, INFO),
265                "Not requesting a config refresh because of throttling parameters. Next refresh possible in %" PRIu64
266                "ms or %u errors. "
267                "See LCB_CNTL_CONFDELAY_THRESH and LCB_CNTL_CONFERRTHRESH to modify the throttling settings",
268                LCB_NS2US(next_ts - now) / 1000, (unsigned)errthresh - errcounter);
269            return LCB_SUCCESS;
270        }
271    }
272
273    if (options == BS_REFRESH_INITIAL) {
274        if (LCBT_SETTING(parent, network)) {
275            lcb_log(LOGARGS(parent, INFO), "Requested network configuration: \"%s\"", LCBT_SETTING(parent, network));
276        } else {
277            lcb_log(LOGARGS(parent, INFO), "Requested network configuration: heuristic");
278        }
279        state = S_INITIAL_PRE;
280        parent->confmon->prepare();
281        tm.rearm(LCBT_SETTING(parent, config_timeout));
282        lcb_aspend_add(&parent->pendops, LCB_PENDTYPE_COUNTER, nullptr);
283    }
284
285    /* Reset the counters */
286    errcounter = 0;
287    if (options != BS_REFRESH_INITIAL) {
288        last_refresh = now;
289    }
290    if (parent->settings->bucket) {
291        options &= BS_REFRESH_OPEN_BUCKET;
292    }
293    parent->confmon->start(options);
294    return LCB_SUCCESS;
295}
296
297Bootstrap::~Bootstrap()
298{
299    tm.release();
300    tmpoll.release();
301    parent->confmon->remove_listener(this);
302}
303
304LIBCOUCHBASE_API
305lcb_STATUS lcb_get_bootstrap_status(lcb_INSTANCE *instance)
306{
307    if (instance->cur_configinfo) {
308        switch (LCBT_SETTING(instance, conntype)) {
309            case LCB_TYPE_CLUSTER:
310                return LCB_SUCCESS;
311            case LCB_TYPE_BUCKET:
312                if (instance->cur_configinfo->vbc->bname != nullptr) {
313                    return LCB_SUCCESS;
314                }
315                /* fall through */
316            default:
317                return LCB_ERR_SDK_INTERNAL;
318        }
319    }
320    if (instance->last_error != LCB_SUCCESS) {
321        return instance->last_error;
322    }
323    if (LCBT_SETTING(instance, conntype) == LCB_TYPE_CLUSTER) {
324        if (lcb::clconfig::http_get_conn(instance->confmon) != nullptr || instance->confmon->get_config() != nullptr) {
325            return LCB_SUCCESS;
326        }
327    }
328    return LCB_ERR_GENERIC;
329}
330
331LIBCOUCHBASE_API
332void lcb_refresh_config(lcb_INSTANCE *instance)
333{
334    instance->bootstrap(BS_REFRESH_ALWAYS);
335}
336