 612b4e1a32
			
		
	
	612b4e1a32
	
	
	
		
			
			The function now takes an output buffer as a parameter. This prevents race conditions by copying the parameter value into a local buffer.
		
			
				
	
	
		
			2954 lines
		
	
	
		
			80 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2954 lines
		
	
	
		
			80 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2016 MariaDB Corporation Ab
 | |
|  *
 | |
|  * Use of this software is governed by the Business Source License included
 | |
|  * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | |
|  *
 | |
|  * Change Date: 2020-01-01
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file service.c  - A representation of a service within MaxScale
 | |
|  */
 | |
| 
 | |
| #include <maxscale/cppdefs.hh>
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <errno.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/types.h>
 | |
| #include <math.h>
 | |
| #include <fcntl.h>
 | |
| #include <map>
 | |
| #include <string>
 | |
| #include <set>
 | |
| #include <vector>
 | |
| 
 | |
| #include <maxscale/service.h>
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/dcb.h>
 | |
| #include <maxscale/paths.h>
 | |
| #include <maxscale/housekeeper.h>
 | |
| #include <maxscale/listener.h>
 | |
| #include <maxscale/log_manager.h>
 | |
| #include <maxscale/poll.h>
 | |
| #include <maxscale/protocol.h>
 | |
| #include <maxscale/resultset.h>
 | |
| #include <maxscale/router.h>
 | |
| #include <maxscale/server.h>
 | |
| #include <maxscale/session.h>
 | |
| #include <maxscale/spinlock.h>
 | |
| #include <maxscale/users.h>
 | |
| #include <maxscale/utils.h>
 | |
| #include <maxscale/version.h>
 | |
| #include <maxscale/jansson.h>
 | |
| #include <maxscale/json_api.h>
 | |
| #include <maxscale/worker.h>
 | |
| 
 | |
| #include "internal/config.h"
 | |
| #include "internal/filter.h"
 | |
| #include "internal/modules.h"
 | |
| #include "internal/service.h"
 | |
| 
 | |
| /** This define is needed in CentOS 6 systems */
 | |
| #if !defined(UINT64_MAX)
 | |
| #define UINT64_MAX      (18446744073709551615UL)
 | |
| #endif
 | |
| 
 | |
| using std::string;
 | |
| using std::set;
 | |
| 
 | |
| /** Base value for server weights */
 | |
| #define SERVICE_BASE_SERVER_WEIGHT 1000
 | |
| 
 | |
| /** To be used with configuration type checks */
 | |
| typedef struct typelib_st
 | |
| {
 | |
|     int          tl_nelems;
 | |
|     const char*  tl_name;
 | |
|     const char** tl_p_elems;
 | |
| } typelib_t;
 | |
| 
 | |
| /** Set of subsequent false,true pairs */
 | |
| static const char* bool_strings[11]  = {"FALSE", "TRUE", "OFF", "ON", "N", "Y", "0", "1", "NO", "YES", 0};
 | |
| typelib_t bool_type  = {MXS_ARRAY_NELEMS(bool_strings) - 1, "bool_type", bool_strings};
 | |
| 
 | |
| /** List of valid values */
 | |
| static const char* sqlvar_target_strings[4] = {"MASTER", "ALL", 0};
 | |
| typelib_t sqlvar_target_type =
 | |
| {
 | |
|     MXS_ARRAY_NELEMS(sqlvar_target_strings) - 1,
 | |
|     "sqlvar_target_type",
 | |
|     sqlvar_target_strings
 | |
| };
 | |
| 
 | |
| static SPINLOCK service_spin = SPINLOCK_INIT;
 | |
| static SERVICE  *allServices = NULL;
 | |
| 
 | |
| static int find_type(typelib_t* tl, const char* needle, int maxlen);
 | |
| 
 | |
| static void service_add_qualified_param(SERVICE*          svc,
 | |
|                                         MXS_CONFIG_PARAMETER* param);
 | |
| static void service_internal_restart(void *data);
 | |
| static void service_calculate_weights(SERVICE *service);
 | |
| 
 | |
| SERVICE* service_alloc(const char *name, const char *router)
 | |
| {
 | |
|     char *my_name = MXS_STRDUP(name);
 | |
|     char *my_router = MXS_STRDUP(router);
 | |
|     SERVICE *service = (SERVICE *)MXS_CALLOC(1, sizeof(*service));
 | |
|     SERVICE_REFRESH_RATE* rate_limits = (SERVICE_REFRESH_RATE*)MXS_CALLOC(config_threadcount(),
 | |
|                                                                          sizeof(*rate_limits));
 | |
|     if (!my_name || !my_router || !service || !rate_limits)
 | |
|     {
 | |
|         MXS_FREE(my_name);
 | |
|         MXS_FREE(my_router);
 | |
|         MXS_FREE(service);
 | |
|         MXS_FREE(rate_limits);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     if ((service->router = (MXS_ROUTER_OBJECT*)load_module(my_router, MODULE_ROUTER)) == NULL)
 | |
|     {
 | |
|         char* home = get_libdir();
 | |
|         char* ldpath = getenv("LD_LIBRARY_PATH");
 | |
| 
 | |
|         MXS_ERROR("Unable to load %s module \"%s\".\n\t\t\t"
 | |
|                   "      Ensure that lib%s.so exists in one of the "
 | |
|                   "following directories :\n\t\t\t      "
 | |
|                   "- %s\n%s%s",
 | |
|                   MODULE_ROUTER,
 | |
|                   my_router,
 | |
|                   my_router,
 | |
|                   home,
 | |
|                   ldpath ? "\t\t\t      - " : "",
 | |
|                   ldpath ? ldpath : "");
 | |
|         MXS_FREE(my_name);
 | |
|         MXS_FREE(my_router);
 | |
|         MXS_FREE(service);
 | |
|         MXS_FREE(rate_limits);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     const MXS_MODULE* module = get_module(my_router, MODULE_ROUTER);
 | |
|     ss_dassert(module);
 | |
| 
 | |
|     service->capabilities = module->module_capabilities;
 | |
|     service->max_retry_interval = SERVICE_MAX_RETRY_INTERVAL;
 | |
|     service->client_count = 0;
 | |
|     service->n_dbref = 0;
 | |
|     service->name = my_name;
 | |
|     service->routerModule = my_router;
 | |
|     service->users_from_all = false;
 | |
|     service->localhost_match_wildcard_host = SERVICE_PARAM_UNINIT;
 | |
|     service->retry_start = true;
 | |
|     service->conn_idle_timeout = SERVICE_NO_SESSION_TIMEOUT;
 | |
|     service->svc_config_param = NULL;
 | |
|     service->routerOptions = NULL;
 | |
|     service->log_auth_warnings = true;
 | |
|     service->strip_db_esc = true;
 | |
|     service->rate_limits = rate_limits;
 | |
|     if (service->name == NULL || service->routerModule == NULL)
 | |
|     {
 | |
|         if (service->name)
 | |
|         {
 | |
|             MXS_FREE(service->name);
 | |
|         }
 | |
|         MXS_FREE(service);
 | |
|         return NULL;
 | |
|     }
 | |
|     service->stats.started = time(0);
 | |
|     service->stats.n_failed_starts = 0;
 | |
|     service->state = SERVICE_STATE_ALLOC;
 | |
|     spinlock_init(&service->spin);
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service->next = allServices;
 | |
|     allServices = service;
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return service;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check to see if a service pointer is valid
 | |
|  *
 | |
|  * @param service       The pointer to check
 | |
|  * @return 1 if the service is in the list of all services
 | |
|  */
 | |
| int
 | |
| service_isvalid(SERVICE *service)
 | |
| {
 | |
|     SERVICE *checkservice;
 | |
|     int rval = 0;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     checkservice = allServices;
 | |
|     while (checkservice)
 | |
|     {
 | |
|         if (checkservice == service)
 | |
|         {
 | |
|             rval = 1;
 | |
|             break;
 | |
|         }
 | |
|         checkservice = checkservice->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static inline void close_port(SERV_LISTENER *port)
 | |
| {
 | |
|     if (port->service->state == SERVICE_STATE_ALLOC)
 | |
|     {
 | |
|         /** The service failed when it was being allocated */
 | |
|         port->service->state = SERVICE_STATE_FAILED;
 | |
|     }
 | |
| 
 | |
|     if (port->listener)
 | |
|     {
 | |
|         dcb_close(port->listener);
 | |
|         port->listener = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start an individual port/protocol pair
 | |
|  *
 | |
|  * @param service       The service
 | |
|  * @param port          The port to start
 | |
|  * @return              The number of listeners started
 | |
|  */
 | |
| static int
 | |
| serviceStartPort(SERVICE *service, SERV_LISTENER *port)
 | |
| {
 | |
|     const size_t ANY_IPV4_ADDRESS_LEN = 7; // strlen("0:0:0:0");
 | |
| 
 | |
|     int listeners = 0;
 | |
|     size_t config_bind_len =
 | |
|         (port->address ? strlen(port->address) : ANY_IPV4_ADDRESS_LEN) + 1 + UINTLEN(port->port);
 | |
|     char config_bind[config_bind_len + 1]; // +1 for NULL
 | |
|     MXS_PROTOCOL *funcs;
 | |
| 
 | |
|     if (service == NULL || service->router == NULL || service->router_instance == NULL)
 | |
|     {
 | |
|         /* Should never happen, this guarantees it can't */
 | |
|         MXS_ERROR("Attempt to start port with null or incomplete service");
 | |
|         close_port(port);
 | |
|         ss_dassert(false);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER, port);
 | |
| 
 | |
|     if (port->listener == NULL)
 | |
|     {
 | |
|         MXS_ERROR("Failed to create listener for service %s.", service->name);
 | |
|         close_port(port);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     port->listener->service = service;
 | |
| 
 | |
|     if (port->ssl)
 | |
|     {
 | |
|         listener_init_SSL(port->ssl);
 | |
|     }
 | |
| 
 | |
|     if ((funcs = (MXS_PROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL)
 | |
|     {
 | |
|         MXS_ERROR("Unable to load protocol module %s. Listener for service %s not started.",
 | |
|                   port->protocol, service->name);
 | |
|         close_port(port);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     memcpy(&(port->listener->func), funcs, sizeof(MXS_PROTOCOL));
 | |
| 
 | |
|     const char *authenticator_name = "NullAuthDeny";
 | |
| 
 | |
|     if (port->authenticator)
 | |
|     {
 | |
|         authenticator_name = port->authenticator;
 | |
|     }
 | |
|     else if (port->listener->func.auth_default)
 | |
|     {
 | |
|         authenticator_name = port->listener->func.auth_default();
 | |
|     }
 | |
| 
 | |
|     MXS_AUTHENTICATOR *authfuncs = (MXS_AUTHENTICATOR *)load_module(authenticator_name, MODULE_AUTHENTICATOR);
 | |
| 
 | |
|     if (authfuncs == NULL)
 | |
|     {
 | |
|         MXS_ERROR("Failed to load authenticator module '%s' for listener '%s'",
 | |
|                   authenticator_name, port->name);
 | |
|         close_port(port);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     // Add protocol and authenticator capabilities from the listener
 | |
|     const MXS_MODULE* proto_mod = get_module(port->protocol, MODULE_PROTOCOL);
 | |
|     const MXS_MODULE* auth_mod = get_module(authenticator_name, MODULE_AUTHENTICATOR);
 | |
|     ss_dassert(proto_mod && auth_mod);
 | |
|     service->capabilities |= proto_mod->module_capabilities | auth_mod->module_capabilities;
 | |
| 
 | |
|     memcpy(&port->listener->authfunc, authfuncs, sizeof(MXS_AUTHENTICATOR));
 | |
| 
 | |
|     /**
 | |
|      * Normally, we'd allocate the DCB specific authentication data. As the
 | |
|      * listeners aren't normal DCBs, we can skip that.
 | |
|      */
 | |
| 
 | |
|     if (port->address)
 | |
|     {
 | |
|         sprintf(config_bind, "%s|%d", port->address, port->port);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         sprintf(config_bind, "::|%d", port->port);
 | |
|     }
 | |
| 
 | |
|     /** Load the authentication users before before starting the listener */
 | |
|     if (port->listener->authfunc.loadusers)
 | |
|     {
 | |
|         switch (port->listener->authfunc.loadusers(port))
 | |
|         {
 | |
|         case MXS_AUTH_LOADUSERS_FATAL:
 | |
|             MXS_ERROR("[%s] Fatal error when loading users for listener '%s', "
 | |
|                       "service is not started.", service->name, port->name);
 | |
|             close_port(port);
 | |
|             return 0;
 | |
| 
 | |
|         case MXS_AUTH_LOADUSERS_ERROR:
 | |
|             MXS_WARNING("[%s] Failed to load users for listener '%s', authentication"
 | |
|                         " might not work.", service->name, port->name);
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     MXS_CONFIG* config = config_get_global_options();
 | |
|     time_t last;
 | |
|     bool warned;
 | |
| 
 | |
|     /**
 | |
|      * At service start last update is set to config->users_refresh_time seconds earlier.
 | |
|      * This way MaxScale could try reloading users just after startup. But only if user
 | |
|      * refreshing has not been turned off.
 | |
|      */
 | |
|     if (config->users_refresh_time == INT32_MAX)
 | |
|     {
 | |
|         last = time(NULL);
 | |
|         warned = true;  // So that there will not be a refresh rate warning.
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         last = time(NULL) - config->users_refresh_time;
 | |
|         warned = false;
 | |
|     }
 | |
| 
 | |
|     int nthreads = config_threadcount();
 | |
| 
 | |
|     for (int i = 0; i < nthreads; ++i)
 | |
|     {
 | |
|         service->rate_limits[i].last = last;
 | |
|         service->rate_limits[i].warned = warned;
 | |
|     }
 | |
| 
 | |
|     if (port->listener->func.listen(port->listener, config_bind))
 | |
|     {
 | |
|         port->listener->session = session_alloc(service, port->listener);
 | |
| 
 | |
|         if (port->listener->session != NULL)
 | |
|         {
 | |
|             port->listener->session->state = SESSION_STATE_LISTENER;
 | |
|             listeners += 1;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("[%s] Failed to create listener session.", service->name);
 | |
|             close_port(port);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("[%s] Failed to listen on %s", service->name, config_bind);
 | |
|         close_port(port);
 | |
|     }
 | |
| 
 | |
|     return listeners;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start all ports for a service.
 | |
|  * serviceStartAllPorts will try to start all listeners associated with the service.
 | |
|  * If no listeners are started, the starting of ports will be retried after a period of time.
 | |
|  * @param service Service to start
 | |
|  * @return Number of started listeners. This is equal to the number of ports the service
 | |
|  * is listening to.
 | |
|  */
 | |
| int serviceStartAllPorts(SERVICE* service)
 | |
| {
 | |
|     SERV_LISTENER *port = service->ports;
 | |
|     int listeners = 0;
 | |
| 
 | |
|     if (port)
 | |
|     {
 | |
|         while (!service->svc_do_shutdown && port)
 | |
|         {
 | |
|             listeners += serviceStartPort(service, port);
 | |
|             port = port->next;
 | |
|         }
 | |
| 
 | |
|         if (service->state == SERVICE_STATE_FAILED)
 | |
|         {
 | |
|             listeners = 0;
 | |
|         }
 | |
|         else if (listeners)
 | |
|         {
 | |
|             service->state = SERVICE_STATE_STARTED;
 | |
|             service->stats.started = time(0);
 | |
|         }
 | |
|         else if (service->retry_start)
 | |
|         {
 | |
|             /** Service failed to start any ports. Try again later. */
 | |
|             service->stats.n_failed_starts++;
 | |
|             char taskname[strlen(service->name) + strlen("_start_retry_") +
 | |
|                                                 (int) ceil(log10(INT_MAX)) + 1];
 | |
|             int retry_after = MXS_MIN(service->stats.n_failed_starts * 10, service->max_retry_interval);
 | |
|             snprintf(taskname, sizeof(taskname), "%s_start_retry_%d",
 | |
|                      service->name, service->stats.n_failed_starts);
 | |
|             hktask_oneshot(taskname, service_internal_restart,
 | |
|                            (void*) service, retry_after);
 | |
|             MXS_NOTICE("Failed to start service %s, retrying in %d seconds.",
 | |
|                        service->name, retry_after);
 | |
| 
 | |
|             /** This will prevent MaxScale from shutting down if service start is retried later */
 | |
|             listeners = 1;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_WARNING("Service '%s' has no listeners defined.", service->name);
 | |
|         listeners = 1; /** Set this to one to suppress errors */
 | |
|     }
 | |
| 
 | |
|     return listeners;
 | |
| }
 | |
| 
 | |
| /** Helper function for copying an array of strings */
 | |
| static char** copy_string_array(char** original)
 | |
| {
 | |
|     char **array = NULL;
 | |
| 
 | |
|     if (original)
 | |
|     {
 | |
|         int values = 0;
 | |
| 
 | |
|         while (original[values])
 | |
|         {
 | |
|             values++;
 | |
|         }
 | |
| 
 | |
|         array = (char**)MXS_MALLOC(sizeof(char*) * (values + 1));
 | |
| 
 | |
|         if (array)
 | |
|         {
 | |
|             for (int i = 0; i < values; i++)
 | |
|             {
 | |
|                 array[i] = MXS_STRDUP_A(original[i]);
 | |
|             }
 | |
|             array[values] = NULL;
 | |
|         }
 | |
|     }
 | |
|     return array;
 | |
| }
 | |
| 
 | |
| /** Helper function for freeing an array of strings */
 | |
| static void free_string_array(char** array)
 | |
| {
 | |
|     if (array)
 | |
|     {
 | |
|         for (int i = 0; array[i]; i++)
 | |
|         {
 | |
|             MXS_FREE(array[i]);
 | |
|         }
 | |
|         MXS_FREE(array);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start a service
 | |
|  *
 | |
|  * This function loads the protocol modules for each port on which the
 | |
|  * service listens and starts the listener on that port
 | |
|  *
 | |
|  * Also create the router_instance for the service.
 | |
|  *
 | |
|  * @param service       The Service that should be started
 | |
|  * @return      Returns the number of listeners created
 | |
|  */
 | |
| int serviceInitialize(SERVICE *service)
 | |
| {
 | |
|     /** Calculate the server weights */
 | |
|     service_calculate_weights(service);
 | |
| 
 | |
|     int listeners = 0;
 | |
|     char **router_options = copy_string_array(service->routerOptions);
 | |
| 
 | |
|     if ((service->router_instance = service->router->createInstance(service, router_options)))
 | |
|     {
 | |
|         if (service->router->getCapabilities)
 | |
|         {
 | |
|             service->capabilities |= service->router->getCapabilities(service->router_instance);
 | |
|         }
 | |
| 
 | |
|         if (!config_get_global_options()->config_check)
 | |
|         {
 | |
|             listeners = serviceStartAllPorts(service);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /** We're only checking that the configuration is valid */
 | |
|             listeners++;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("%s: Failed to create router instance. Service not started.", service->name);
 | |
|         service->state = SERVICE_STATE_FAILED;
 | |
|     }
 | |
| 
 | |
|     free_string_array(router_options);
 | |
| 
 | |
|     return listeners;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Remove a listener from use
 | |
|  *
 | |
|  * @note This does not free the memory
 | |
|  *
 | |
|  * @param service Service where @c port points to
 | |
|  * @param port Port to remove
 | |
|  */
 | |
| void serviceRemoveListener(SERVICE *service, SERV_LISTENER *target)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener == target)
 | |
|         {
 | |
|             listener_set_active(listener, false);
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool serviceLaunchListener(SERVICE *service, SERV_LISTENER *port)
 | |
| {
 | |
|     ss_dassert(service->state != SERVICE_STATE_FAILED);
 | |
|     bool rval = true;
 | |
| 
 | |
|     spinlock_acquire(&service->spin);
 | |
| 
 | |
|     if (serviceStartPort(service, port) == 0)
 | |
|     {
 | |
|         /** Failed to start the listener */
 | |
|         serviceRemoveListener(service, port);
 | |
|         rval = false;
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service->spin);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool serviceStopListener(SERVICE *service, const char *name)
 | |
| {
 | |
|     bool rval = false;
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
 | |
|         {
 | |
|             if (poll_remove_dcb(listener->listener) == 0)
 | |
|             {
 | |
|                 listener->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
 | |
|                 rval = true;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool serviceStartListener(SERVICE *service, const char *name)
 | |
| {
 | |
|     bool rval = false;
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
 | |
|         {
 | |
|             if (listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED &&
 | |
|                 poll_add_dcb(listener->listener) == 0)
 | |
|             {
 | |
|                 listener->listener->session->state = SESSION_STATE_LISTENER;
 | |
|                 rval = true;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| int service_launch_all()
 | |
| {
 | |
|     SERVICE *ptr;
 | |
|     int n = 0, i;
 | |
|     bool error = false;
 | |
|     int num_svc = 0;
 | |
| 
 | |
|     for (ptr = allServices; ptr; ptr = ptr->next)
 | |
|     {
 | |
|         num_svc++;
 | |
|     }
 | |
| 
 | |
|     MXS_NOTICE("Starting a total of %d services...", num_svc);
 | |
| 
 | |
|     int curr_svc = 1;
 | |
|     ptr = allServices;
 | |
|     while (ptr && !ptr->svc_do_shutdown)
 | |
|     {
 | |
|         n += (i = serviceInitialize(ptr));
 | |
|         MXS_NOTICE("Service '%s' started (%d/%d)", ptr->name, curr_svc++, num_svc);
 | |
| 
 | |
|         if (i == 0)
 | |
|         {
 | |
|             MXS_ERROR("Failed to start service '%s'.", ptr->name);
 | |
|             error = true;
 | |
|         }
 | |
| 
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
|     return error ? 0 : n;
 | |
| }
 | |
| 
 | |
| bool serviceStop(SERVICE *service)
 | |
| {
 | |
|     int listeners = 0;
 | |
| 
 | |
|     if (service)
 | |
|     {
 | |
|         LISTENER_ITERATOR iter;
 | |
| 
 | |
|         for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|              listener; listener = listener_iterator_next(&iter))
 | |
|         {
 | |
|             if (listener_is_active(listener) &&
 | |
|                 listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER)
 | |
|             {
 | |
|                 if (poll_remove_dcb(listener->listener) == 0)
 | |
|                 {
 | |
|                     listener->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
 | |
|                     listeners++;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         service->state = SERVICE_STATE_STOPPED;
 | |
|     }
 | |
| 
 | |
|     return listeners > 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Restart a service
 | |
|  *
 | |
|  * This function stops the listener for the service
 | |
|  *
 | |
|  * @param service       The Service that should be restarted
 | |
|  * @return      Returns the number of listeners restarted
 | |
|  */
 | |
| bool serviceStart(SERVICE *service)
 | |
| {
 | |
|     int listeners = 0;
 | |
| 
 | |
|     if (service)
 | |
|     {
 | |
|         LISTENER_ITERATOR iter;
 | |
| 
 | |
|         for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|              listener; listener = listener_iterator_next(&iter))
 | |
|         {
 | |
|             if (listener_is_active(listener) &&
 | |
|                 listener->listener && listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED)
 | |
|             {
 | |
|                 if (poll_add_dcb(listener->listener) == 0)
 | |
|                 {
 | |
|                     listener->listener->session->state = SESSION_STATE_LISTENER;
 | |
|                     listeners++;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         service->state = SERVICE_STATE_STARTED;
 | |
|     }
 | |
| 
 | |
|     return listeners > 0;
 | |
| }
 | |
| 
 | |
| void service_free(SERVICE *service)
 | |
| {
 | |
|     SERVICE *ptr;
 | |
|     SERVER_REF *srv;
 | |
|     if (service->stats.n_current)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     /* First of all remove from the linked list */
 | |
|     spinlock_acquire(&service_spin);
 | |
|     if (allServices == service)
 | |
|     {
 | |
|         allServices = service->next;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         ptr = allServices;
 | |
|         while (ptr && ptr->next != service)
 | |
|         {
 | |
|             ptr = ptr->next;
 | |
|         }
 | |
|         if (ptr)
 | |
|         {
 | |
|             ptr->next = service->next;
 | |
|         }
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     /* Clean up session and free the memory */
 | |
|     while (service->dbref)
 | |
|     {
 | |
|         srv = service->dbref;
 | |
|         service->dbref = service->dbref->next;
 | |
|         MXS_FREE(srv);
 | |
|     }
 | |
| 
 | |
|     MXS_FREE(service->name);
 | |
|     MXS_FREE(service->routerModule);
 | |
| 
 | |
|     config_parameter_free(service->svc_config_param);
 | |
|     serviceClearRouterOptions(service);
 | |
| 
 | |
|     MXS_FREE(service);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a listener to a service
 | |
|  *
 | |
|  * @param service Service where listener is added
 | |
|  * @param proto   Listener to add
 | |
|  */
 | |
| static void service_add_listener(SERVICE* service, SERV_LISTENER* proto)
 | |
| {
 | |
|     do
 | |
|     {
 | |
|         /** Read the current value of the list's head. This will be our expected
 | |
|          * value for the following compare-and-swap operation. */
 | |
|         proto->next = (SERV_LISTENER*)atomic_load_ptr((void**)&service->ports);
 | |
|     }
 | |
|     /** Compare the current value to our expected value and if they match, replace
 | |
|      * the current value with our new value. */
 | |
|     while (!atomic_cas_ptr((void**)&service->ports, (void**)&proto->next, proto));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a listener for the service
 | |
|  *
 | |
|  * @param service       The service
 | |
|  * @param protocol      The name of the protocol module
 | |
|  * @param address       The address to listen with
 | |
|  * @param port          The port to listen on
 | |
|  * @param authenticator Name of the authenticator to be used
 | |
|  * @param ssl           SSL configuration
 | |
|  *
 | |
|  * @return Created listener or NULL on error
 | |
|  */
 | |
| SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const char *protocol,
 | |
|                                      const char *address, unsigned short port, const char *authenticator,
 | |
|                                      const char *options, SSL_LISTENER *ssl)
 | |
| {
 | |
|     SERV_LISTENER *proto = listener_alloc(service, name, protocol, address,
 | |
|                                           port, authenticator, options, ssl);
 | |
| 
 | |
|     if (proto)
 | |
|     {
 | |
|         service_add_listener(service, proto);
 | |
|     }
 | |
| 
 | |
|     return proto;
 | |
| }
 | |
| 
 | |
| SERV_LISTENER* service_find_listener(SERVICE* service,
 | |
|                                      const char* socket,
 | |
|                                      const char* address, unsigned short port)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener))
 | |
|         {
 | |
|             bool is_same_port = false;
 | |
| 
 | |
|             if (port && (port == listener->port) &&
 | |
|                 ((address && listener->address && strcmp(listener->address, address) == 0) ||
 | |
|                  (address == NULL && listener->address == NULL)))
 | |
|             {
 | |
|                 is_same_port = true;
 | |
|             }
 | |
| 
 | |
|             bool is_same_socket = false;
 | |
| 
 | |
|             if (!is_same_port)
 | |
|             {
 | |
|                 if (socket && listener->address && strcmp(listener->address, socket) == 0)
 | |
|                 {
 | |
|                     is_same_socket = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (is_same_port || is_same_socket)
 | |
|             {
 | |
|                 return listener;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if a protocol/port pair is part of the service
 | |
|  *
 | |
|  * @param service       The service
 | |
|  * @param protocol      The name of the protocol module
 | |
|  * @param address       The address to listen on
 | |
|  * @param port          The port to listen on
 | |
|  * @return      True if the protocol/port is already part of the service
 | |
|  */
 | |
| bool serviceHasListener(SERVICE* service, const char* name, const char* protocol,
 | |
|                         const char* address, unsigned short port)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) &&
 | |
|             // Listener with same name exists
 | |
|             (strcmp(listener->name, name) == 0 ||
 | |
|              // Listener listening on the same interface and port exists
 | |
|              ((strcmp(listener->protocol, protocol) == 0 && listener->port == port &&
 | |
|                ((address && listener->address && strcmp(listener->address, address) == 0) ||
 | |
|                 (address == NULL && listener->address == NULL))))))
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool service_has_named_listener(SERVICE *service, const char *name)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Allocate a new server reference
 | |
|  *
 | |
|  * @param server Server to refer to
 | |
|  * @return Server reference or NULL on error
 | |
|  */
 | |
| static SERVER_REF* server_ref_create(SERVER *server)
 | |
| {
 | |
|     SERVER_REF *sref = (SERVER_REF*)MXS_MALLOC(sizeof(SERVER_REF));
 | |
| 
 | |
|     if (sref)
 | |
|     {
 | |
|         sref->next = NULL;
 | |
|         sref->server = server;
 | |
|         sref->weight = SERVICE_BASE_SERVER_WEIGHT;
 | |
|         sref->connections = 0;
 | |
|         sref->active = true;
 | |
|     }
 | |
| 
 | |
|     return sref;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a backend database server to a service
 | |
|  *
 | |
|  * @param service       The service to add the server to
 | |
|  * @param server        The server to add
 | |
|  */
 | |
| bool serviceAddBackend(SERVICE *service, SERVER *server)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (!serviceHasBackend(service, server))
 | |
|     {
 | |
|         SERVER_REF *new_ref = server_ref_create(server);
 | |
| 
 | |
|         if (new_ref)
 | |
|         {
 | |
|             rval = true;
 | |
|             spinlock_acquire(&service->spin);
 | |
| 
 | |
|             service->n_dbref++;
 | |
| 
 | |
|             if (service->dbref)
 | |
|             {
 | |
|                 SERVER_REF *ref = service->dbref;
 | |
|                 SERVER_REF *prev = ref;
 | |
| 
 | |
|                 while (ref)
 | |
|                 {
 | |
|                     if (ref->server == server)
 | |
|                     {
 | |
|                         ref->active = true;
 | |
|                         break;
 | |
|                     }
 | |
|                     prev = ref;
 | |
|                     ref = ref->next;
 | |
|                 }
 | |
| 
 | |
|                 if (ref == NULL)
 | |
|                 {
 | |
|                     /** A new server that hasn't been used by this service */
 | |
|                     atomic_synchronize();
 | |
|                     prev->next = new_ref;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_FREE(new_ref);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 atomic_synchronize();
 | |
|                 service->dbref = new_ref;
 | |
|             }
 | |
|             spinlock_release(&service->spin);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Remove a server from a service
 | |
|  *
 | |
|  * This function sets the server reference into an inactive state. This does not
 | |
|  * remove the server from the list or free any of the memory.
 | |
|  *
 | |
|  * @param service Service to modify
 | |
|  * @param server  Server to remove
 | |
|  */
 | |
| void serviceRemoveBackend(SERVICE *service, const SERVER *server)
 | |
| {
 | |
|     spinlock_acquire(&service->spin);
 | |
| 
 | |
|     for (SERVER_REF *ref = service->dbref; ref; ref = ref->next)
 | |
|     {
 | |
|         if (ref->server == server && ref->active)
 | |
|         {
 | |
|             ref->active = false;
 | |
|             service->n_dbref--;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service->spin);
 | |
| }
 | |
| /**
 | |
|  * Test if a server is part of a service
 | |
|  *
 | |
|  * @param service       The service to add the server to
 | |
|  * @param server        The server to add
 | |
|  * @return              Non-zero if the server is already part of the service
 | |
|  */
 | |
| bool
 | |
| serviceHasBackend(SERVICE *service, SERVER *server)
 | |
| {
 | |
|     SERVER_REF *ptr;
 | |
| 
 | |
|     spinlock_acquire(&service->spin);
 | |
|     ptr = service->dbref;
 | |
|     while (ptr)
 | |
|     {
 | |
|         if (ptr->server == server && ptr->active)
 | |
|         {
 | |
|             break;
 | |
|         }
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
|     spinlock_release(&service->spin);
 | |
| 
 | |
|     return ptr != NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a router option to a service
 | |
|  *
 | |
|  * @param service       The service to add the router option to
 | |
|  * @param option        The option string
 | |
|  */
 | |
| void
 | |
| serviceAddRouterOption(SERVICE *service, char *option)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     spinlock_acquire(&service->spin);
 | |
|     if (service->routerOptions == NULL)
 | |
|     {
 | |
|         service->routerOptions = (char **)MXS_CALLOC(2, sizeof(char *));
 | |
|         MXS_ABORT_IF_NULL(service->routerOptions);
 | |
|         service->routerOptions[0] = MXS_STRDUP_A(option);
 | |
|         service->routerOptions[1] = NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         for (i = 0; service->routerOptions[i]; i++)
 | |
|         {
 | |
|             ;
 | |
|         }
 | |
|         service->routerOptions = (char **)MXS_REALLOC(service->routerOptions, (i + 2) * sizeof(char *));
 | |
|         MXS_ABORT_IF_NULL(service->routerOptions);
 | |
|         service->routerOptions[i] = MXS_STRDUP_A(option);
 | |
|         service->routerOptions[i + 1] = NULL;
 | |
|     }
 | |
|     spinlock_release(&service->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Remove the router options for the service
 | |
|  *
 | |
|  * @param       service The service to remove the options from
 | |
|  */
 | |
| void
 | |
| serviceClearRouterOptions(SERVICE *service)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     spinlock_acquire(&service->spin);
 | |
|     if (service->routerOptions != NULL)
 | |
|     {
 | |
|         for (i = 0; service->routerOptions[i]; i++)
 | |
|         {
 | |
|             MXS_FREE(service->routerOptions[i]);
 | |
|         }
 | |
|         MXS_FREE(service->routerOptions);
 | |
|         service->routerOptions = NULL;
 | |
|     }
 | |
|     spinlock_release(&service->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the service user that is used to log in to the backebd servers
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param user          The user name to use for connections
 | |
|  * @param auth          The authentication data we need, e.g. MySQL SHA1 password
 | |
|  * @return      0 on failure
 | |
|  */
 | |
| int
 | |
| serviceSetUser(SERVICE *service, const char *user, const char *auth)
 | |
| {
 | |
|     if (service->credentials.name != user)
 | |
|     {
 | |
|         snprintf(service->credentials.name,
 | |
|                  sizeof(service->credentials.name), "%s", user);
 | |
|     }
 | |
| 
 | |
|     if (service->credentials.authdata != auth)
 | |
|     {
 | |
|         snprintf(service->credentials.authdata,
 | |
|                  sizeof(service->credentials.authdata), "%s", auth);
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get the service user that is used to log in to the backebd servers
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param user          The user name to use for connections
 | |
|  * @param auth          The authentication data we need, e.g. MySQL SHA1 password
 | |
|  * @return              0 on failure
 | |
|  */
 | |
| int
 | |
| serviceGetUser(SERVICE *service, char **user, char **auth)
 | |
| {
 | |
|     if (service->credentials.name == NULL || service->credentials.authdata == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     *user = service->credentials.name;
 | |
|     *auth = service->credentials.authdata;
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable root user for this service
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param action        1 for root enable, 0 for disable access
 | |
|  * @return              0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceEnableRootUser(SERVICE *service, int action)
 | |
| {
 | |
|     if (action != 0 && action != 1)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     service->enable_root = action;
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable loading the user data from only one server or all of them
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param action        1 for all servers, 0 for single server
 | |
|  * @return              0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceAuthAllServers(SERVICE *service, int action)
 | |
| {
 | |
|     if (action != 0 && action != 1)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     service->users_from_all = action;
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Whether to strip escape characters from the name of the database the client
 | |
|  * is connecting to.
 | |
|  * @param service Service to configure
 | |
|  * @param action 0 for disabled, 1 for enabled
 | |
|  * @return 1 if successful, 0 on error
 | |
|  */
 | |
| int serviceStripDbEsc(SERVICE* service, int action)
 | |
| {
 | |
|     if (action != 0 && action != 1)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     service->strip_db_esc = action;
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets the session timeout for the service.
 | |
|  * @param service Service to configure
 | |
|  * @param val Timeout in seconds
 | |
|  * @return 1 on success, 0 when the value is invalid
 | |
|  */
 | |
| int
 | |
| serviceSetTimeout(SERVICE *service, int val)
 | |
| {
 | |
| 
 | |
|     if (val < 0)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /** Enable the session timeout checks if and only if at least one service is
 | |
|      * configured with a idle timeout. */
 | |
|     if ((service->conn_idle_timeout = val))
 | |
|     {
 | |
|         dcb_enable_session_timeouts();
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| void serviceSetVersionString(SERVICE *service, const char* value)
 | |
| {
 | |
|     if (service->version_string != value)
 | |
|     {
 | |
|         snprintf(service->version_string, sizeof(service->version_string), "%s", value);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sets the connection limits, if any, for the service.
 | |
|  * @param service Service to configure
 | |
|  * @param max The maximum number of client connections at any one time
 | |
|  * @param queued    The maximum number of connections to queue up when
 | |
|  *                  max_connections clients are already connected
 | |
|  * @param timeout   Maximum amount of time to wait for a connection to
 | |
|  *                  become available.
 | |
|  * @return 1 on success, 0 when the values are invalid
 | |
|  */
 | |
| int
 | |
| serviceSetConnectionLimits(SERVICE *service, int max, int queued, int timeout)
 | |
| {
 | |
| 
 | |
|     if (max < 0 || queued < 0)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     service->max_connections = max;
 | |
| 
 | |
|     ss_info_dassert(queued == 0, "Queued connections not implemented.");
 | |
|     ss_info_dassert(timeout == 0, "Queued connections not implemented.");
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable or disable the restarting of the service on failure.
 | |
|  * @param service Service to configure
 | |
|  * @param value A string representation of a boolean value
 | |
|  */
 | |
| void serviceSetRetryOnFailure(SERVICE *service, const char* value)
 | |
| {
 | |
|     if (value)
 | |
|     {
 | |
|         service->retry_start = config_truth_value(value);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void service_set_retry_interval(SERVICE *service, int value)
 | |
| {
 | |
|     ss_dassert(value > 0);
 | |
|     service->max_retry_interval = value;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the filters used by the service
 | |
|  *
 | |
|  * @param service       The service itself
 | |
|  * @param filters       ASCII string of filters to use
 | |
|  * @return True if loading and creating all filters was successful. False if a
 | |
|  * filter module was not found or the instance creation failed.
 | |
|  */
 | |
| bool
 | |
| serviceSetFilters(SERVICE *service, char *filters)
 | |
| {
 | |
|     MXS_FILTER_DEF **flist = NULL;
 | |
|     char *ptr = NULL, *brkt = NULL;
 | |
|     int n = 0;
 | |
|     bool rval = true;
 | |
|     uint64_t capabilities = 0;
 | |
| 
 | |
|     if ((flist = (MXS_FILTER_DEF **) MXS_MALLOC(sizeof(MXS_FILTER_DEF *))) == NULL)
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
|     ptr = strtok_r(filters, "|", &brkt);
 | |
|     while (ptr)
 | |
|     {
 | |
|         fix_section_name(ptr);
 | |
| 
 | |
|         n++;
 | |
|         MXS_FILTER_DEF **tmp;
 | |
|         if ((tmp = (MXS_FILTER_DEF **) MXS_REALLOC(flist,
 | |
|                                                    (n + 1) * sizeof(MXS_FILTER_DEF *))) == NULL)
 | |
|         {
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         flist = tmp;
 | |
|         char *filter_name = trim(ptr);
 | |
| 
 | |
|         if ((flist[n - 1] = filter_def_find(filter_name)))
 | |
|         {
 | |
|             if (filter_load(flist[n - 1]))
 | |
|             {
 | |
|                 const MXS_MODULE* module = get_module(flist[n - 1]->module, MODULE_FILTER);
 | |
|                 ss_dassert(module);
 | |
|                 capabilities |= module->module_capabilities;
 | |
|                 if (flist[n - 1]->obj->getCapabilities)
 | |
|                 {
 | |
|                     capabilities |= flist[n - 1]->obj->getCapabilities(flist[n - 1]->filter);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Failed to load filter '%s' for service '%s'.",
 | |
|                           filter_name, service->name);
 | |
|                 rval = false;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Unable to find filter '%s' for service '%s'",
 | |
|                       filter_name, service->name);
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         flist[n] = NULL;
 | |
|         ptr = strtok_r(NULL, "|", &brkt);
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         service->filters = flist;
 | |
|         service->n_filters = n;
 | |
|         service->capabilities |= capabilities;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_FREE(flist);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a named service
 | |
|  *
 | |
|  * @param servname      The name of the service to find
 | |
|  * @return The service or NULL if not found
 | |
|  */
 | |
| SERVICE *
 | |
| service_find(const char *servname)
 | |
| {
 | |
|     SERVICE *service;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     while (service && strcmp(service->name, servname) != 0)
 | |
|     {
 | |
|         service = service->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return service;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Print details of an individual service
 | |
|  *
 | |
|  * @param service       Service to print
 | |
|  */
 | |
| void
 | |
| printService(SERVICE *service)
 | |
| {
 | |
|     SERVER_REF  *ptr = service->dbref;
 | |
|     struct tm result;
 | |
|     char time_buf[30];
 | |
|     int i;
 | |
| 
 | |
| 
 | |
|     printf("\tService:                              %s\n", service->name);
 | |
|     printf("\tRouter:                               %s\n", service->routerModule);
 | |
|     printf("\tStarted:              %s",
 | |
|            asctime_r(localtime_r(&service->stats.started, &result), time_buf));
 | |
|     printf("\tBackend databases\n");
 | |
|     while (ptr)
 | |
|     {
 | |
|         printf("\t\t[%s]:%d  Protocol: %s\n", ptr->server->name, ptr->server->port, ptr->server->protocol);
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
|     if (service->n_filters)
 | |
|     {
 | |
|         printf("\tFilter chain:         ");
 | |
|         for (i = 0; i < service->n_filters; i++)
 | |
|         {
 | |
|             printf("%s %s ", service->filters[i]->name,
 | |
|                    i + 1 < service->n_filters ? "|" : "");
 | |
|         }
 | |
|         printf("\n");
 | |
|     }
 | |
| 
 | |
|     printf("\tTotal connections:    %d\n", service->stats.n_sessions);
 | |
|     printf("\tCurrently connected:  %d\n", service->stats.n_current);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print all services
 | |
|  *
 | |
|  * Designed to be called within a debugger session in order
 | |
|  * to display all active services within the gateway
 | |
|  */
 | |
| void
 | |
| printAllServices()
 | |
| {
 | |
|     SERVICE *ptr;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     ptr = allServices;
 | |
|     while (ptr)
 | |
|     {
 | |
|         printService(ptr);
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print all services to a DCB
 | |
|  *
 | |
|  * Designed to be called within a CLI command in order
 | |
|  * to display all active services within the gateway
 | |
|  */
 | |
| void
 | |
| dprintAllServices(DCB *dcb)
 | |
| {
 | |
|     SERVICE *ptr;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     ptr = allServices;
 | |
|     while (ptr)
 | |
|     {
 | |
|         dprintService(dcb, ptr);
 | |
|         ptr = ptr->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print details of a single service.
 | |
|  *
 | |
|  * @param dcb           DCB to print data to
 | |
|  * @param service       The service to print
 | |
|  */
 | |
| void dprintService(DCB *dcb, SERVICE *service)
 | |
| {
 | |
|     SERVER_REF *server = service->dbref;
 | |
|     struct tm result;
 | |
|     char timebuf[30];
 | |
|     int i;
 | |
| 
 | |
|     dcb_printf(dcb, "\tService:                             %s\n", service->name);
 | |
|     dcb_printf(dcb, "\tRouter:                              %s\n", service->routerModule);
 | |
|     switch (service->state)
 | |
|     {
 | |
|     case SERVICE_STATE_STARTED:
 | |
|         dcb_printf(dcb, "\tState:                               Started\n");
 | |
|         break;
 | |
|     case SERVICE_STATE_STOPPED:
 | |
|         dcb_printf(dcb, "\tState:                               Stopped\n");
 | |
|         break;
 | |
|     case SERVICE_STATE_FAILED:
 | |
|         dcb_printf(dcb, "\tState:                               Failed\n");
 | |
|         break;
 | |
|     case SERVICE_STATE_ALLOC:
 | |
|         dcb_printf(dcb, "\tState:                               Allocated\n");
 | |
|         break;
 | |
|     }
 | |
|     if (service->router && service->router_instance)
 | |
|     {
 | |
|         service->router->diagnostics(service->router_instance, dcb);
 | |
|     }
 | |
|     dcb_printf(dcb, "\tStarted:                             %s",
 | |
|                asctime_r(localtime_r(&service->stats.started, &result), timebuf));
 | |
|     dcb_printf(dcb, "\tRoot user access:                    %s\n",
 | |
|                service->enable_root ? "Enabled" : "Disabled");
 | |
|     if (service->n_filters)
 | |
|     {
 | |
|         dcb_printf(dcb, "\tFilter chain:                ");
 | |
|         for (i = 0; i < service->n_filters; i++)
 | |
|         {
 | |
|             dcb_printf(dcb, "%s %s ", service->filters[i]->name,
 | |
|                        i + 1 < service->n_filters ? "|" : "");
 | |
|         }
 | |
|         dcb_printf(dcb, "\n");
 | |
|     }
 | |
|     dcb_printf(dcb, "\tBackend databases:\n");
 | |
|     while (server)
 | |
|     {
 | |
|         if (SERVER_REF_IS_ACTIVE(server))
 | |
|         {
 | |
|             dcb_printf(dcb, "\t\t[%s]:%d    Protocol: %s    Name: %s\n",
 | |
|                        server->server->name, server->server->port,
 | |
|                        server->server->protocol, server->server->unique_name);
 | |
|         }
 | |
|         server = server->next;
 | |
|     }
 | |
|     if (*service->weightby)
 | |
|     {
 | |
|         dcb_printf(dcb, "\tRouting weight parameter:            %s\n",
 | |
|                    service->weightby);
 | |
|     }
 | |
| 
 | |
|     dcb_printf(dcb, "\tTotal connections:                   %d\n",
 | |
|                service->stats.n_sessions);
 | |
|     dcb_printf(dcb, "\tCurrently connected:                 %d\n",
 | |
|                service->stats.n_current);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * List the defined services in a tabular format.
 | |
|  *
 | |
|  * @param dcb           DCB to print the service list to.
 | |
|  */
 | |
| void
 | |
| dListServices(DCB *dcb)
 | |
| {
 | |
|     SERVICE *service;
 | |
|     const char HORIZ_SEPARATOR[] = "--------------------------+-------------------"
 | |
|                                    "+--------+----------------+-------------------\n";
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     if (service)
 | |
|     {
 | |
|         dcb_printf(dcb, "Services.\n");
 | |
|         dcb_printf(dcb, "%s", HORIZ_SEPARATOR);
 | |
|         dcb_printf(dcb, "%-25s | %-17s | #Users | Total Sessions | Backend databases\n",
 | |
|                    "Service Name", "Router Module");
 | |
|         dcb_printf(dcb, "%s", HORIZ_SEPARATOR);
 | |
|     }
 | |
|     while (service)
 | |
|     {
 | |
|         ss_dassert(service->stats.n_current >= 0);
 | |
|         dcb_printf(dcb, "%-25s | %-17s | %6d | %14d | ",
 | |
|                    service->name, service->routerModule,
 | |
|                    service->stats.n_current, service->stats.n_sessions);
 | |
| 
 | |
|         SERVER_REF* server_ref = service->dbref;
 | |
|         bool first = true;
 | |
|         while (server_ref)
 | |
|         {
 | |
|             if (SERVER_REF_IS_ACTIVE(server_ref))
 | |
|             {
 | |
|                 if (first)
 | |
|                 {
 | |
|                     dcb_printf(dcb, "%s", server_ref->server->unique_name);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     dcb_printf(dcb, ", %s", server_ref->server->unique_name);
 | |
|                 }
 | |
|                 first = false;
 | |
|             }
 | |
|             server_ref = server_ref->next;
 | |
|         }
 | |
|         dcb_printf(dcb, "\n");
 | |
|         service = service->next;
 | |
|     }
 | |
|     if (allServices)
 | |
|     {
 | |
|         dcb_printf(dcb, "%s\n", HORIZ_SEPARATOR);
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * List the defined listeners in a tabular format.
 | |
|  *
 | |
|  * @param dcb           DCB to print the service list to.
 | |
|  */
 | |
| void
 | |
| dListListeners(DCB *dcb)
 | |
| {
 | |
|     SERVICE *service;
 | |
|     SERV_LISTENER *port;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     if (service)
 | |
|     {
 | |
|         dcb_printf(dcb, "Listeners.\n");
 | |
|         dcb_printf(dcb, "---------------------+---------------------+"
 | |
|                    "--------------------+-----------------+-------+--------\n");
 | |
|         dcb_printf(dcb, "%-20s | %-19s | %-18s | %-15s | Port  | State\n",
 | |
|                    "Name", "Service Name", "Protocol Module", "Address");
 | |
|         dcb_printf(dcb, "---------------------+---------------------+"
 | |
|                    "--------------------+-----------------+-------+--------\n");
 | |
|     }
 | |
|     while (service)
 | |
|     {
 | |
|         LISTENER_ITERATOR iter;
 | |
| 
 | |
|         for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|              listener; listener = listener_iterator_next(&iter))
 | |
|         {
 | |
|             if (listener_is_active(listener))
 | |
|             {
 | |
|                 dcb_printf(dcb, "%-20s | %-19s | %-18s | %-15s | %5d | %s\n",
 | |
|                            listener->name, service->name, listener->protocol,
 | |
|                            (listener && listener->address) ? listener->address : "*",
 | |
|                            listener->port,
 | |
|                            (!listener->listener ||
 | |
|                             !listener->listener->session ||
 | |
|                             listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ?
 | |
|                            "Stopped" : "Running");
 | |
|             }
 | |
|         }
 | |
|         service = service->next;
 | |
|     }
 | |
|     if (allServices)
 | |
|     {
 | |
|         dcb_printf(dcb, "---------------------+---------------------+"
 | |
|                    "--------------------+-----------------+-------+--------\n\n");
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Update the definition of a service
 | |
|  *
 | |
|  * @param service       The service to update
 | |
|  * @param router        The router module to use
 | |
|  * @param user          The user to use to extract information from the database
 | |
|  * @param auth          The password for the user above
 | |
|  */
 | |
| void
 | |
| service_update(SERVICE *service, char *router, char *user, char *auth)
 | |
| {
 | |
|     MXS_ROUTER_OBJECT *router_obj;
 | |
| 
 | |
|     if (!strcmp(service->routerModule, router))
 | |
|     {
 | |
|         if ((router_obj = (MXS_ROUTER_OBJECT*)load_module(router, MODULE_ROUTER)) == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Failed to update router "
 | |
|                       "for service %s to %s.",
 | |
|                       service->name,
 | |
|                       router);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_NOTICE("Update router for service %s to %s.",
 | |
|                        service->name,
 | |
|                        router);
 | |
|             MXS_FREE(service->routerModule);
 | |
|             service->routerModule = MXS_STRDUP_A(router);
 | |
|             service->router = router_obj;
 | |
|         }
 | |
|     }
 | |
|     if (user &&
 | |
|         (strcmp(service->credentials.name, user) != 0 ||
 | |
|          strcmp(service->credentials.authdata, auth) != 0))
 | |
|     {
 | |
|         MXS_NOTICE("Update credentials for service %s.", service->name);
 | |
|         serviceSetUser(service, user, auth);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Refresh the database users for the service
 | |
|  * This function replaces the MySQL users used by the service with the latest
 | |
|  * version found on the backend servers. There is a limit on how often the users
 | |
|  * can be reloaded and if this limit is exceeded, the reload will fail.
 | |
|  * @param service Service to reload
 | |
|  * @return 0 on success and 1 on error
 | |
|  */
 | |
| int service_refresh_users(SERVICE *service)
 | |
| {
 | |
|     ss_dassert(service);
 | |
|     int ret = 1;
 | |
|     int self = mxs_worker_get_current_id();
 | |
|     ss_dassert(self >= 0);
 | |
|     time_t now = time(NULL);
 | |
| 
 | |
|     if ((service->capabilities & ACAP_TYPE_ASYNC) == 0)
 | |
|     {
 | |
|         spinlock_acquire(&service->spin);
 | |
|         // Use only one rate limitation for synchronous authenticators to keep
 | |
|         // rate limitations synchronous as well
 | |
|         self = 0;
 | |
|     }
 | |
| 
 | |
|     MXS_CONFIG* config = config_get_global_options();
 | |
| 
 | |
|     /* Check if refresh rate limit has been exceeded */
 | |
|     if (now < service->rate_limits[self].last + config->users_refresh_time)
 | |
|     {
 | |
|         if (!service->rate_limits[self].warned)
 | |
|         {
 | |
|             MXS_WARNING("[%s] Refresh rate limit (once every %ld seconds) exceeded for "
 | |
|                         "load of users' table.",
 | |
|                         service->name, config->users_refresh_time);
 | |
|             service->rate_limits[self].warned = true;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         service->rate_limits[self].last = now;
 | |
|         service->rate_limits[self].warned = false;
 | |
| 
 | |
| 
 | |
|         ret = 0;
 | |
|         LISTENER_ITERATOR iter;
 | |
| 
 | |
|         for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|              listener; listener = listener_iterator_next(&iter))
 | |
|         {
 | |
|             /** Load the authentication users before before starting the listener */
 | |
|             if (listener_is_active(listener) && listener->listener &&
 | |
|                 listener->listener->authfunc.loadusers)
 | |
|             {
 | |
|                 switch (listener->listener->authfunc.loadusers(listener))
 | |
|                 {
 | |
|                 case MXS_AUTH_LOADUSERS_FATAL:
 | |
|                     MXS_ERROR("[%s] Fatal error when loading users for listener '%s',"
 | |
|                               " authentication will not work.", service->name, listener->name);
 | |
|                     ret = 1;
 | |
|                     break;
 | |
| 
 | |
|                 case MXS_AUTH_LOADUSERS_ERROR:
 | |
|                     MXS_WARNING("[%s] Failed to load users for listener '%s', authentication"
 | |
|                                 " might not work.", service->name, listener->name);
 | |
|                     ret = 1;
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if ((service->capabilities & ACAP_TYPE_ASYNC) == 0)
 | |
|     {
 | |
|         spinlock_release(&service->spin);
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| void service_add_parameters(SERVICE *service, const MXS_CONFIG_PARAMETER *param)
 | |
| {
 | |
|     while (param)
 | |
|     {
 | |
|         MXS_CONFIG_PARAMETER *new_param = config_clone_param(param);
 | |
|         new_param->next = service->svc_config_param;
 | |
|         service->svc_config_param = new_param;
 | |
|         param = param->next;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Function to find a string in typelib_t
 | |
|  * (similar to find_type() of mysys/typelib.c)
 | |
|  *
 | |
|  *       SYNOPSIS
 | |
|  *       find_type()
 | |
|  *       lib                  typelib_t
 | |
|  *       find                 String to find
 | |
|  *       length               Length of string to find
 | |
|  *       part_match           Allow part matching of value
 | |
|  *
 | |
|  *       RETURN
 | |
|  *       0 error
 | |
|  *       > 0 position in TYPELIB->type_names +1
 | |
|  */
 | |
| static int find_type(typelib_t*  tl,
 | |
|                      const char* needle,
 | |
|                      int         maxlen)
 | |
| {
 | |
|     int i;
 | |
| 
 | |
|     if (tl == NULL || needle == NULL || maxlen <= 0)
 | |
|     {
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < tl->tl_nelems; i++)
 | |
|     {
 | |
|         if (strncasecmp(tl->tl_p_elems[i], needle, maxlen) == 0)
 | |
|         {
 | |
|             return i + 1;
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add qualified config parameter to SERVICE struct.
 | |
|  */
 | |
| static void service_add_qualified_param(SERVICE*          svc,
 | |
|                                         MXS_CONFIG_PARAMETER* param)
 | |
| {
 | |
|     spinlock_acquire(&svc->spin);
 | |
| 
 | |
|     if (svc->svc_config_param == NULL)
 | |
|     {
 | |
|         svc->svc_config_param = config_clone_param(param);
 | |
|         svc->svc_config_param->next = NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_CONFIG_PARAMETER* p = svc->svc_config_param;
 | |
|         MXS_CONFIG_PARAMETER* prev = NULL;
 | |
| 
 | |
|         while (true)
 | |
|         {
 | |
|             MXS_CONFIG_PARAMETER* old;
 | |
| 
 | |
|             /** Replace existing parameter in the list, free old */
 | |
|             if (strncasecmp(param->name,
 | |
|                             p->name,
 | |
|                             strlen(param->name)) == 0)
 | |
|             {
 | |
|                 old = p;
 | |
|                 p = config_clone_param(param);
 | |
|                 p->next = old->next;
 | |
| 
 | |
|                 if (prev != NULL)
 | |
|                 {
 | |
|                     prev->next = p;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     svc->svc_config_param = p;
 | |
|                 }
 | |
|                 MXS_FREE(old);
 | |
|                 break;
 | |
|             }
 | |
|             prev = p;
 | |
|             p = p->next;
 | |
| 
 | |
|             /** Hit end of the list, add new parameter */
 | |
|             if (p == NULL)
 | |
|             {
 | |
|                 p = config_clone_param(param);
 | |
|                 prev->next = p;
 | |
|                 p->next = NULL;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     /** Increment service's configuration version */
 | |
|     atomic_add(&svc->svc_config_version, 1);
 | |
|     spinlock_release(&svc->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the name of the service
 | |
|  *
 | |
|  * @param svc           The service
 | |
|  */
 | |
| char *
 | |
| service_get_name(SERVICE *svc)
 | |
| {
 | |
|     return svc->name;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the weighting parameter for the service
 | |
|  *
 | |
|  * @param       service         The service pointer
 | |
|  * @param       weightby        The parameter name to weight the routing by
 | |
|  */
 | |
| void serviceWeightBy(SERVICE *service, const char *weightby)
 | |
| {
 | |
|     if (service->weightby != weightby)
 | |
|     {
 | |
|         snprintf(service->weightby, sizeof(service->weightby), "%s", weightby);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the parameter the wervice shoudl use to weight connections
 | |
|  * by
 | |
|  * @param service               The Service pointer
 | |
|  */
 | |
| const char* serviceGetWeightingParameter(SERVICE *service)
 | |
| {
 | |
|     return service->weightby;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable localhost authentication match criteria
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param action        1 for enable, 0 for disable access
 | |
|  * @return              0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action)
 | |
| {
 | |
|     if (action != 0 && action != 1)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     service->localhost_match_wildcard_host = action;
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| void service_shutdown()
 | |
| {
 | |
|     SERVICE* svc;
 | |
|     spinlock_acquire(&service_spin);
 | |
|     svc = allServices;
 | |
|     while (svc != NULL)
 | |
|     {
 | |
|         svc->svc_do_shutdown = true;
 | |
|         svc = svc->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Destroy a listener
 | |
|  *
 | |
|  * @param sl  The listener to destroy.
 | |
|  *
 | |
|  * @return The next listener or NULL if there is not one.
 | |
|  */
 | |
| static SERV_LISTENER* service_destroy_listener(SERV_LISTENER* sl)
 | |
| {
 | |
|     SERV_LISTENER* next = sl->next;
 | |
| 
 | |
|     dcb_close(sl->listener);
 | |
| 
 | |
|     // TODO: What else should be closed and freed here?
 | |
| 
 | |
|     return next;
 | |
| }
 | |
| 
 | |
| typedef std::map<MXS_FILTER*, void(*)(MXS_FILTER*)> DestructorsByFilter;
 | |
| /**
 | |
|  * Destroy one service instance
 | |
|  *
 | |
|  * @param svc  The service to destroy.
 | |
|  */
 | |
| static void service_destroy_instance(SERVICE* svc, DestructorsByFilter* filters_to_delete)
 | |
| {
 | |
|     SERV_LISTENER* sl = svc->ports;
 | |
| 
 | |
|     while (sl)
 | |
|     {
 | |
|         sl = service_destroy_listener(sl);
 | |
|     }
 | |
| 
 | |
|     /* Call destroyInstance hook for routers */
 | |
|     if (svc->router->destroyInstance && svc->router_instance)
 | |
|     {
 | |
|         svc->router->destroyInstance(svc->router_instance);
 | |
|     }
 | |
|     if (svc->n_filters)
 | |
|     {
 | |
|         MXS_FILTER_DEF **filters = svc->filters;
 | |
|         for (int i = 0; i < svc->n_filters; i++)
 | |
|         {
 | |
|             if (filters[i]->obj->destroyInstance && filters[i]->filter)
 | |
|             {
 | |
|                 if (filters_to_delete->find(filters[i]->filter) == filters_to_delete->end())
 | |
|                 {
 | |
|                     auto entry = std::make_pair(filters[i]->filter, filters[i]->obj->destroyInstance);
 | |
| 
 | |
|                     filters_to_delete->insert(entry);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void service_destroy_instances(void)
 | |
| {
 | |
|     spinlock_acquire(&service_spin);
 | |
|     DestructorsByFilter filters_to_delete;
 | |
|     SERVICE* svc = allServices;
 | |
|     while (svc != NULL)
 | |
|     {
 | |
|         ss_dassert(svc->svc_do_shutdown);
 | |
|         service_destroy_instance(svc, &filters_to_delete);
 | |
| 
 | |
|         svc = svc->next;
 | |
|     }
 | |
| 
 | |
|     for (auto i = filters_to_delete.begin(); i != filters_to_delete.end(); ++i)
 | |
|     {
 | |
|         i->second(i->first);
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the count of all sessions active for all services
 | |
|  *
 | |
|  * @return Count of all active sessions
 | |
|  */
 | |
| int
 | |
| serviceSessionCountAll()
 | |
| {
 | |
|     SERVICE *service;
 | |
|     int rval = 0;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     while (service)
 | |
|     {
 | |
|         rval += service->stats.n_current;
 | |
|         service = service->next;
 | |
|     }
 | |
|     spinlock_release(&service_spin);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provide a row to the result set that defines the set of service
 | |
|  * listeners
 | |
|  *
 | |
|  * TODO: Replace these
 | |
|  *
 | |
|  * @param set   The result set
 | |
|  * @param data  The index of the row to send
 | |
|  * @return The next row or NULL
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| serviceListenerRowCallback(RESULTSET *set, void *data)
 | |
| {
 | |
|     int *rowno = (int *)data;
 | |
|     int i = 0;;
 | |
|     char buf[20];
 | |
|     RESULT_ROW *row;
 | |
|     SERVICE *service;
 | |
|     SERV_LISTENER *lptr = NULL;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     if (service)
 | |
|     {
 | |
|         lptr = service->ports;
 | |
|     }
 | |
|     while (i < *rowno && service)
 | |
|     {
 | |
|         lptr = service->ports;
 | |
|         while (i < *rowno && lptr)
 | |
|         {
 | |
|             if ((lptr = lptr->next) != NULL)
 | |
|             {
 | |
|                 i++;
 | |
|             }
 | |
|         }
 | |
|         if (i < *rowno)
 | |
|         {
 | |
|             service = service->next;
 | |
|             if (service && (lptr = service->ports) != NULL)
 | |
|             {
 | |
|                 i++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     if (lptr == NULL)
 | |
|     {
 | |
|         spinlock_release(&service_spin);
 | |
|         MXS_FREE(data);
 | |
|         return NULL;
 | |
|     }
 | |
|     (*rowno)++;
 | |
|     row = resultset_make_row(set);
 | |
|     resultset_row_set(row, 0, service->name);
 | |
|     resultset_row_set(row, 1, lptr->protocol);
 | |
|     resultset_row_set(row, 2, (lptr && lptr->address) ? lptr->address : "*");
 | |
|     sprintf(buf, "%d", lptr->port);
 | |
|     resultset_row_set(row, 3, buf);
 | |
|     resultset_row_set(row, 4,
 | |
|                       (!lptr->listener || !lptr->listener->session ||
 | |
|                        lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ?
 | |
|                       "Stopped" : "Running");
 | |
|     spinlock_release(&service_spin);
 | |
|     return row;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a resultset that has the current set of services in it
 | |
|  *
 | |
|  * @return A Result set
 | |
|  */
 | |
| RESULTSET *
 | |
| serviceGetListenerList()
 | |
| {
 | |
|     RESULTSET *set;
 | |
|     int *data;
 | |
| 
 | |
|     if ((data = (int *)MXS_MALLOC(sizeof(int))) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
|     *data = 0;
 | |
|     if ((set = resultset_create(serviceListenerRowCallback, data)) == NULL)
 | |
|     {
 | |
|         MXS_FREE(data);
 | |
|         return NULL;
 | |
|     }
 | |
|     resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "Protocol Module", 20, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "Address", 15, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "Port", 5, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "State", 8, COL_TYPE_VARCHAR);
 | |
| 
 | |
|     return set;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provide a row to the result set that defines the set of services
 | |
|  *
 | |
|  * @param set   The result set
 | |
|  * @param data  The index of the row to send
 | |
|  * @return The next row or NULL
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| serviceRowCallback(RESULTSET *set, void *data)
 | |
| {
 | |
|     int *rowno = (int *)data;
 | |
|     int i = 0;
 | |
|     char buf[20];
 | |
|     RESULT_ROW *row;
 | |
|     SERVICE *service;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
|     service = allServices;
 | |
|     while (i < *rowno && service)
 | |
|     {
 | |
|         i++;
 | |
|         service = service->next;
 | |
|     }
 | |
|     if (service == NULL)
 | |
|     {
 | |
|         spinlock_release(&service_spin);
 | |
|         MXS_FREE(data);
 | |
|         return NULL;
 | |
|     }
 | |
|     (*rowno)++;
 | |
|     row = resultset_make_row(set);
 | |
|     resultset_row_set(row, 0, service->name);
 | |
|     resultset_row_set(row, 1, service->routerModule);
 | |
|     sprintf(buf, "%d", service->stats.n_current);
 | |
|     resultset_row_set(row, 2, buf);
 | |
|     sprintf(buf, "%d", service->stats.n_sessions);
 | |
|     resultset_row_set(row, 3, buf);
 | |
|     spinlock_release(&service_spin);
 | |
|     return row;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a result set that has the current set of services in it
 | |
|  *
 | |
|  * @return A Result set
 | |
|  */
 | |
| RESULTSET *
 | |
| serviceGetList()
 | |
| {
 | |
|     RESULTSET *set;
 | |
|     int *data;
 | |
| 
 | |
|     if ((data = (int *)MXS_MALLOC(sizeof(int))) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
|     *data = 0;
 | |
|     if ((set = resultset_create(serviceRowCallback, data)) == NULL)
 | |
|     {
 | |
|         MXS_FREE(data);
 | |
|         return NULL;
 | |
|     }
 | |
|     resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "Router Module", 20, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "No. Sessions", 10, COL_TYPE_VARCHAR);
 | |
|     resultset_add_column(set, "Total Sessions", 10, COL_TYPE_VARCHAR);
 | |
| 
 | |
|     return set;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Function called by the housekeeper thread to retry starting of a service
 | |
|  * @param data Service to restart
 | |
|  */
 | |
| static void service_internal_restart(void *data)
 | |
| {
 | |
|     SERVICE* service = (SERVICE*)data;
 | |
|     serviceStartAllPorts(service);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check that all services have listeners
 | |
|  * @return True if all services have listeners
 | |
|  */
 | |
| bool service_all_services_have_listeners()
 | |
| {
 | |
|     bool rval = true;
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     SERVICE* service = allServices;
 | |
| 
 | |
|     while (service)
 | |
|     {
 | |
|         LISTENER_ITERATOR iter;
 | |
|         SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
| 
 | |
|         if (listener == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Service '%s' has no listeners.", service->name);
 | |
|             rval = false;
 | |
|         }
 | |
| 
 | |
|         service = service->next;
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static void service_calculate_weights(SERVICE *service)
 | |
| {
 | |
|     const char *weightby = serviceGetWeightingParameter(service);
 | |
| 
 | |
|     if (*weightby && service->dbref)
 | |
|     {
 | |
|         char buf[50]; // Enough to hold most numbers
 | |
|         /** Service has a weighting parameter and at least one server */
 | |
|         int total = 0;
 | |
| 
 | |
|         /** Calculate total weight */
 | |
|         for (SERVER_REF *server = service->dbref; server; server = server->next)
 | |
|         {
 | |
|             server->weight = SERVICE_BASE_SERVER_WEIGHT;
 | |
| 
 | |
|             if (server_get_parameter(server->server, weightby, buf, sizeof(buf)))
 | |
|             {
 | |
|                 total += atoi(buf);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (total == 0)
 | |
|         {
 | |
|             MXS_WARNING("Weighting Parameter for service '%s' will be ignored as "
 | |
|                         "no servers have values for the parameter '%s'.",
 | |
|                         service->name, weightby);
 | |
|         }
 | |
|         else if (total < 0)
 | |
|         {
 | |
|             MXS_ERROR("Sum of weighting parameter '%s' for service '%s' exceeds "
 | |
|                       "maximum value of %d. Weighting will be ignored.",
 | |
|                       weightby, service->name, INT_MAX);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /** Calculate the relative weight of the servers */
 | |
|             for (SERVER_REF *server = service->dbref; server; server = server->next)
 | |
|             {
 | |
|                 if (server_get_parameter(server->server, weightby, buf, sizeof(buf)))
 | |
|                 {
 | |
|                     int wght = atoi(buf);
 | |
|                     int perc = (wght * SERVICE_BASE_SERVER_WEIGHT) / total;
 | |
| 
 | |
|                     if (perc == 0)
 | |
|                     {
 | |
|                         MXS_WARNING("Weighting parameter '%s' with a value of %d for"
 | |
|                                     " server '%s' rounds down to zero with total weight"
 | |
|                                     " of %d for service '%s'. No queries will be "
 | |
|                                     "routed to this server as long as a server with"
 | |
|                                     " positive weight is available.",
 | |
|                                     weightby, wght, server->server->unique_name,
 | |
|                                     total, service->name);
 | |
|                     }
 | |
|                     else if (perc < 0)
 | |
|                     {
 | |
|                         MXS_ERROR("Weighting parameter '%s' for server '%s' is too large, "
 | |
|                                   "maximum value is %d. No weighting will be used for this "
 | |
|                                   "server.", weightby, server->server->unique_name,
 | |
|                                   INT_MAX / SERVICE_BASE_SERVER_WEIGHT);
 | |
|                         perc = SERVICE_BASE_SERVER_WEIGHT;
 | |
|                     }
 | |
|                     server->weight = perc;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_WARNING("Server '%s' has no parameter '%s' used for weighting"
 | |
|                                 " for service '%s'.", server->server->unique_name,
 | |
|                                 weightby, service->name);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void service_update_weights()
 | |
| {
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service; service = service->next)
 | |
|     {
 | |
|         service_calculate_weights(service);
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| bool service_server_in_use(const SERVER *server)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service && !rval; service = service->next)
 | |
|     {
 | |
|         spinlock_acquire(&service->spin);
 | |
| 
 | |
|         for (SERVER_REF *ref = service->dbref; ref && !rval; ref = ref->next)
 | |
|         {
 | |
|             if (ref->active && ref->server == server)
 | |
|             {
 | |
|                 rval = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         spinlock_release(&service->spin);
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a service configuration at the location pointed by @c filename
 | |
|  *
 | |
|  * @param service Service to serialize into a configuration
 | |
|  * @param filename Filename where configuration is written
 | |
|  * @return True on success, false on error
 | |
|  */
 | |
| static bool create_service_config(const SERVICE *service, const char *filename)
 | |
| {
 | |
|     int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 | |
| 
 | |
|     if (file == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to open file '%s' when serializing service '%s': %d, %s",
 | |
|                   filename, service->name, errno, mxs_strerror(errno));
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * TODO: Check for return values on all of the dprintf calls
 | |
|      */
 | |
|     dprintf(file, "[%s]\n", service->name);
 | |
|     dprintf(file, "%s=service\n", CN_TYPE);
 | |
|     dprintf(file, "%s=%s\n", CN_USER, service->credentials.name);
 | |
|     dprintf(file, "%s=%s\n", CN_PASSWORD, service->credentials.authdata);
 | |
|     dprintf(file, "%s=%s\n", CN_ENABLE_ROOT_USER, service->enable_root ? "true" : "false");
 | |
|     dprintf(file, "%s=%d\n", CN_MAX_RETRY_INTERVAL, service->max_retry_interval);
 | |
|     dprintf(file, "%s=%d\n", CN_MAX_CONNECTIONS, service->max_connections);
 | |
|     dprintf(file, "%s=%ld\n", CN_CONNECTION_TIMEOUT, service->conn_idle_timeout);
 | |
|     dprintf(file, "%s=%s\n", CN_AUTH_ALL_SERVERS, service->users_from_all ? "true" : "false");
 | |
|     dprintf(file, "%s=%s\n", CN_STRIP_DB_ESC, service->strip_db_esc ? "true" : "false");
 | |
|     dprintf(file, "%s=%s\n", CN_LOCALHOST_MATCH_WILDCARD_HOST,
 | |
|             service->localhost_match_wildcard_host ? "true" : "false");
 | |
|     dprintf(file, "%s=%s\n", CN_LOG_AUTH_WARNINGS, service->log_auth_warnings ? "true" : "false");
 | |
|     dprintf(file, "%s=%s\n", CN_RETRY_ON_FAILURE, service->retry_start ? "true" : "false");
 | |
| 
 | |
|     if (*service->version_string)
 | |
|     {
 | |
|         dprintf(file, "%s=%s\n", CN_VERSION_STRING, service->version_string);
 | |
|     }
 | |
| 
 | |
|     if (*service->weightby)
 | |
|     {
 | |
|         dprintf(file, "%s=%s\n", CN_WEIGHTBY, service->weightby);
 | |
|     }
 | |
| 
 | |
|     if (service->dbref)
 | |
|     {
 | |
|         dprintf(file, "%s=", CN_SERVERS);
 | |
|         const char *sep = "";
 | |
| 
 | |
|         for (SERVER_REF *db = service->dbref; db; db = db->next)
 | |
|         {
 | |
|             if (SERVER_REF_IS_ACTIVE(db))
 | |
|             {
 | |
|                 dprintf(file, "%s%s", sep, db->server->unique_name);
 | |
|                 sep = ",";
 | |
|             }
 | |
|         }
 | |
|         dprintf(file, "\n");
 | |
|     }
 | |
| 
 | |
|     close(file);
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool service_serialize(const SERVICE *service)
 | |
| {
 | |
|     bool rval = false;
 | |
|     char filename[PATH_MAX];
 | |
|     snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(),
 | |
|              service->name);
 | |
| 
 | |
|     if (unlink(filename) == -1 && errno != ENOENT)
 | |
|     {
 | |
|         MXS_ERROR("Failed to remove temporary service configuration at '%s': %d, %s",
 | |
|                   filename, errno, mxs_strerror(errno));
 | |
|     }
 | |
|     else if (create_service_config(service, filename))
 | |
|     {
 | |
|         char final_filename[PATH_MAX];
 | |
|         strcpy(final_filename, filename);
 | |
| 
 | |
|         char *dot = strrchr(final_filename, '.');
 | |
|         ss_dassert(dot);
 | |
|         *dot = '\0';
 | |
| 
 | |
|         if (rename(filename, final_filename) == 0)
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Failed to rename temporary service configuration at '%s': %d, %s",
 | |
|                       filename, errno, mxs_strerror(errno));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool service_serialize_servers(const SERVICE *service)
 | |
| {
 | |
|     bool rval = false;
 | |
|     char filename[PATH_MAX];
 | |
|     snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(),
 | |
|              service->name);
 | |
| 
 | |
|     if (unlink(filename) == -1 && errno != ENOENT)
 | |
|     {
 | |
|         MXS_ERROR("Failed to remove temporary service configuration at '%s': %d, %s",
 | |
|                   filename, errno, mxs_strerror(errno));
 | |
|     }
 | |
|     else if (create_service_config(service, filename))
 | |
|     {
 | |
|         char final_filename[PATH_MAX];
 | |
|         strcpy(final_filename, filename);
 | |
| 
 | |
|         char *dot = strrchr(final_filename, '.');
 | |
|         ss_dassert(dot);
 | |
|         *dot = '\0';
 | |
| 
 | |
|         if (rename(filename, final_filename) == 0)
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Failed to rename temporary service configuration at '%s': %d, %s",
 | |
|                       filename, errno, mxs_strerror(errno));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| void service_print_users(DCB *dcb, const SERVICE *service)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) && listener->listener &&
 | |
|             listener->listener->authfunc.diagnostic)
 | |
|         {
 | |
|             dcb_printf(dcb, "User names (%s): ", listener->name);
 | |
| 
 | |
|             listener->listener->authfunc.diagnostic(dcb, listener);
 | |
| 
 | |
|             dcb_printf(dcb, "\n");
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool service_port_is_used(unsigned short port)
 | |
| {
 | |
|     bool rval = false;
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service && !rval; service = service->next)
 | |
|     {
 | |
|         LISTENER_ITERATOR iter;
 | |
| 
 | |
|         for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|              listener; listener = listener_iterator_next(&iter))
 | |
|         {
 | |
|             if (listener_is_active(listener) && listener->port == port)
 | |
|             {
 | |
|                 rval = true;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static const char* service_state_to_string(int state)
 | |
| {
 | |
|     switch (state)
 | |
|     {
 | |
|     case SERVICE_STATE_STARTED:
 | |
|         return "Started";
 | |
| 
 | |
|     case SERVICE_STATE_STOPPED:
 | |
|         return "Stopped";
 | |
| 
 | |
|     case SERVICE_STATE_FAILED:
 | |
|         return "Failed";
 | |
| 
 | |
|     case SERVICE_STATE_ALLOC:
 | |
|         return "Allocated";
 | |
| 
 | |
|     default:
 | |
|         ss_dassert(false);
 | |
|         return "Unknown";
 | |
|     }
 | |
| }
 | |
| 
 | |
| json_t* service_parameters_to_json(const SERVICE* service)
 | |
| {
 | |
|     json_t* rval = json_object();
 | |
| 
 | |
|     string options;
 | |
| 
 | |
|     if (service->routerOptions && service->routerOptions[0])
 | |
|     {
 | |
|         options += service->routerOptions[0];
 | |
| 
 | |
|         for (int i = 1; service->routerOptions[i]; i++)
 | |
|         {
 | |
|             options += ",";
 | |
|             options += service->routerOptions[i];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     json_object_set_new(rval, CN_ROUTER_OPTIONS, json_string(options.c_str()));
 | |
|     json_object_set_new(rval, CN_USER, json_string(service->credentials.name));
 | |
|     json_object_set_new(rval, CN_PASSWORD, json_string(service->credentials.authdata));
 | |
| 
 | |
|     json_object_set_new(rval, CN_ENABLE_ROOT_USER, json_boolean(service->enable_root));
 | |
|     json_object_set_new(rval, CN_MAX_RETRY_INTERVAL, json_integer(service->max_retry_interval));
 | |
|     json_object_set_new(rval, CN_MAX_CONNECTIONS, json_integer(service->max_connections));
 | |
|     json_object_set_new(rval, CN_CONNECTION_TIMEOUT, json_integer(service->conn_idle_timeout));
 | |
| 
 | |
|     json_object_set_new(rval, CN_AUTH_ALL_SERVERS, json_boolean(service->users_from_all));
 | |
|     json_object_set_new(rval, CN_STRIP_DB_ESC, json_boolean(service->strip_db_esc));
 | |
|     json_object_set_new(rval, CN_LOCALHOST_MATCH_WILDCARD_HOST,
 | |
|                         json_boolean(service->localhost_match_wildcard_host));
 | |
|     json_object_set_new(rval, CN_VERSION_STRING, json_string(service->version_string));
 | |
| 
 | |
|     if (*service->weightby)
 | |
|     {
 | |
|         json_object_set_new(rval, CN_WEIGHTBY, json_string(service->weightby));
 | |
|     }
 | |
| 
 | |
|     json_object_set_new(rval, CN_LOG_AUTH_WARNINGS, json_boolean(service->log_auth_warnings));
 | |
|     json_object_set_new(rval, CN_RETRY_ON_FAILURE, json_boolean(service->retry_start));
 | |
| 
 | |
|     /** Add custom module parameters */
 | |
|     const MXS_MODULE* mod = get_module(service->routerModule, MODULE_ROUTER);
 | |
|     config_add_module_params_json(mod, service->svc_config_param, config_service_params, rval);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static inline bool have_active_servers(const SERVICE* service)
 | |
| {
 | |
|     for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
 | |
|     {
 | |
|         if (SERVER_REF_IS_ACTIVE(ref))
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static json_t* service_all_listeners_json_data(const SERVICE* service)
 | |
| {
 | |
|     json_t* arr = json_array();
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener))
 | |
|         {
 | |
|             json_array_append_new(arr, listener_to_json(listener));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return arr;
 | |
| }
 | |
| 
 | |
| static json_t* service_listener_json_data(const SERVICE* service, const char* name)
 | |
| {
 | |
|     LISTENER_ITERATOR iter;
 | |
| 
 | |
|     for (SERV_LISTENER *listener = listener_iterator_init(service, &iter);
 | |
|          listener; listener = listener_iterator_next(&iter))
 | |
|     {
 | |
|         if (listener_is_active(listener) && strcmp(listener->name, name) == 0)
 | |
|         {
 | |
|             return listener_to_json(listener);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| json_t* service_attributes(const SERVICE* service)
 | |
| {
 | |
|     json_t* attr = json_object();
 | |
| 
 | |
|     json_object_set_new(attr, CN_ROUTER, json_string(service->routerModule));
 | |
|     json_object_set_new(attr, CN_STATE, json_string(service_state_to_string(service->state)));
 | |
| 
 | |
|     if (service->router && service->router_instance && service->router->diagnostics_json)
 | |
|     {
 | |
|         json_t* diag = service->router->diagnostics_json(service->router_instance);
 | |
| 
 | |
|         if (diag)
 | |
|         {
 | |
|             json_object_set_new(attr, CN_ROUTER_DIAGNOSTICS, diag);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     struct tm result;
 | |
|     char timebuf[30];
 | |
| 
 | |
|     asctime_r(localtime_r(&service->stats.started, &result), timebuf);
 | |
|     trim(timebuf);
 | |
| 
 | |
|     json_object_set_new(attr, "started", json_string(timebuf));
 | |
|     json_object_set_new(attr, "total_connections", json_integer(service->stats.n_sessions));
 | |
|     json_object_set_new(attr, "connections", json_integer(service->stats.n_current));
 | |
| 
 | |
|     /** Add service parameters and listeners */
 | |
|     json_object_set_new(attr, CN_PARAMETERS, service_parameters_to_json(service));
 | |
|     json_object_set_new(attr, CN_LISTENERS, service_all_listeners_json_data(service));
 | |
| 
 | |
|     return attr;
 | |
| }
 | |
| 
 | |
| json_t* service_relationships(const SERVICE* service, const char* host)
 | |
| {
 | |
|     /** Store relationships to other objects */
 | |
|     json_t* rel = json_object();
 | |
| 
 | |
|     if (service->n_filters)
 | |
|     {
 | |
|         json_t* filters = mxs_json_relationship(host, MXS_JSON_API_FILTERS);
 | |
| 
 | |
|         for (int i = 0; i < service->n_filters; i++)
 | |
|         {
 | |
|             mxs_json_add_relation(filters, service->filters[i]->name, CN_FILTERS);
 | |
|         }
 | |
| 
 | |
|         json_object_set_new(rel, CN_FILTERS, filters);
 | |
|     }
 | |
| 
 | |
|     if (have_active_servers(service))
 | |
|     {
 | |
|         json_t* servers = mxs_json_relationship(host, MXS_JSON_API_SERVERS);
 | |
| 
 | |
|         for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
 | |
|         {
 | |
|             if (SERVER_REF_IS_ACTIVE(ref))
 | |
|             {
 | |
|                 mxs_json_add_relation(servers, ref->server->unique_name, CN_SERVERS);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         json_object_set_new(rel, CN_SERVERS, servers);
 | |
|     }
 | |
| 
 | |
|     return rel;
 | |
| }
 | |
| 
 | |
| json_t* service_json_data(const SERVICE* service, const char* host)
 | |
| {
 | |
|     json_t* rval = json_object();
 | |
| 
 | |
|     spinlock_acquire(&service->spin);
 | |
| 
 | |
|     json_object_set_new(rval, CN_ID, json_string(service->name));
 | |
|     json_object_set_new(rval, CN_TYPE, json_string(CN_SERVICES));
 | |
|     json_object_set_new(rval, CN_ATTRIBUTES, service_attributes(service));
 | |
|     json_object_set_new(rval, CN_RELATIONSHIPS, service_relationships(service, host));
 | |
|     json_object_set_new(rval, CN_LINKS, mxs_json_self_link(host, CN_SERVICES, service->name));
 | |
| 
 | |
|     spinlock_release(&service->spin);
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| json_t* service_to_json(const SERVICE* service, const char* host)
 | |
| {
 | |
|     string self = MXS_JSON_API_SERVICES;
 | |
|     self += service->name;
 | |
|     return mxs_json_resource(host, self.c_str(), service_json_data(service, host));
 | |
| }
 | |
| 
 | |
| json_t* service_listener_list_to_json(const SERVICE* service, const char* host)
 | |
| {
 | |
|     /** This needs to be done here as the listeners are sort of sub-resources
 | |
|      * of the service. */
 | |
|     string self = MXS_JSON_API_SERVICES;
 | |
|     self += service->name;
 | |
|     self += "/listeners";
 | |
| 
 | |
|     return mxs_json_resource(host, self.c_str(), service_all_listeners_json_data(service));
 | |
| }
 | |
| 
 | |
| json_t* service_listener_to_json(const SERVICE* service, const char* name, const char* host)
 | |
| {
 | |
|     /** This needs to be done here as the listeners are sort of sub-resources
 | |
|      * of the service. */
 | |
|     string self = MXS_JSON_API_SERVICES;
 | |
|     self += service->name;
 | |
|     self += "/listeners/";
 | |
|     self += name;
 | |
| 
 | |
|     return mxs_json_resource(host, self.c_str(), service_listener_json_data(service, name));
 | |
| }
 | |
| 
 | |
| json_t* service_list_to_json(const char* host)
 | |
| {
 | |
|     json_t* arr = json_array();
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service; service = service->next)
 | |
|     {
 | |
|         json_t* svc = service_json_data(service, host);
 | |
| 
 | |
|         if (svc)
 | |
|         {
 | |
|             json_array_append_new(arr, svc);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return mxs_json_resource(host, MXS_JSON_API_SERVICES, arr);
 | |
| }
 | |
| 
 | |
| json_t* service_relations_to_filter(const MXS_FILTER_DEF* filter, const char* host)
 | |
| {
 | |
|     json_t* rel = mxs_json_relationship(host, MXS_JSON_API_SERVICES);
 | |
| 
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service; service = service->next)
 | |
|     {
 | |
|         spinlock_acquire(&service->spin);
 | |
| 
 | |
|         for (int i = 0; i < service->n_filters; i++)
 | |
|         {
 | |
|             if (service->filters[i] == filter)
 | |
|             {
 | |
|                 mxs_json_add_relation(rel, service->name, CN_SERVICES);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         spinlock_release(&service->spin);
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return rel;
 | |
| }
 | |
| 
 | |
| 
 | |
| json_t* service_relations_to_server(const SERVER* server, const char* host)
 | |
| {
 | |
|     std::vector<std::string> names;
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE *service = allServices; service; service = service->next)
 | |
|     {
 | |
|         spinlock_acquire(&service->spin);
 | |
| 
 | |
|         for (SERVER_REF *ref = service->dbref; ref; ref = ref->next)
 | |
|         {
 | |
|             if (ref->server == server && SERVER_REF_IS_ACTIVE(ref))
 | |
|             {
 | |
|                 names.push_back(service->name);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         spinlock_release(&service->spin);
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     json_t* rel = NULL;
 | |
| 
 | |
|     if (!names.empty())
 | |
|     {
 | |
|         rel = mxs_json_relationship(host, MXS_JSON_API_SERVICES);
 | |
| 
 | |
|         for (std::vector<std::string>::iterator it = names.begin();
 | |
|              it != names.end(); it++)
 | |
|         {
 | |
|             mxs_json_add_relation(rel, it->c_str(), CN_SERVICES);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rel;
 | |
| }
 | |
| 
 | |
| uint64_t service_get_version(const SERVICE *service, service_version_which_t which)
 | |
| {
 | |
|     uint64_t version = 0;
 | |
| 
 | |
|     if (which == SERVICE_VERSION_ANY)
 | |
|     {
 | |
|         SERVER_REF* sref = service->dbref;
 | |
| 
 | |
|         while (sref && !sref->active)
 | |
|         {
 | |
|             sref = sref->next;
 | |
|         }
 | |
| 
 | |
|         if (sref)
 | |
|         {
 | |
|             version = server_get_version(sref->server);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         size_t n = 0;
 | |
| 
 | |
|         uint64_t v;
 | |
| 
 | |
|         if (which == SERVICE_VERSION_MIN)
 | |
|         {
 | |
|             v = UINT64_MAX;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             ss_dassert(which == SERVICE_VERSION_MAX);
 | |
| 
 | |
|             v = 0;
 | |
|         }
 | |
| 
 | |
|         SERVER_REF* sref = service->dbref;
 | |
| 
 | |
|         while (sref)
 | |
|         {
 | |
|             if (sref->active)
 | |
|             {
 | |
|                 ++n;
 | |
| 
 | |
|                 SERVER* s = sref->server;
 | |
|                 uint64_t server_version = server_get_version(s);
 | |
| 
 | |
|                 if (which == SERVICE_VERSION_MIN)
 | |
|                 {
 | |
|                     if (server_version < v)
 | |
|                     {
 | |
|                         v = server_version;
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     ss_dassert(which == SERVICE_VERSION_MAX);
 | |
| 
 | |
|                     if (server_version > v)
 | |
|                     {
 | |
|                         v = server_version;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             sref = sref->next;
 | |
|         }
 | |
| 
 | |
|         if (n == 0)
 | |
|         {
 | |
|             v = 0;
 | |
|         }
 | |
| 
 | |
|         version = v;
 | |
|     }
 | |
| 
 | |
|     return version;
 | |
| }
 | |
| 
 | |
| bool service_thread_init()
 | |
| {
 | |
|     spinlock_acquire(&service_spin);
 | |
| 
 | |
|     for (SERVICE* service = allServices; service; service = service->next)
 | |
|     {
 | |
|         if (service->capabilities & ACAP_TYPE_ASYNC)
 | |
|         {
 | |
|             service_refresh_users(service);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     spinlock_release(&service_spin);
 | |
| 
 | |
|     return true;
 | |
| }
 |