/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2013 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef LCB_CLCONFIG_H #define LCB_CLCONFIG_H #include "hostlist.h" #include #include #include /** @file */ /** * @defgroup lcb-confmon Cluster Configuration Management * * @brief Monitors the retrieval and application of new cluster topology maps * (vBucket Configurations) * * @details * This module attempts to implement the 'Configuration Provider' interface * described at https://docs.google.com/document/d/1bSMt0Sj1uQtm0OYolQaJDJg4sASfoCEwU6_gjm1he8s/edit * * The model is fairly complex though significantly more maintainable and * testable than the previous model. The basic idea is as follows: * * *
    * *
  1. * There is a _Configuration Monitor_ object (Confmon) which acts * as the configuration supervisor. It is responsible for returning * configuration objects to those entities which request it. *
  2. * *
  3. * There are multiple _Configuration Provider_ (Provider) objects. * These providers aggregate configurations from multiple sources and * implement a common interface to: * *
      *
    1. Return a _quick_ configuration without fetching from network or disk * (see Provider::get_cached()) *
    2. Schedule a refresh to retrieve the latest configuration from the * network (see Provider::refresh())
    3. * *
    4. Notify the monitor that it has received a new configuration. The * monitor itself will determine whether or not to accept the new * configuration by examining the configuration and determining if it is more * recent than the one currently in used. See lcb_confmon_set_next()
    5. *
  4. * *
  5. * _Configuration Info_ objects. These objects are refcounted wrappers * around vbucket configuration handles. They have a refcount and also an * integer which can be used to compare with other objects for 'freshness'. * See ConfigInfo *
  6. * *
  7. * _Configuration Listeners_. These are registered with the global supervisor * and are invoked whenever a new valid configuration is detected. This is * really only ever used during bootstrap or testing where we are explicitly * waiting for a configuration without having any actual commands to schedule. * See Listener *
  8. *
*/ /** *@addtogroup lcb-confmon *@{ */ namespace lcb { namespace clconfig { /** * @brief Enumeration of the various config providers available. * The type of methods available. These are enumerated in order of preference */ enum Method { /** File-based "configcache" provider. Implemented in bc_file.c */ CLCONFIG_FILE, /** New-style config-over-memcached provider. Implemented in bc_cccp.c */ CLCONFIG_CCCP, /** Old-style streaming HTTP provider. Implemented in bc_http.c */ CLCONFIG_HTTP, /** Raw memcached provided */ CLCONFIG_MCRAW, /** Cluster administration provider. Static config with services */ CLCONFIG_CLADMIN, CLCONFIG_MAX, /** Ephemeral source, used for tests */ CLCONFIG_PHONY }; /** Event types propagated to listeners */ enum EventType { /** Called when a new configuration is being set in confmon */ CLCONFIG_EVENT_GOT_NEW_CONFIG, /** Called when _any_ configuration is received via set_enxt */ CLCONFIG_EVENT_GOT_ANY_CONFIG, /** Called when all providers have been tried */ CLCONFIG_EVENT_PROVIDERS_CYCLED, /** The monitor has stopped */ CLCONFIG_EVENT_MONITOR_STOPPED }; /** @brief Possible confmon states */ enum State { /** The monitor is idle and not requesting a new configuration */ CONFMON_S_INACTIVE = 0, /** The monitor is actively requesting a configuration */ CONFMON_S_ACTIVE = 1 << 0, /** The monitor is fetching a configuration, but is in a throttle state */ CONFMON_S_ITERGRACE = 1 << 1 }; struct Provider; struct Listener; class ConfigInfo; /** * This object contains the information needed for libcouchbase to deal with * when retrieving new configs. */ struct Confmon { /** * @brief Create a new configuration monitor. * This function creates a new `confmon` object which can be used to manage * configurations and their providers. * * @param settings * @param iot * * Once the confmon object has been created you may enable or disable various * providers (see lcb_confmon_set_provider_active()). Once no more providers * remain to be activated you should call lcb_confmon_prepare() once. Then * call the rest of the functions. */ Confmon(lcb_settings*, lcbio_pTABLE iot); void destroy() { delete this; } ~Confmon(); /** * Get the provider following the current provider, or NULL if this is * the last provider in the list. * @param cur The current provider. * @return The next provider, or NULL if no more providers remain. */ Provider *next_active(Provider *cur); /** * Gets the first active provider. * @return the first provider, or NULL if no providers exist. */ Provider *first_active(); /** * Prepares the configuration monitor object for operations. This will insert * all the enabled providers into a list. Call this function each time a * provider has been enabled. * @param mon */ void prepare(); /** * Set a given provider as being 'active'. This will activate the * provider as well as call #prepare() to update the list. * @param type The ID of the provider to activate * @param enabled true for activate, false for deactivate */ void set_active(Method type, bool enabled); /** * @brief Request a configuration refresh * * Start traversing the list of current providers, requesting a new * configuration for each. This function will asynchronously loop through all * providers until one provides a new configuration. * * You may call #stop() to asynchronously break out of the loop. * If the confmon is already in a refreshing state * (i.e. #is_refreshing()) returns true then this function does * nothing. * * This function is reentrant safe and may be called at any time. * * @param mon * @see lcb_confmon_add_listener() * @see #stop() * @see #is_refreshing() */ void start(); /** * @brief Cancel a pending configuration refresh. * * Stops the monitor. This will call Provider::pause() for each active * provider. Typically called before destruction or when a new configuration * has been found. * * This function is safe to call anywhere. If the monitor is already stopped * then this function does nothing. * * @param mon * @see #start() * @see #is_refreshing() */ void stop(); /** * @brief Check if the monitor is waiting for a new config from a provider * @param mon * @return true if refreshing, false if idle */ bool is_refreshing() const { return (state & CONFMON_S_ACTIVE) != 0; } /** * Get the current configuration * @return The configuration */ ConfigInfo* get_config() const { return config; } /** * Get the last error which occurred on this object * @return The last error */ lcb_error_t get_last_error() const { return last_error; } /** * @brief Get the current monitor state * @param mon the monitor * @return a set of flags consisting of @ref State values. */ int get_state() const { return state; } void stop_real(); void do_next_provider(); int do_set_next(ConfigInfo*, bool notify_miss); void invoke_listeners(EventType, ConfigInfo*); /** * @brief Indicate that a provider has failed and advance the monitor * * Indicate that the current provider has failed to obtain a new configuration. * This is always called by a provider and should be invoked when the provider * has encountered an internal error which caused it to be unable to fetch * the configuration. * * Note that this function is safe to call from any provider at any time. If * the provider is not the current provider then it is treated as an async * push notification failure and ignored. This function is _not_ safe to call * from consumers of providers * * Once this is called, the confmon instance will either roll over to the next * provider or enter the inactive state depending on the configuration and * whether the current provider is the last provider in the list. * * @param provider * @param err */ void provider_failed(Provider *which, lcb_error_t why); /** * @brief Indicate that a provider has successfuly retrieved a configuration. * * Indicates that the provider has fetched a new configuration from the network * and that confmon should attempt to propagate it. It has similar semantics * to lcb_confmon_provider_failed() except that the second argument is a config * object rather than an error code. The second argument must not be `NULL` * * The monitor will compare the new config against the current config. * If the new config does not feature any changes from the current config then * it is ignored and the confmon instance will proceed to the next provider. * This is done through a direct call to provider_failed(provider, LCB_SUCCESS). * * This function should _not_ be called outside of an asynchronous provider's * handler. * * @param provider the provider which yielded the new configuration * @param info the new configuration */ void provider_got_config(Provider *which, ConfigInfo* config); /** Dump information about the monitor * @param mon the monitor object * @param fp the file to which information should be written */ void dump(FILE *fp); Provider* get_provider(Method m) const { return all_providers[m]; } /** * @brief Register a listener to be invoked on state changes and events * * Adds a 'listener' object to be called at each configuration update. The * listener may co-exist with other listeners (though it should never be added * twice). When a new configuration is received and accept, the listener's * Listener::callback field will be invoked with it. * * The callback will continue to be invoked for each new configuration received * until remove_listener is called. Note that the listener is not allocated * by the confmon and its responsibility is the user's * * @param mon the monitor * @param listener the listener. The listener's contents are not copied into * confmon and should thus remain valid until it is removed */ void add_listener(Listener* lsn); /** * @brief Unregister (and remove) a listener added via lcb_confmon_add_listener() * @param mon the monitor * @param listener the listener */ void remove_listener(Listener *lsn); /**Current provider. This provider may either fail or succeed. * In either case unless the provider can provide us with a specific * config which is newer than the one we have, it will roll over to the * next provider. */ Provider *cur_provider; /** All providers we know about. Currently this means the 'builtin' providers */ Provider* all_providers[CLCONFIG_MAX]; /** The current configuration pointer. This contains the most recent accepted * configuration */ ConfigInfo * config; typedef std::list ListenerList; /** List of listeners for events */ ListenerList listeners; lcb_settings *settings; lcb_error_t last_error; lcbio_pTABLE iot; /** This is the async handle for a reentrant start */ lcb::io::Timer as_start; /** Async handle for a reentrant stop */ lcb::io::Timer as_stop; /* CONFMON_S_* values. Used internally */ int state; /** Last time the provider was stopped. As a microsecond timestamp */ lcb_uint32_t last_stop_us; typedef std::list ProviderList; ProviderList active_providers; }; /** * The base structure of a provider. This structure is intended to be * 'subclassed' by implementors. */ struct Provider { Provider(Confmon*, Method type_); /** Destroy the resources created by this provider. */ virtual ~Provider(); /** * Get the current map known to this provider. This should not perform * any blocking operations. Providers which use a push model may use * this method as an asynchronous return value for a previously-received * configuration. */ virtual ConfigInfo* get_cached() = 0; /** * Request a new configuration. This will be called by the manager when * the cached configuration (i.e. 'get_cached') is deemed invalid. Thus * this function should unconditionally try to schedule getting the * newest configuration it can. When the configuration has been received, * the provider may call provider_success or provider_failed. * * @note * The PROVIDER is responsible for terminating its own * process. In other words there is no safeguard within confmon itself * against a provider taking an excessively long time; therefore a provider * should implement a timeout mechanism of its choice to promptly deliver * a success or failure. * * @param pb */ virtual lcb_error_t refresh() = 0; /** * Callback invoked to the provider to indicate that it should cease * performing any "Active" configuration changes. Note that this is only * a hint and a provider may perform its own hooking based on this. In any * event receiving this callback is indicating that the provider will not * be needed again in quite some time. How long this "time" is can range * between 0 seconds and several minutes depending on how a user has * configured the client. * @param pb * @return true if actually paused */ virtual bool pause() { return false; } /** * Called when a new configuration has been received. * * @param provider the provider instance * @param config the current configuration. * Note that this should only update the server list and do nothing * else. */ virtual void config_updated(lcbvb_CONFIG*) { } /** * Retrieve the list of nodes from this provider, if applicable * @param p the provider * @return A list of nodes, or NULL if the provider does not have a list */ virtual const lcb::Hostlist* get_nodes() const { return NULL; } /** * Call to change the configured nodes of this provider. * @param p The provider * @param l The list of nodes to apply */ virtual void configure_nodes(const lcb::Hostlist&) { } /** * Dump state information. This callback is optional * @param p the provider * @param f the file to write to */ virtual void dump(FILE *) const { } void enable() { enabled = 1; } virtual void enable(void *) { assert("Must be implemented in subclass if used" && 0); } /** The type of provider */ const Method type; /** Whether this provider has been disabled/enabled explicitly by a user */ bool enabled; /** The parent manager object */ Confmon *parent; lcb_settings& settings() const { return *parent->settings; } }; Provider *new_cccp_provider(Confmon*); Provider *new_file_provider(Confmon*); Provider *new_http_provider(Confmon*); Provider *new_mcraw_provider(Confmon*); Provider *new_cladmin_provider(Confmon*); /** @brief refcounted object encapsulating a vbucket config */ class ConfigInfo { public: /** * Creates a new configuration wrapper object containing the vbucket config * pointed to by 'config'. Its initial refcount will be set to 1. * * @param config a newly parsed configuration * @param origin the type of provider from which the config originated. * @return a new ConfigInfo object. This should be incref()'d/decref()'d * as needed. */ static ConfigInfo* create(lcbvb_CONFIG *vbc, Method origin) { return new ConfigInfo(vbc, origin); } /** * @brief Compares two info structures and determine which one is newer * * This function returns an integer less than * zero, zero or greater than zero if the first argument is considered older * than, equal to, or later than the second argument. * @param a * @param b * @see lcbvb_get_revision * @see ConfigInfo::cmpclock */ int compare(const ConfigInfo&); /** * @brief Increment the refcount on a config object */ void incref() { refcount++; } /** * @brief Decrement the refcount on a config object. * Decrement the refcount. If the internal refcount reaches 0 then the internal * members (including the vbucket config handle itself) will be freed. * @param info the configuration */ void decref() { if (!--refcount) { delete this; } } operator lcbvb_CONFIG*() const { return vbc; } Method get_origin() const { return origin; } /** Actual configuration */ lcbvb_CONFIG* vbc; private: ConfigInfo(lcbvb_CONFIG *vbc, Method origin); ~ConfigInfo(); /** Comparative clock with which to compare */ uint64_t cmpclock; /** Reference counter */ unsigned int refcount; /** Origin provider type which produced this config */ Method origin; }; /** * @brief Listener for events * One or more listeners may be installed into the confmon which will have * a callback invoked on significant vbucket events. See clconfig_event_t * for a variety of events the listener can know. */ struct Listener { virtual ~Listener() { } /** Linked list node */ lcb_list_t ll; /** * Callback invoked for significant events * * @param event the event which took place * @param config the configuration associated with the event. Note that * `config` may also be NULL */ virtual void clconfig_lsn(EventType event, ConfigInfo *config) = 0; }; /* Method-specific setup methods.. */ /** * @name File Provider-specific APIs * @{ */ /** * Sets the input/output filename for the file provider. This also enables * the file provider. * @param p the provider * @param f the filename (if NULL, a temporary filename will be created) * @param ro whether the client will never modify the file * @return true on success, false on failure. */ bool file_set_filename(Provider *p, const char *f, bool ro); /** * Retrieve the filename for the provider * @param p The provider of type LCB_CLCONFIG_FILE * @return the current filename being used. */ const char* file_get_filename(Provider *p); void file_set_readonly(Provider *p, bool val); /**@}*/ /** * @name HTTP Provider-specific APIs * @{ */ /** * Get the socket representing the current REST connection to the cluster * (if applicable) * @param mon * @return */ const lcbio_SOCKET* http_get_conn(const Provider *p); static inline const lcbio_SOCKET* http_get_conn(Confmon *c) { return http_get_conn(c->get_provider(CLCONFIG_HTTP)); } /** * Get the hostname for the current REST connection to the cluster * @param mon * @return */ const lcb_host_t * http_get_host(const Provider *p); static inline const lcb_host_t* http_get_host(Confmon *c) { return http_get_host(c->get_provider(CLCONFIG_HTTP)); } /**@}*/ /** * @name CCCP Provider-specific APIs * @{ */ /** * Note, to initialize the CCCP provider, you should use * cccp->enable(instance); */ /** * @brief Notify the CCCP provider about a new configuration from a * `NOT_MY_VBUCKET` response * * This should be called by the packet handler when a configuration has been * received as a payload to a response with the error of `NOT_MY_VBUCKET`. * * @param provider The CCCP provider * @param host The hostname (without the port) on which the packet was received * @param data The configuration JSON blob * @return LCB_SUCCESS, or an error code if the configuration could not be * set */ lcb_error_t cccp_update(Provider *provider, const char *host, const char *data); /** * @brief Notify the CCCP provider about a configuration received from a * `CMD_GET_CLUSTER_CONFIG` response. * * @param cookie The cookie object attached to the packet * @param err The error code for the reply * @param bytes The payload pointer * @param nbytes Size of payload * @param origin Host object from which the packet was received */ void cccp_update(const void *cookie, lcb_error_t err, const void *bytes, size_t nbytes, const lcb_host_t *origin); /**@}*/ /**@name Raw Memcached (MCRAW) Provider-specific APIs * @{*/ /**@}*/ /**@}*/ } // clconfig } // lcb #endif /* LCB_CLCONFIG_H */