From 2baa90f92dce1cb5b71c7a0b7b8e2ed93f704e77 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Thu, 29 Oct 2009 22:31:59 +0100 Subject: Add fail over utility functions These functions should be used by providers to centrally manage lists of servers. Servers are grouped into services and each service has it's own list of servers. If, however, you will try to add a same server into two different services, they will share a common structure. This means that a host will only be resolved once. --- server/providers/fail_over.c | 543 +++++++++++++++++++++++++++++++++++++++++++ server/providers/fail_over.h | 106 +++++++++ 2 files changed, 649 insertions(+) create mode 100644 server/providers/fail_over.c create mode 100644 server/providers/fail_over.h (limited to 'server/providers') diff --git a/server/providers/fail_over.c b/server/providers/fail_over.c new file mode 100644 index 00000000..ce94ac30 --- /dev/null +++ b/server/providers/fail_over.c @@ -0,0 +1,543 @@ +/* + SSSD + + Fail over helper functions. + + Authors: + Martin Nagy + + Copyright (C) Red Hat, Inc 2009 + + This program 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; either version 3 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#include + +#include +#include +#include +#include + +#include "util/dlinklist.h" +#include "util/refcount.h" +#include "util/util.h" +#include "providers/fail_over.h" +#include "resolv/async_resolv.h" + +#define STATUS_DIFF(p, tv2) ((p)->last_status_change.tv_sec - (tv2).tv_sec) + +#define DEFAULT_PORT_STATUS PORT_NEUTRAL +#define DEFAULT_SERVER_STATUS SERVER_NAME_NOT_RESOLVED + +struct fo_ctx { + struct fo_service *service_list; + struct server_common *server_common_list; + + /* Settings. */ + time_t retry_timeout; +}; + +struct fo_service { + struct fo_service *prev; + struct fo_service *next; + + struct fo_ctx *ctx; + char *name; + struct fo_server *active_server; + struct fo_server *server_list; +}; + +struct fo_server { + struct fo_server *prev; + struct fo_server *next; + + void *user_data; + int port; + int port_status; + struct fo_service *service; + struct timeval last_status_change; + struct server_common *common; +}; + +struct server_common { + REFCOUNT_COMMON; + + struct server_common *prev; + struct server_common *next; + + char *name; + struct hostent *hostent; + struct resolve_service_request *request_list; + int server_status; + struct timeval last_status_change; +}; + +struct resolve_service_request { + struct resolve_service_request *prev; + struct resolve_service_request *next; + + struct server_common *server_common; + struct tevent_req *req; +}; + +struct status { + int value; + struct timeval last_change; +}; + +struct fo_ctx * +fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout) +{ + struct fo_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct fo_ctx); + if (ctx == NULL) { + DEBUG(1, ("No memory\n")); + return NULL; + } + + ctx->retry_timeout = retry_timeout; + + return ctx; +} + +/* + * This function will return the status of the server. If the status was + * last updated a long time ago, we will first reset the status. + */ +static enum server_status +get_server_status(struct fo_server *server) +{ + struct timeval tv; + time_t timeout; + + if (server->common == NULL) + return SERVER_NAME_RESOLVED; + + timeout = server->service->ctx->retry_timeout; + if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) { + gettimeofday(&tv, NULL); + if (STATUS_DIFF(server->common, tv) > timeout) { + server->common->server_status = SERVER_NAME_RESOLVED; + server->last_status_change.tv_sec = tv.tv_sec; + } + } + + return server->common->server_status; +} + +/* + * This function will return the status of the service. If the status was + * last updated a long time ago, we will first reset the status. + */ +static enum port_status +get_port_status(struct fo_server *server) +{ + struct timeval tv; + time_t timeout; + + timeout = server->service->ctx->retry_timeout; + if (timeout != 0 && server->port_status == PORT_NOT_WORKING) { + gettimeofday(&tv, NULL); + if (STATUS_DIFF(server, tv) > timeout) { + server->port_status = PORT_NEUTRAL; + server->last_status_change.tv_sec = tv.tv_sec; + } + } + + return server->port_status; +} + +static int +server_works(struct fo_server *server) +{ + if (get_server_status(server) == SERVER_NOT_WORKING) + return 0; + + return 1; +} + +static int +service_works(struct fo_server *server) +{ + if (!server_works(server)) + return 0; + if (get_port_status(server) == PORT_NOT_WORKING) + return 0; + + return 1; +} + +static int +service_destructor(struct fo_service *service) +{ + DLIST_REMOVE(service->ctx->service_list, service); + return 0; +} + +int +fo_new_service(struct fo_ctx *ctx, const char *name, + struct fo_service **_service) +{ + struct fo_service *service; + int ret; + + ret = fo_get_service(ctx, name, &service); + if (ret == EOK) { + DEBUG(1, ("Service %s already exists\n", name)); + return EEXIST; + } else if (ret != ENOENT) { + return ret; + } + + service = talloc_zero(ctx, struct fo_service); + if (service == NULL) + return ENOMEM; + + service->name = talloc_strdup(service, name); + if (service->name == NULL) { + talloc_free(service); + return ENOMEM; + } + + service->ctx = ctx; + DLIST_ADD(ctx->service_list, service); + + talloc_set_destructor(service, service_destructor); + *_service = service; + + return EOK; +} + +int +fo_get_service(struct fo_ctx *ctx, const char *name, + struct fo_service **_service) +{ + struct fo_service *service; + + DLIST_FOR_EACH(service, ctx->service_list) { + if (!strcasecmp(name, service->name)) { + *_service = service; + return EOK; + } + } + + return ENOENT; +} + +static int +get_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name, + struct server_common **_common) +{ + struct server_common *common; + + DLIST_FOR_EACH(common, ctx->server_common_list) { + if (!strcmp(name, common->name)) { + *_common = rc_reference(mem_ctx, struct server_common, common); + if (_common == NULL) + return ENOMEM; + return EOK; + } + } + + return ENOENT; +} + +static struct server_common * +create_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name) +{ + struct server_common *common; + + common = rc_alloc(mem_ctx, struct server_common); + if (common == NULL) + return NULL; + + common->name = talloc_strdup(common, name); + if (common->name == NULL) { + talloc_free(common); + return NULL; + } + + common->prev = NULL; + common->next = NULL; + common->hostent = NULL; + common->request_list = NULL; + common->server_status = DEFAULT_SERVER_STATUS; + common->last_status_change.tv_sec = 0; + common->last_status_change.tv_usec = 0; + + DLIST_ADD_END(ctx->server_common_list, common, struct server_common *); + return common; +} + +int +fo_add_server(struct fo_service *service, const char *name, int port, + void *user_data) +{ + struct fo_server *server; + int ret; + + DLIST_FOR_EACH(server, service->server_list) { + if (server->port != port || server->user_data != user_data) + continue; + if (name == NULL && server->common == NULL) { + return EEXIST; + } else if (name != NULL && server->common != NULL) { + if (!strcmp(name, server->common->name)) + return EEXIST; + } + } + + server = talloc_zero(service, struct fo_server); + if (server == NULL) + return ENOMEM; + + server->port = port; + server->user_data = user_data; + server->service = service; + server->port_status = DEFAULT_PORT_STATUS; + + if (name != NULL) { + ret = get_server_common(server, service->ctx, name, &server->common); + if (ret == ENOENT) { + server->common = create_server_common(server, service->ctx, name); + if (server->common == NULL) { + talloc_free(server); + return ENOMEM; + } + } else if (ret != EOK) { + talloc_free(server); + return ret; + } + } + + DLIST_ADD_END(service->server_list, server, struct fo_server *); + + return EOK; +} + +static int +get_first_server_entity(struct fo_service *service, struct fo_server **_server) +{ + struct fo_server *server; + + /* If we already have a working server, use that one. */ + server = service->active_server; + if (server != NULL) { + if (service_works(server)) { + *_server = server; + return EOK; + } + service->active_server = NULL; + } + + /* Otherwise iterate through the server list. */ + DLIST_FOR_EACH(server, service->server_list) { + if (service_works(server)) { + *_server = server; + return EOK; + } + } + + return ENOENT; +} + +static int +resolve_service_request_destructor(struct resolve_service_request *request) +{ + DLIST_REMOVE(request->server_common->request_list, request); + return 0; +} + +static int +set_lookup_hook(struct fo_server *server, struct tevent_req *req) +{ + struct resolve_service_request *request; + + request = talloc(req, struct resolve_service_request); + if (request == NULL) { + DEBUG(1, ("No memory\n")); + talloc_free(request); + return ENOMEM; + } + request->server_common = server->common; + request->req = req; + DLIST_ADD(server->common->request_list, request); + talloc_set_destructor(request, resolve_service_request_destructor); + + return EOK; +} + +/******************************************************************* + * Get server to connect to. * + *******************************************************************/ + +struct resolve_service_state { + struct fo_server *server; +}; + +static void fo_resolve_service_done(struct tevent_req *subreq); + +struct tevent_req * +fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *resolv, struct fo_service *service) +{ + int ret; + struct fo_server *server; + struct tevent_req *req; + struct tevent_req *subreq; + struct resolve_service_state *state; + + req = tevent_req_create(mem_ctx, &state, struct resolve_service_state); + if (req == NULL) + return NULL; + + ret = get_first_server_entity(service, &server); + if (ret != EOK) { + DEBUG(1, ("No available servers for service '%s'\n", service->name)); + goto done; + } + + state->server = server; + + if (server->common == NULL) { + /* This server doesn't have a name, we don't do name resolution. */ + tevent_req_done(req); + tevent_req_post(req, ev); + return req; + } + + switch (get_server_status(server)) { + case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */ + /* TODO: Enable IPv6. */ + subreq = resolv_gethostbyname_send(server->common, ev, resolv, + server->common->name, AF_INET); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, fo_resolve_service_done, server->common); + /* FALLTHROUGH */ + case SERVER_RESOLVING_NAME: + /* Name resolution is already under way. Just add ourselves into the + * waiting queue so we get notified after the operation is finished. */ + ret = set_lookup_hook(server, req); + if (ret != EOK) + goto done; + break; + default: /* The name is already resolved. Return immediately. */ + tevent_req_done(req); + tevent_req_post(req, ev); + break; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void +fo_resolve_service_done(struct tevent_req *subreq) +{ + int resolv_status; + struct resolve_service_request *request; + struct server_common *common; + + common = tevent_req_callback_data(subreq, struct server_common); + + if (common->hostent != NULL) + talloc_free(common->hostent); + resolv_gethostbyname_recv(common, subreq, &resolv_status, NULL, + &common->hostent); + talloc_free(subreq); + + if (common->hostent == NULL) { + DEBUG(1, ("Failed to resolve %s: %s\n", common->name, + resolv_strerror(resolv_status))); + } + + /* Take care of all requests for this server. */ + while ((request = common->request_list) != NULL) { + DLIST_REMOVE(common->request_list, request); + if (resolv_status) + tevent_req_error(request->req, resolv_status); + else + tevent_req_done(request->req); + } +} + +int +fo_resolve_service_recv(struct tevent_req *req, struct fo_server **server) +{ + struct resolve_service_state *state; + enum tevent_req_state tstate; + uint64_t err = EIO; + + state = tevent_req_data(req, struct resolve_service_state); + + if (tevent_req_is_error(req, &tstate, &err)) + return err; + + if (server) + *server = state->server; + + return EOK; +} + +void +fo_set_server_status(struct fo_server *server, enum server_status status) +{ + if (server->common == NULL) { + DEBUG(1, ("Bug: Trying to set server status of a name-less server\n")); + return; + } + server->common->server_status = status; + gettimeofday(&server->common->last_status_change, NULL); +} + +void +fo_set_port_status(struct fo_server *server, enum port_status status) +{ + server->port_status = status; + gettimeofday(&server->last_status_change, NULL); + if (status == PORT_WORKING) { + fo_set_server_status(server, SERVER_WORKING); + server->service->active_server = server; + } +} + +void * +fo_get_server_user_data(struct fo_server *server) +{ + return server->user_data; +} + +int +fo_get_server_port(struct fo_server *server) +{ + return server->port; +} + +struct hostent * +fo_get_server_hostent(struct fo_server *server) +{ + if (server->common == NULL) { + DEBUG(1, ("Bug: Trying to get hostent from a name-less server\n")); + return NULL; + } + return server->common->hostent; +} diff --git a/server/providers/fail_over.h b/server/providers/fail_over.h new file mode 100644 index 00000000..b6e2f2f7 --- /dev/null +++ b/server/providers/fail_over.h @@ -0,0 +1,106 @@ +#ifndef __FAIL_OVER_H__ +#define __FAIL_OVER_H__ + +#include +#include + +/* Some forward declarations that don't have to do anything with fail over. */ +struct hostent; +struct resolv_ctx; +struct tevent_context; +struct tevent_req; + +enum port_status { + PORT_NEUTRAL, /* We didn't try this port yet. */ + PORT_WORKING, /* This port was reported to work. */ + PORT_NOT_WORKING /* This port was reported to not work. */ +}; + +enum server_status { + SERVER_NAME_NOT_RESOLVED, /* We didn't yet resolved the host name. */ + SERVER_RESOLVING_NAME, /* Name resolving is in progress. */ + SERVER_NAME_RESOLVED, /* We resolved the host name but didn't try to connect. */ + SERVER_WORKING, /* We successfully connected to the server. */ + SERVER_NOT_WORKING /* We tried and failed to connect to the server. */ +}; + +struct fo_ctx; +struct fo_service; +struct fo_server; + +/* + * Create a new fail over context. The 'retry_timeout' argument specifies the + * duration in seconds of how long a server or port will be considered + * non-working after being marked as such. + */ +struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx, + time_t retry_timeout); + +/* + * Create a new service structure for 'ctx', saving it to the location pointed + * to by '_service'. The needed memory will be allocated from 'ctx'. + * Service name will be set to 'name'. + */ +int fo_new_service(struct fo_ctx *ctx, + const char *name, + struct fo_service **_service); + +/* + * Look up service named 'name' from the 'ctx' service list. Target of + * '_service' will be set to the service if it was found. + */ +int fo_get_service(struct fo_ctx *ctx, + const char *name, + struct fo_service **_service); + +/* + * Adds a server 'name' to the 'service'. Port 'port' will be used for + * connection. If 'name' is NULL, no server resolution will be done. + */ +int fo_add_server(struct fo_service *service, + const char *name, + int port, + void *user_data); + +/* + * Request the first server from the service's list of servers. It is only + * considered if it is not marked as not working (or the retry interval already + * passed). If the server address wasn't resolved yet, it will be done. + */ +struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *resolv, + struct fo_service *service); + +int fo_resolve_service_recv(struct tevent_req *req, + struct fo_server **server); + +/* + * Set feedback about 'server'. Caller should use this to indicate a problem + * with the server itself, not only with the service on that server. This + * should be used, for example, when the IP address of the server can't be + * reached. This setting can affect other services as well, since they can + * share the same server. + */ +void fo_set_server_status(struct fo_server *server, + enum server_status status); + +/* + * Set feedback about the port status. This function should be used when + * the server itself is working but the service is not. When status is set + * to PORT_WORKING, 'server' is also marked as an "active server" for it's + * service. When the next fo_resolve_service_send() function is called, this + * server will be preferred. This will hold as long as it is not marked as + * not-working. + */ +void fo_set_port_status(struct fo_server *server, + enum port_status status); + + +void *fo_get_server_user_data(struct fo_server *server); + +int fo_get_server_port(struct fo_server *server); + +struct hostent *fo_get_server_hostent(struct fo_server *server); + +#endif /* !__FAIL_OVER_H__ */ -- cgit