465 lines
11 KiB
C
465 lines
11 KiB
C
/*
|
|
* This file is distributed as part of the SkySQL Gateway. It is free
|
|
* software: you can redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License as published by the Free Software Foundation,
|
|
* version 2.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright SkySQL Ab 2013
|
|
*/
|
|
|
|
/**
|
|
* @file service.c - A representation of the service within the gateway.
|
|
*
|
|
* @verbatim
|
|
* Revision History
|
|
*
|
|
* Date Who Description
|
|
* 18/06/13 Mark Riddoch Initial implementation
|
|
* 24/06/13 Massimiliano Pinto Added: Loading users from mysql backend in serviceStart
|
|
* @endverbatim
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <session.h>
|
|
#include <service.h>
|
|
#include <server.h>
|
|
#include <router.h>
|
|
#include <spinlock.h>
|
|
#include <modules.h>
|
|
#include <dcb.h>
|
|
#include <users.h>
|
|
#include <dbusers.h>
|
|
#include <poll.h>
|
|
|
|
static SPINLOCK service_spin = SPINLOCK_INIT;
|
|
static SERVICE *allServices = NULL;
|
|
|
|
/**
|
|
* Allocate a new service for the gateway to support
|
|
*
|
|
*
|
|
* @param servname The service name
|
|
* @param router Name of the router module this service uses
|
|
*
|
|
* @return The newly created service or NULL if an error occured
|
|
*/
|
|
SERVICE *
|
|
service_alloc(char *servname, char *router)
|
|
{
|
|
SERVICE *service;
|
|
|
|
if ((service = (SERVICE *)malloc(sizeof(SERVICE))) == NULL)
|
|
return NULL;
|
|
if ((service->router = load_module(router, MODULE_ROUTER)) == NULL)
|
|
{
|
|
free(service);
|
|
return NULL;
|
|
}
|
|
service->name = strdup(servname);
|
|
service->routerModule = strdup(router);
|
|
memset(&service->stats, 0, sizeof(SERVICE_STATS));
|
|
service->ports = NULL;
|
|
service->stats.started = time(0);
|
|
service->state = SERVICE_STATE_ALLOC;
|
|
service->credentials.name = NULL;
|
|
service->credentials.authdata = NULL;
|
|
service->users = users_alloc();
|
|
service->routerOptions = NULL;
|
|
service->databases = NULL;
|
|
spinlock_init(&service->spin);
|
|
|
|
spinlock_acquire(&service_spin);
|
|
service->next = allServices;
|
|
allServices = service;
|
|
spinlock_release(&service_spin);
|
|
|
|
return service;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
serviceStart(SERVICE *service)
|
|
{
|
|
SERV_PROTOCOL *port;
|
|
int listeners = 0;
|
|
char config_bind[40];
|
|
GWPROTOCOL *funcs;
|
|
|
|
service->router_instance = service->router->createInstance(service,
|
|
service->routerOptions);
|
|
|
|
port = service->ports;
|
|
while (port)
|
|
{
|
|
if ((port->listener = dcb_alloc()) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (strcmp(port->protocol, "MySQLClient") == 0) {
|
|
int loaded = -1;
|
|
|
|
loaded = load_mysql_users(service);
|
|
|
|
fprintf(stderr, "@@@@ MySQL Users loaded: %i\n", loaded);
|
|
}
|
|
|
|
if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL)
|
|
{
|
|
free(port->listener);
|
|
port->listener = NULL;
|
|
}
|
|
memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL));
|
|
port->listener->session = NULL;
|
|
sprintf(config_bind, "0.0.0.0:%d", port->port);
|
|
if (port->listener->func.listen(port->listener, config_bind))
|
|
listeners++;
|
|
port->listener->session = session_alloc(service, port->listener);
|
|
port->listener->session->state = SESSION_STATE_LISTENER;
|
|
|
|
port = port->next;
|
|
}
|
|
if (listeners)
|
|
service->stats.started = time(0);
|
|
|
|
return listeners;
|
|
}
|
|
|
|
/**
|
|
* Start all the services
|
|
*
|
|
* @return Return the number of services started
|
|
*/
|
|
int
|
|
serviceStartAll()
|
|
{
|
|
SERVICE *ptr;
|
|
int n = 0;
|
|
|
|
ptr = allServices;
|
|
while (ptr)
|
|
{
|
|
n += serviceStart(ptr);
|
|
ptr = ptr->next;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Stop a service
|
|
*
|
|
* This function stops the listener for the service
|
|
*
|
|
* @param service The Service that should be stopped
|
|
* @return Returns the number of listeners restarted
|
|
*/
|
|
int
|
|
serviceStop(SERVICE *service)
|
|
{
|
|
SERV_PROTOCOL *port;
|
|
int listeners = 0;
|
|
|
|
port = service->ports;
|
|
while (port)
|
|
{
|
|
poll_remove_dcb(port->listener);
|
|
port->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
|
|
listeners++;
|
|
|
|
port = port->next;
|
|
}
|
|
|
|
return listeners;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
int
|
|
serviceRestart(SERVICE *service)
|
|
{
|
|
SERV_PROTOCOL *port;
|
|
int listeners = 0;
|
|
|
|
port = service->ports;
|
|
while (port)
|
|
{
|
|
poll_add_dcb(port->listener);
|
|
port->listener->session->state = SESSION_STATE_LISTENER;
|
|
listeners++;
|
|
|
|
port = port->next;
|
|
}
|
|
|
|
return listeners;
|
|
}
|
|
|
|
|
|
/**
|
|
* Deallocate the specified service
|
|
*
|
|
* @param service The service to deallocate
|
|
* @return Returns true if the service was freed
|
|
*/
|
|
int
|
|
service_free(SERVICE *service)
|
|
{
|
|
SERVICE *ptr;
|
|
|
|
if (service->stats.n_current)
|
|
return 0;
|
|
/* 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 */
|
|
free(service->name);
|
|
free(service->routerModule);
|
|
if (service->credentials.name)
|
|
free(service->credentials.name);
|
|
if (service->credentials.authdata)
|
|
free(service->credentials.authdata);
|
|
free(service);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Add a protocol/port pair to the service
|
|
*
|
|
* @param service The service
|
|
* @param protocol The name of the protocol module
|
|
* @param port The port to listen on
|
|
* @return TRUE if the protocol/port could be added
|
|
*/
|
|
int
|
|
serviceAddProtocol(SERVICE *service, char *protocol, unsigned short port)
|
|
{
|
|
SERV_PROTOCOL *proto;
|
|
|
|
if ((proto = (SERV_PROTOCOL *)malloc(sizeof(SERV_PROTOCOL))) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
proto->protocol = strdup(protocol);
|
|
proto->port = port;
|
|
spinlock_acquire(&service->spin);
|
|
proto->next = service->ports;
|
|
service->ports = proto;
|
|
spinlock_release(&service->spin);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Add a backend database server to a service
|
|
*
|
|
* @param service The service to add the server to
|
|
* @param server The server to add
|
|
*/
|
|
void
|
|
serviceAddBackend(SERVICE *service, SERVER *server)
|
|
{
|
|
spinlock_acquire(&service->spin);
|
|
server->nextdb = service->databases;
|
|
service->databases = server;
|
|
spinlock_release(&service->spin);
|
|
}
|
|
|
|
/**
|
|
* 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 **)calloc(2, sizeof(char *));
|
|
service->routerOptions[0] = strdup(option);
|
|
service->routerOptions[1] = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; service->routerOptions[i]; i++)
|
|
;
|
|
service->routerOptions = (char **)realloc(service->routerOptions,
|
|
(i + 2) * sizeof(char *));
|
|
service->routerOptions[i] = strdup(option);
|
|
service->routerOptions[i+1] = 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, char *user, char *auth)
|
|
{
|
|
if (service->credentials.name)
|
|
free(service->credentials.name);
|
|
if (service->credentials.authdata)
|
|
free(service->credentials.authdata);
|
|
service->credentials.name = strdup(user);
|
|
service->credentials.authdata = strdup(auth);
|
|
|
|
if (service->credentials.name == NULL || service->credentials.authdata == NULL)
|
|
return 0;
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Print details of an individual service
|
|
*
|
|
* @param service Service to print
|
|
*/
|
|
void
|
|
printService(SERVICE *service)
|
|
{
|
|
SERVER *ptr = service->databases;
|
|
|
|
printf("Service %p\n", service);
|
|
printf("\tService: %s\n", service->name);
|
|
printf("\tRouter: %s (%p)\n", service->routerModule, service->router);
|
|
printf("\tStarted: %s", asctime(localtime(&service->stats.started)));
|
|
printf("\tBackend databases\n");
|
|
while (ptr)
|
|
{
|
|
printf("\t\t%s:%d Protocol: %s\n", ptr->name, ptr->port, ptr->protocol);
|
|
ptr = ptr->nextdb;
|
|
}
|
|
printf("\tUsers data: %p\n", service->users);
|
|
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 debugger session in order
|
|
* to display all active services within the gateway
|
|
*/
|
|
void
|
|
dprintAllServices(DCB *dcb)
|
|
{
|
|
SERVICE *ptr;
|
|
|
|
spinlock_acquire(&service_spin);
|
|
ptr = allServices;
|
|
while (ptr)
|
|
{
|
|
SERVER *server = ptr->databases;
|
|
dcb_printf(dcb, "Service %p\n", ptr);
|
|
dcb_printf(dcb, "\tService: %s\n", ptr->name);
|
|
dcb_printf(dcb, "\tRouter: %s (%p)\n", ptr->routerModule,
|
|
ptr->router);
|
|
if (ptr->router)
|
|
ptr->router->diagnostics(ptr->router_instance, dcb);
|
|
dcb_printf(dcb, "\tStarted: %s",
|
|
asctime(localtime(&ptr->stats.started)));
|
|
dcb_printf(dcb, "\tBackend databases\n");
|
|
while (server)
|
|
{
|
|
dcb_printf(dcb, "\t\t%s:%d Protocol: %s\n", server->name, server->port,
|
|
server->protocol);
|
|
server = server->nextdb;
|
|
}
|
|
dcb_printf(dcb, "\tUsers data: %p\n", ptr->users);
|
|
dcb_printf(dcb, "\tTotal connections: %d\n", ptr->stats.n_sessions);
|
|
dcb_printf(dcb, "\tCurrently connected: %d\n", ptr->stats.n_current);
|
|
ptr = ptr->next;
|
|
}
|
|
spinlock_release(&service_spin);
|
|
}
|