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:
parent
a17aa28eed
commit
878d01e276
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user