The enums exposed by the connector are not intended to be used by the users of the library. The fact that the protocol, and other, modules used it was in violation of how the library is intended to be used. Adding an internal mapping into MaxScale also removes some of the dependencies that the core has on the connector.
		
			
				
	
	
		
			727 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			727 lines
		
	
	
		
			18 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 maxinfo.c - A "routing module" that in fact merely gives access
 | 
						|
 * to a MaxScale information schema usign the MySQL protocol
 | 
						|
 *
 | 
						|
 * @verbatim
 | 
						|
 * Revision History
 | 
						|
 *
 | 
						|
 * Date       Who                 Description
 | 
						|
 * 16/02/15   Mark Riddoch        Initial implementation
 | 
						|
 * 27/02/15   Massimiliano Pinto  Added maxinfo_add_mysql_user
 | 
						|
 * 09/09/2015 Martin Brampton     Modify error handler
 | 
						|
 *
 | 
						|
 * @endverbatim
 | 
						|
 */
 | 
						|
 | 
						|
#include "maxinfo.h"
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <time.h>
 | 
						|
 | 
						|
#include <maxscale/alloc.h>
 | 
						|
#include <maxscale/service.h>
 | 
						|
#include <maxscale/server.h>
 | 
						|
#include <maxscale/router.h>
 | 
						|
#include <maxscale/modinfo.h>
 | 
						|
#include <maxscale/modutil.h>
 | 
						|
#include <maxscale/monitor.h>
 | 
						|
#include <maxscale/atomic.h>
 | 
						|
#include <maxscale/spinlock.h>
 | 
						|
#include <maxscale/dcb.h>
 | 
						|
#include <maxscale/maxscale.h>
 | 
						|
#include <maxscale/log_manager.h>
 | 
						|
#include <maxscale/resultset.h>
 | 
						|
#include <maxscale/version.h>
 | 
						|
#include <maxscale/resultset.h>
 | 
						|
#include <maxscale/secrets.h>
 | 
						|
#include <maxscale/users.h>
 | 
						|
#include <maxscale/protocol/mysql.h>
 | 
						|
 | 
						|
#include "../../../core/maxscale/modules.h"
 | 
						|
#include "../../../core/maxscale/monitor.h"
 | 
						|
#include "../../../core/maxscale/session.h"
 | 
						|
#include "../../../core/maxscale/poll.h"
 | 
						|
 | 
						|
extern char *create_hex_sha1_sha1_passwd(char *passwd);
 | 
						|
 | 
						|
static int maxinfo_statistics(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
 | 
						|
static int maxinfo_ping(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
 | 
						|
static int maxinfo_execute_query(INFO_INSTANCE *, INFO_SESSION *, char *);
 | 
						|
static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *router_session, GWBUF *queue);
 | 
						|
 | 
						|
 | 
						|
/* The router entry points */
 | 
						|
static  MXS_ROUTER *createInstance(SERVICE *service, char **options);
 | 
						|
static  MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
 | 
						|
static  void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
 | 
						|
static  void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
 | 
						|
static  int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
 | 
						|
static  void diagnostics(MXS_ROUTER *instance, DCB *dcb);
 | 
						|
static  json_t* diagnostics_json(const MXS_ROUTER *instance);
 | 
						|
static  uint64_t getCapabilities(MXS_ROUTER* instance);
 | 
						|
static  void handleError(MXS_ROUTER     *instance,
 | 
						|
                         MXS_ROUTER_SESSION  *router_session,
 | 
						|
                         GWBUF          *errbuf,
 | 
						|
                         DCB            *backend_dcb,
 | 
						|
                         mxs_error_action_t action,
 | 
						|
                         bool           *succp);
 | 
						|
 | 
						|
static SPINLOCK     instlock;
 | 
						|
static INFO_INSTANCE    *instances;
 | 
						|
 | 
						|
/**
 | 
						|
 * The module entry point routine. It is this routine that
 | 
						|
 * must populate the structure that is referred to as the
 | 
						|
 * "module object", this is a structure with the set of
 | 
						|
 * external entry points for this module.
 | 
						|
 *
 | 
						|
 * @return The module object
 | 
						|
 */
 | 
						|
MXS_MODULE* MXS_CREATE_MODULE()
 | 
						|
{
 | 
						|
    MXS_NOTICE("Initialise MaxInfo router module.");
 | 
						|
    spinlock_init(&instlock);
 | 
						|
    instances = NULL;
 | 
						|
 | 
						|
    static MXS_ROUTER_OBJECT MyObject =
 | 
						|
    {
 | 
						|
        createInstance,
 | 
						|
        newSession,
 | 
						|
        closeSession,
 | 
						|
        freeSession,
 | 
						|
        execute,
 | 
						|
        diagnostics,
 | 
						|
        diagnostics_json,
 | 
						|
        NULL,
 | 
						|
        handleError,
 | 
						|
        getCapabilities,
 | 
						|
        NULL
 | 
						|
    };
 | 
						|
 | 
						|
    static MXS_MODULE info =
 | 
						|
    {
 | 
						|
        MXS_MODULE_API_ROUTER,
 | 
						|
        MXS_MODULE_ALPHA_RELEASE,
 | 
						|
        MXS_ROUTER_VERSION,
 | 
						|
        "The MaxScale Information Schema",
 | 
						|
        "V1.0.0",
 | 
						|
        RCAP_TYPE_NO_AUTH,
 | 
						|
        &MyObject,
 | 
						|
        NULL, /* Process init. */
 | 
						|
        NULL, /* Process finish. */
 | 
						|
        NULL, /* Thread init. */
 | 
						|
        NULL, /* Thread finish. */
 | 
						|
        {
 | 
						|
            {MXS_END_MODULE_PARAMS}
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    return &info;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an instance of the router for a particular service
 | 
						|
 * within the gateway.
 | 
						|
 *
 | 
						|
 * @param service   The service this router is being create for
 | 
						|
 * @param options   Any array of options for the query router
 | 
						|
 *
 | 
						|
 * @return The instance data for this new instance
 | 
						|
 */
 | 
						|
static  MXS_ROUTER  *
 | 
						|
createInstance(SERVICE *service, char **options)
 | 
						|
{
 | 
						|
    INFO_INSTANCE *inst;
 | 
						|
    int i;
 | 
						|
 | 
						|
    if ((inst = MXS_MALLOC(sizeof(INFO_INSTANCE))) == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    inst->service = service;
 | 
						|
    spinlock_init(&inst->lock);
 | 
						|
 | 
						|
    if (options)
 | 
						|
    {
 | 
						|
        for (i = 0; options[i]; i++)
 | 
						|
        {
 | 
						|
            MXS_ERROR("Unknown option for MaxInfo '%s'", options[i]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * We have completed the creation of the instance data, so now
 | 
						|
     * insert this router instance into the linked list of routers
 | 
						|
     * that have been created with this module.
 | 
						|
     */
 | 
						|
    spinlock_acquire(&instlock);
 | 
						|
    inst->next = instances;
 | 
						|
    instances = inst;
 | 
						|
    spinlock_release(&instlock);
 | 
						|
 | 
						|
    return (MXS_ROUTER *)inst;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Associate a new session with this instance of the router.
 | 
						|
 *
 | 
						|
 * @param instance  The router instance data
 | 
						|
 * @param session   The session itself
 | 
						|
 * @return Session specific data for this session
 | 
						|
 */
 | 
						|
static MXS_ROUTER_SESSION *
 | 
						|
newSession(MXS_ROUTER *instance, MXS_SESSION *session)
 | 
						|
{
 | 
						|
    INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
 | 
						|
    INFO_SESSION *client;
 | 
						|
 | 
						|
    if ((client = (INFO_SESSION *)MXS_MALLOC(sizeof(INFO_SESSION))) == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    client->session = session;
 | 
						|
    client->dcb = session->client_dcb;
 | 
						|
    client->queue = NULL;
 | 
						|
 | 
						|
    spinlock_acquire(&inst->lock);
 | 
						|
    client->next = inst->sessions;
 | 
						|
    inst->sessions = client;
 | 
						|
    spinlock_release(&inst->lock);
 | 
						|
 | 
						|
    session->state = SESSION_STATE_READY;
 | 
						|
 | 
						|
    return (void *)client;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Close a session with the router, this is the mechanism
 | 
						|
 * by which a router may cleanup data structure etc.
 | 
						|
 *
 | 
						|
 * @param instance      The router instance data
 | 
						|
 * @param router_session    The session being closed
 | 
						|
 */
 | 
						|
static  void
 | 
						|
closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
 | 
						|
{
 | 
						|
    INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
 | 
						|
    INFO_SESSION *session = (INFO_SESSION *)router_session;
 | 
						|
 | 
						|
 | 
						|
    spinlock_acquire(&inst->lock);
 | 
						|
    if (inst->sessions == session)
 | 
						|
    {
 | 
						|
        inst->sessions = session->next;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        INFO_SESSION *ptr = inst->sessions;
 | 
						|
        while (ptr && ptr->next != session)
 | 
						|
        {
 | 
						|
            ptr = ptr->next;
 | 
						|
        }
 | 
						|
        if (ptr)
 | 
						|
        {
 | 
						|
            ptr->next = session->next;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    spinlock_release(&inst->lock);
 | 
						|
    /**
 | 
						|
     * Router session is freed in session.c:session_close, when session who
 | 
						|
     * owns it, is freed.
 | 
						|
     */
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Free a maxinfo session
 | 
						|
 *
 | 
						|
 * @param router_instance   The router session
 | 
						|
 * @param router_client_session The router session as returned from newSession
 | 
						|
 */
 | 
						|
static void freeSession(MXS_ROUTER* router_instance,
 | 
						|
                        MXS_ROUTER_SESSION* router_client_session)
 | 
						|
{
 | 
						|
    MXS_FREE(router_client_session);
 | 
						|
    return;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Error Handler routine
 | 
						|
 *
 | 
						|
 * The routine will handle errors that occurred in backend writes.
 | 
						|
 *
 | 
						|
 * @param instance        The router instance
 | 
						|
 * @param router_session  The router session
 | 
						|
 * @param message         The error message to reply
 | 
						|
 * @param backend_dcb     The backend DCB
 | 
						|
 * @param action          The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT
 | 
						|
 * @param succp           Result of action: true iff router can continue
 | 
						|
 *
 | 
						|
 */
 | 
						|
static void handleError(MXS_ROUTER         *instance,
 | 
						|
                        MXS_ROUTER_SESSION *router_session,
 | 
						|
                        GWBUF              *errbuf,
 | 
						|
                        DCB                *backend_dcb,
 | 
						|
                        mxs_error_action_t action,
 | 
						|
                        bool               *succp)
 | 
						|
 | 
						|
{
 | 
						|
    ss_dassert(backend_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER);
 | 
						|
    DCB *client_dcb;
 | 
						|
    MXS_SESSION *session = backend_dcb->session;
 | 
						|
 | 
						|
    client_dcb = session->client_dcb;
 | 
						|
 | 
						|
    if (session->state == SESSION_STATE_ROUTER_READY)
 | 
						|
    {
 | 
						|
        CHK_DCB(client_dcb);
 | 
						|
        client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
 | 
						|
    }
 | 
						|
 | 
						|
    /** false because connection is not available anymore */
 | 
						|
    dcb_close(backend_dcb);
 | 
						|
    *succp = false;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * We have data from the client, this is a SQL command, or other MySQL
 | 
						|
 * packet type.
 | 
						|
 *
 | 
						|
 * @param instance       The router instance
 | 
						|
 * @param router_session The router session returned from the newSession call
 | 
						|
 * @param queue          The queue of data buffers to route
 | 
						|
 * @return The number of bytes sent
 | 
						|
 */
 | 
						|
static int
 | 
						|
execute(MXS_ROUTER *rinstance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
 | 
						|
{
 | 
						|
    INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
 | 
						|
    INFO_SESSION *session = (INFO_SESSION *)router_session;
 | 
						|
    uint8_t *data;
 | 
						|
    int length, len, residual;
 | 
						|
    char *sql;
 | 
						|
 | 
						|
    if (GWBUF_TYPE(queue) == GWBUF_TYPE_HTTP)
 | 
						|
    {
 | 
						|
        return handle_url(instance, session, queue);
 | 
						|
    }
 | 
						|
    if (session->queue)
 | 
						|
    {
 | 
						|
        queue = gwbuf_append(session->queue, queue);
 | 
						|
        session->queue = NULL;
 | 
						|
        queue = gwbuf_make_contiguous(queue);
 | 
						|
    }
 | 
						|
    data = (uint8_t *)GWBUF_DATA(queue);
 | 
						|
    length = data[0] + (data[1] << 8) + (data[2] << 16);
 | 
						|
    if (length + 4 > GWBUF_LENGTH(queue))
 | 
						|
    {
 | 
						|
        // Incomplete packet, must be buffered
 | 
						|
        session->queue = queue;
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    int rc = 1;
 | 
						|
    // We have a complete request in a single buffer
 | 
						|
    if (modutil_MySQL_Query(queue, &sql, &len, &residual))
 | 
						|
    {
 | 
						|
        sql = strndup(sql, len);
 | 
						|
        rc = maxinfo_execute_query(instance, session, sql);
 | 
						|
        MXS_FREE(sql);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        switch (MYSQL_COMMAND(queue))
 | 
						|
        {
 | 
						|
        case MXS_COM_PING:
 | 
						|
            rc = maxinfo_ping(instance, session, queue);
 | 
						|
            break;
 | 
						|
        case MXS_COM_STATISTICS:
 | 
						|
            rc = maxinfo_statistics(instance, session, queue);
 | 
						|
            break;
 | 
						|
        case MXS_COM_QUIT:
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            MXS_ERROR("Unexpected MySQL command 0x%x",
 | 
						|
                      MYSQL_COMMAND(queue));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    // MaxInfo doesn't route the data forward so it should be freed.
 | 
						|
    gwbuf_free(queue);
 | 
						|
    return rc;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Display router diagnostics
 | 
						|
 *
 | 
						|
 * @param instance  Instance of the router
 | 
						|
 * @param dcb       DCB to send diagnostics to
 | 
						|
 */
 | 
						|
static  void
 | 
						|
diagnostics(MXS_ROUTER *instance, DCB *dcb)
 | 
						|
{
 | 
						|
    return; /* Nothing to do currently */
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Display router diagnostics
 | 
						|
 *
 | 
						|
 * @param instance  Instance of the router
 | 
						|
 * @param dcb       DCB to send diagnostics to
 | 
						|
 */
 | 
						|
static  json_t*
 | 
						|
diagnostics_json(const MXS_ROUTER *instance)
 | 
						|
{
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Capabilities interface for the rotuer
 | 
						|
 *
 | 
						|
 * Not used for the maxinfo router
 | 
						|
 */
 | 
						|
static uint64_t
 | 
						|
getCapabilities(MXS_ROUTER* instance)
 | 
						|
{
 | 
						|
    return RCAP_TYPE_NONE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Return some basic statistics from the router in response to a COM_STATISTICS
 | 
						|
 * request.
 | 
						|
 *
 | 
						|
 * @param router    The router instance
 | 
						|
 * @param session   The connection that requested the statistics
 | 
						|
 * @param queue     The statistics request
 | 
						|
 *
 | 
						|
 * @return non-zero on sucessful send
 | 
						|
 */
 | 
						|
static int
 | 
						|
maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
 | 
						|
{
 | 
						|
    char result[1000];
 | 
						|
    uint8_t *ptr;
 | 
						|
    GWBUF *ret;
 | 
						|
    int len;
 | 
						|
 | 
						|
    snprintf(result, 1000,
 | 
						|
             "Uptime: %u  Threads: %u  Sessions: %u ",
 | 
						|
             maxscale_uptime(),
 | 
						|
             config_threadcount(),
 | 
						|
             serviceSessionCountAll());
 | 
						|
    if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL)
 | 
						|
    {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    len = strlen(result);
 | 
						|
    ptr = GWBUF_DATA(ret);
 | 
						|
    *ptr++ = len & 0xff;
 | 
						|
    *ptr++ = (len & 0xff00) >> 8;
 | 
						|
    *ptr++ = (len & 0xff0000) >> 16;
 | 
						|
    *ptr++ = 1;
 | 
						|
    memcpy(ptr, result, len);
 | 
						|
 | 
						|
    return session->dcb->func.write(session->dcb, ret);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Respond to a COM_PING command
 | 
						|
 *
 | 
						|
 * @param router    The router instance
 | 
						|
 * @param session   The connection that requested the ping
 | 
						|
 * @param queue     The ping request
 | 
						|
 */
 | 
						|
static int
 | 
						|
maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
 | 
						|
{
 | 
						|
    uint8_t *ptr;
 | 
						|
    GWBUF *ret;
 | 
						|
    int len;
 | 
						|
 | 
						|
    if ((ret = gwbuf_alloc(5)) == NULL)
 | 
						|
    {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    ptr = GWBUF_DATA(ret);
 | 
						|
    *ptr++ = 0x01;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 1;
 | 
						|
    *ptr = 0;       // OK
 | 
						|
 | 
						|
    return session->dcb->func.write(session->dcb, ret);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Populate the version comment with the MaxScale version
 | 
						|
 *
 | 
						|
 * @param   result  The result set
 | 
						|
 * @param   data    Pointer to int which is row count
 | 
						|
 * @return  The populated row
 | 
						|
 */
 | 
						|
static RESULT_ROW *
 | 
						|
version_comment(RESULTSET *result, void *data)
 | 
						|
{
 | 
						|
    int *context = (int *)data;
 | 
						|
    RESULT_ROW *row;
 | 
						|
 | 
						|
    if (*context == 0)
 | 
						|
    {
 | 
						|
        (*context)++;
 | 
						|
        row = resultset_make_row(result);
 | 
						|
        resultset_row_set(row, 0, MAXSCALE_VERSION);
 | 
						|
        return row;
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The hardwired select @@vercom response
 | 
						|
 *
 | 
						|
 * @param dcb   The DCB of the client
 | 
						|
 */
 | 
						|
static void
 | 
						|
respond_vercom(DCB *dcb)
 | 
						|
{
 | 
						|
    RESULTSET *result;
 | 
						|
    int context = 0;
 | 
						|
 | 
						|
    if ((result = resultset_create(version_comment, &context)) == NULL)
 | 
						|
    {
 | 
						|
        maxinfo_send_error(dcb, 0, "No resources available");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    resultset_add_column(result, "@@version_comment", 40, COL_TYPE_VARCHAR);
 | 
						|
    resultset_stream_mysql(result, dcb);
 | 
						|
    resultset_free(result);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Populate the version comment with the MaxScale version
 | 
						|
 *
 | 
						|
 * @param   result  The result set
 | 
						|
 * @param   data    Pointer to int which is row count
 | 
						|
 * @return  The populated row
 | 
						|
 */
 | 
						|
static RESULT_ROW *
 | 
						|
starttime_row(RESULTSET *result, void *data)
 | 
						|
{
 | 
						|
    int *context = (int *)data;
 | 
						|
    RESULT_ROW *row;
 | 
						|
    struct tm tm;
 | 
						|
    static char buf[40];
 | 
						|
 | 
						|
    if (*context == 0)
 | 
						|
    {
 | 
						|
        (*context)++;
 | 
						|
        row = resultset_make_row(result);
 | 
						|
        sprintf(buf, "%u", (unsigned int)maxscale_started());
 | 
						|
        resultset_row_set(row, 0, buf);
 | 
						|
        return row;
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The hardwired select ... as starttime response
 | 
						|
 *
 | 
						|
 * @param dcb   The DCB of the client
 | 
						|
 */
 | 
						|
static void
 | 
						|
respond_starttime(DCB *dcb)
 | 
						|
{
 | 
						|
    RESULTSET *result;
 | 
						|
    int context = 0;
 | 
						|
 | 
						|
    if ((result = resultset_create(starttime_row, &context)) == NULL)
 | 
						|
    {
 | 
						|
        maxinfo_send_error(dcb, 0, "No resources available");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    resultset_add_column(result, "starttime", 40, COL_TYPE_VARCHAR);
 | 
						|
    resultset_stream_mysql(result, dcb);
 | 
						|
    resultset_free(result);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Send a MySQL OK packet to the DCB
 | 
						|
 *
 | 
						|
 * @param dcb   The DCB to send the OK packet to
 | 
						|
 * @return result of a write call, non-zero if write was successful
 | 
						|
 */
 | 
						|
static int
 | 
						|
maxinfo_send_ok(DCB *dcb)
 | 
						|
{
 | 
						|
    GWBUF *buf;
 | 
						|
    uint8_t *ptr;
 | 
						|
 | 
						|
    if ((buf = gwbuf_alloc(11)) == NULL)
 | 
						|
    {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    ptr = GWBUF_DATA(buf);
 | 
						|
    *ptr++ = 7; // Payload length
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 1; // Seqno
 | 
						|
    *ptr++ = 0; // ok
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 2;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 0;
 | 
						|
    *ptr++ = 0;
 | 
						|
    return dcb->func.write(dcb, buf);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Execute a SQL query against the MaxScale Information Schema
 | 
						|
 *
 | 
						|
 * @param instance  The instance strcture
 | 
						|
 * @param session   The session pointer
 | 
						|
 * @param sql       The SQL to execute
 | 
						|
 */
 | 
						|
static int
 | 
						|
maxinfo_execute_query(INFO_INSTANCE *instance, INFO_SESSION *session, char *sql)
 | 
						|
{
 | 
						|
    MAXINFO_TREE    *tree;
 | 
						|
    PARSE_ERROR err;
 | 
						|
 | 
						|
    MXS_INFO("SQL statement: '%s' for 0x%p.",
 | 
						|
             sql, session->dcb);
 | 
						|
    if (strcmp(sql, "select @@version_comment limit 1") == 0)
 | 
						|
    {
 | 
						|
        respond_vercom(session->dcb);
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    /* Below is a kludge for MonYog, if we see
 | 
						|
     *  select unix_timestamp... as starttime
 | 
						|
     * just return the starttime of MaxScale
 | 
						|
     */
 | 
						|
    if (strncasecmp(sql, "select UNIX_TIMESTAMP",
 | 
						|
                    strlen("select UNIX_TIMESTAMP")) == 0
 | 
						|
        && (strstr(sql, "as starttime") != NULL || strstr(sql, "AS starttime") != NULL))
 | 
						|
    {
 | 
						|
        respond_starttime(session->dcb);
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    if (strcasecmp(sql, "set names 'utf8'") == 0)
 | 
						|
    {
 | 
						|
        return maxinfo_send_ok(session->dcb);
 | 
						|
    }
 | 
						|
    if (strncasecmp(sql, "set session", 11) == 0)
 | 
						|
    {
 | 
						|
        return maxinfo_send_ok(session->dcb);
 | 
						|
    }
 | 
						|
    if (strncasecmp(sql, "set autocommit", 14) == 0)
 | 
						|
    {
 | 
						|
        return maxinfo_send_ok(session->dcb);
 | 
						|
    }
 | 
						|
    if (strncasecmp(sql, "SELECT `ENGINES`.`SUPPORT`", 26) == 0)
 | 
						|
    {
 | 
						|
        return maxinfo_send_ok(session->dcb);
 | 
						|
    }
 | 
						|
    if ((tree = maxinfo_parse(sql, &err)) == NULL)
 | 
						|
    {
 | 
						|
        maxinfo_send_parse_error(session->dcb, sql, err);
 | 
						|
        MXS_NOTICE("Failed to parse SQL statement: '%s'.", sql);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        maxinfo_execute(session->dcb, tree);
 | 
						|
        maxinfo_free_tree(tree);
 | 
						|
    }
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Session all result set
 | 
						|
 * @return A resultset for all sessions
 | 
						|
 */
 | 
						|
static RESULTSET *
 | 
						|
maxinfoSessionsAll()
 | 
						|
{
 | 
						|
    return sessionGetList(SESSION_LIST_ALL);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Client session result set
 | 
						|
 * @return A resultset for all sessions
 | 
						|
 */
 | 
						|
static RESULTSET *
 | 
						|
maxinfoClientSessions()
 | 
						|
{
 | 
						|
    return sessionGetList(SESSION_LIST_CONNECTION);
 | 
						|
}
 | 
						|
 | 
						|
typedef RESULTSET *(*RESULTSETFUNC)();
 | 
						|
 | 
						|
/**
 | 
						|
 * Table that maps a URI to a function to call to
 | 
						|
 * to obtain the result set related to that URI
 | 
						|
 */
 | 
						|
static struct uri_table
 | 
						|
{
 | 
						|
    char        *uri;
 | 
						|
    RESULTSETFUNC   func;
 | 
						|
} supported_uri[] =
 | 
						|
{
 | 
						|
    { "/services", serviceGetList },
 | 
						|
    { "/listeners", serviceGetListenerList },
 | 
						|
    { "/modules", moduleGetList },
 | 
						|
    { "/monitors", monitorGetList },
 | 
						|
    { "/sessions", maxinfoSessionsAll },
 | 
						|
    { "/clients", maxinfoClientSessions },
 | 
						|
    { "/servers", serverGetList },
 | 
						|
    { "/variables", maxinfo_variables },
 | 
						|
    { "/status", maxinfo_status },
 | 
						|
    { "/event/times", eventTimesGetList },
 | 
						|
    { NULL, NULL }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * We have data from the client, this is a HTTP URL
 | 
						|
 *
 | 
						|
 * @param instance  The router instance
 | 
						|
 * @param session   The router session returned from the newSession call
 | 
						|
 * @param queue     The queue of data buffers to route
 | 
						|
 * @return The number of bytes sent
 | 
						|
 */
 | 
						|
static  int
 | 
						|
handle_url(INFO_INSTANCE *instance, INFO_SESSION *session, GWBUF *queue)
 | 
						|
{
 | 
						|
    char *uri;
 | 
						|
    int i;
 | 
						|
    RESULTSET *set;
 | 
						|
 | 
						|
    uri = (char *)GWBUF_DATA(queue);
 | 
						|
    for (i = 0; supported_uri[i].uri; i++)
 | 
						|
    {
 | 
						|
        if (strcmp(uri, supported_uri[i].uri) == 0)
 | 
						|
        {
 | 
						|
            set = (*supported_uri[i].func)();
 | 
						|
            resultset_stream_json(set, session->dcb);
 | 
						|
            resultset_free(set);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    gwbuf_free(queue);
 | 
						|
    return 1;
 | 
						|
}
 |