Persist server changes to services

When a service is added or removed from a service, a supplementary
configuration file is created. This allows MaxScale to survive restars and
unexpected downtime even if runtime changes to the servers of a service
have been made.

With these changes, it is possible to start MaxScale without any servers,
create servers, add the created servers to services and monitors and
restart Maxscale without losing the runtime configuration changes.
This commit is contained in:
Markus Makela 2016-11-12 22:49:27 +02:00
parent a17aa28eed
commit 878d01e276
3 changed files with 136 additions and 32 deletions

View File

@ -200,7 +200,7 @@ extern int serviceHasProtocol(SERVICE *service, const char *protocol,
const char* address, unsigned short port);
extern void serviceAddBackend(SERVICE *, SERVER *);
extern void serviceRemoveBackend(SERVICE *, const SERVER *);
extern int serviceHasBackend(SERVICE *, SERVER *);
extern bool serviceHasBackend(SERVICE *, SERVER *);
extern void serviceAddRouterOption(SERVICE *, char *);
extern void serviceClearRouterOptions(SERVICE *);
extern int serviceStart(SERVICE *);
@ -264,4 +264,20 @@ static inline uint64_t service_get_capabilities(const SERVICE *service)
*/
bool service_server_in_use(const SERVER *server);
/**
* @brief Serialize a service to a file
*
* This partially converts @c service into an INI format file. Only the servers
* of the service are serialized. This allows the service to keep using the servers
* added at runtime even after a restart.
*
* NOTE: This does not persist the complete service configuration and requires
* that an existing service configuration is in the main configuration file.
* Changes to service parameters are not persisted.
*
* @param service Service to serialize
* @return False if the serialization of the service fails, true if it was successful
*/
bool service_serialize_servers(const SERVICE *service);
MXS_END_DECLS

View File

@ -43,6 +43,10 @@
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <math.h>
#include <fcntl.h>
#include <maxscale/session.h>
#include <maxscale/service.h>
#include <maxscale/gw_protocol.h>
@ -56,12 +60,9 @@
#include <maxscale/filter.h>
#include <maxscale/poll.h>
#include <maxscale/log_manager.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <maxscale/housekeeper.h>
#include <maxscale/resultset.h>
#include <maxscale/gwdirs.h>
#include <math.h>
#include <maxscale/version.h>
#include <maxscale/queuemanager.h>
#include <maxscale/alloc.h>
@ -769,43 +770,46 @@ static SERVER_REF* server_ref_create(SERVER *server)
void
serviceAddBackend(SERVICE *service, SERVER *server)
{
SERVER_REF *new_ref = server_ref_create(server);
if (new_ref)
if (!serviceHasBackend(service, server))
{
spinlock_acquire(&service->spin);
SERVER_REF *new_ref = server_ref_create(server);
service->n_dbref++;
if (service->dbref)
if (new_ref)
{
SERVER_REF *ref = service->dbref;
SERVER_REF *prev = ref;
spinlock_acquire(&service->spin);
while (ref)
service->n_dbref++;
if (service->dbref)
{
if (ref->server == server)
SERVER_REF *ref = service->dbref;
SERVER_REF *prev = ref;
while (ref)
{
ref->active = true;
break;
if (ref->server == server)
{
ref->active = true;
break;
}
prev = ref;
ref = ref->next;
}
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;
if (ref == NULL)
{
/** A new server that hasn't been used by this service */
atomic_synchronize();
prev->next = new_ref;
}
}
else
{
atomic_synchronize();
service->dbref = new_ref;
}
spinlock_release(&service->spin);
}
else
{
atomic_synchronize();
service->dbref = new_ref;
}
spinlock_release(&service->spin);
}
}
@ -841,7 +845,7 @@ void serviceRemoveBackend(SERVICE *service, const SERVER *server)
* @param server The server to add
* @return Non-zero if the server is already part of the service
*/
int
bool
serviceHasBackend(SERVICE *service, SERVER *server)
{
SERVER_REF *ptr;
@ -2217,3 +2221,85 @@ bool service_server_in_use(const SERVER *server)
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)
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to open file '%s' when serializing service '%s': %d, %s",
filename, service->name, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
return false;
}
/**
* Only additional parameters are added to the configuration. This prevents
* duplication or addition of parameters that don't support it.
*
* TODO: Check for return values on all of the dprintf calls
*/
dprintf(file, "[%s]\n", service->name);
if (service->dbref)
{
dprintf(file, "servers=");
for (SERVER_REF *db = service->dbref; db; db = db->next)
{
if (db != service->dbref)
{
dprintf(file, ",");
}
dprintf(file, "%s", db->server->unique_name);
}
dprintf(file, "\n");
}
close(file);
return true;
}
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)
{
char err[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to remove temporary service configuration at '%s': %d, %s",
filename, errno, strerror_r(errno, err, sizeof(err)));
}
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
{
char err[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to rename temporary service configuration at '%s': %d, %s",
filename, errno, strerror_r(errno, err, sizeof(err)));
}
}
return rval;
}

View File

@ -749,6 +749,7 @@ static void cmd_AddServer(DCB *dcb, void *a, void *b)
if (service)
{
serviceAddBackend(service, server);
service_serialize_servers(service);
}
else if (monitor)
{
@ -806,6 +807,7 @@ static void cmd_RemoveServer(DCB *dcb, void *a, void *b)
if (service)
{
serviceRemoveBackend(service, server);
service_serialize_servers(service);
}
else if (monitor)
{