diff --git a/include/maxscale/service.h b/include/maxscale/service.h index e8fd7850e..95ac954c6 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -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 diff --git a/server/core/service.c b/server/core/service.c index 7fe22d93e..f205338ed 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -43,6 +43,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -56,12 +60,9 @@ #include #include #include -#include -#include #include #include #include -#include #include #include #include @@ -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; +} diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 19e33470e..ad568d625 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -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) {