fail_over.c revision bd4c2ed5aec7f57ea04500f0e43f151eedfdde45
/*
SSSD
Fail over helper functions.
Authors:
Martin Nagy <mnagy@redhat.com>
Jakub Hrozek <jhrozek@redhat.com>
Copyright (C) Red Hat, Inc 2010
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 <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdbool.h>
#include <strings.h>
#include <talloc.h>
#include "util/dlinklist.h"
#include "util/refcount.h"
#include "providers/fail_over.h"
#include "resolv/async_resolv.h"
#define DEFAULT_PORT_STATUS PORT_NEUTRAL
#define DEFAULT_SRV_STATUS SRV_NEUTRAL
enum srv_lookup_status {
SRV_NEUTRAL, /* We didn't try this SRV lookup yet */
SRV_RESOLVED, /* This SRV lookup is resolved */
SRV_RESOLVE_ERROR, /* Could not resolve this SRV lookup */
SRV_EXPIRED /* Need to refresh the SRV query */
};
struct fo_ctx {
struct fo_service *service_list;
struct server_common *server_common_list;
struct fo_options *opts;
void *srv_pvt;
};
struct fo_service {
struct fo_service *prev;
struct fo_service *next;
char *name;
struct fo_server *active_server;
struct fo_server *last_tried_server;
struct fo_server *server_list;
/* Function pointed by user_data_cmp returns 0 if user_data is equal
* or nonzero value if not. Set to NULL if no user data comparison
* is needed in fail over duplicate servers detection.
*/
};
struct fo_server {
bool primary;
void *user_data;
int port;
enum port_status port_status;
struct fo_service *service;
struct timeval last_status_change;
struct server_common *common;
};
struct server_common {
struct server_common *prev;
struct server_common *next;
char *name;
struct resolv_hostent *rhostent;
struct resolve_service_request *request_list;
enum server_status server_status;
struct timeval last_status_change;
};
struct srv_data {
char *dns_domain;
char *discovery_domain;
char *sssd_domain;
char *proto;
char *srv;
int srv_lookup_status;
int ttl;
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 tevent_context *ev;
};
struct status {
int value;
struct timeval last_change;
};
struct fo_ctx *
{
return NULL;
}
return NULL;
}
"Created new fail over context, retry timeout is %ld\n",
return ctx;
}
static const char *
{
switch (status) {
case PORT_NEUTRAL:
return "neutral";
case PORT_WORKING:
return "working";
case PORT_NOT_WORKING:
return "not working";
}
return "unknown port status";
}
static const char *
{
switch (status) {
case SRV_NEUTRAL:
return "neutral";
case SRV_RESOLVED:
return "resolved";
case SRV_RESOLVE_ERROR:
return "not resolved";
case SRV_EXPIRED:
return "expired";
}
return "unknown SRV lookup status";
}
static const char *
{
switch (status) {
case SERVER_NAME_NOT_RESOLVED:
return "name not resolved";
case SERVER_RESOLVING_NAME:
return "resolving name";
case SERVER_NAME_RESOLVED:
return "name resolved";
case SERVER_WORKING:
return "working";
case SERVER_NOT_WORKING:
return "not working";
}
return "unknown server status";
}
int fo_is_srv_lookup(struct fo_server *s)
{
return s && s->srv_data;
}
{
return;
}
}
static struct fo_server *
{
}
}
}
}
/* add back the meta server to denote SRV lookup */
}
return meta;
}
static enum srv_lookup_status
{
/* Determine timeout value based on state of previous lookup. */
} else {
}
switch(data->srv_lookup_status) {
case SRV_EXPIRED:
case SRV_NEUTRAL:
break;
case SRV_RESOLVED:
break;
case SRV_RESOLVE_ERROR:
"Changing state of SRV lookup from 'SRV_RESOLVE_ERROR' to "
"'SRV_NEUTRAL'.\n");
break;
default:
}
}
return data->srv_lookup_status;
}
static void
{
}
/*
* 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
{
return SERVER_NAME_RESOLVED;
}
}
"Hostname resolution expired, resetting the server "
}
}
/*
* 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
{
"Reseting the status of port %d for server '%s'\n",
}
}
return server->port_status;
}
static int
{
return 0;
return 1;
}
static int
{
if (!server_works(server))
return 0;
return 0;
return 1;
}
static int
{
return 0;
}
int
struct fo_service **_service)
{
struct fo_service *service;
int ret;
if (_service) {
}
return EEXIST;
return ret;
}
return ENOMEM;
return ENOMEM;
}
if (_service) {
}
return EOK;
}
int
struct fo_service **_service)
{
struct fo_service *service;
return EOK;
}
}
return ENOENT;
}
static int
struct server_common **_common)
{
struct server_common *common;
return ENOMEM;
return EOK;
}
}
return ENOENT;
}
static int server_common_destructor(void *memptr)
{
struct server_common *common;
if (common->request_list) {
"BUG: pending requests still associated with this server\n");
return -1;
}
return 0;
}
static struct server_common *
{
struct server_common *common;
return NULL;
}
return NULL;
}
return common;
}
static struct fo_server *
{
if (server_owner == NULL) {
return NULL;
}
return NULL;
}
return server;
}
int
const char *discovery_domain, const char *sssd_domain,
{
"Adding new SRV server to service '%s' using '%s'.\n",
/* Compare user data only if user_data_cmp and both arguments
* are not NULL.
*/
continue;
}
}
if (fo_is_srv_lookup(server)) {
if (((discovery_domain == NULL &&
(discovery_domain != NULL &&
return EEXIST;
}
}
}
/* SRV servers are always primary */
return ENOMEM;
}
/* add the SRV-specific data */
return ENOMEM;
return ENOMEM;
if (discovery_domain) {
return ENOMEM;
return ENOMEM;
}
return ENOMEM;
return EOK;
}
static struct fo_server *
{
int ret;
return NULL;
return NULL;
}
return NULL;
}
}
return server;
}
int
{
int count = 0;
count++;
}
return count;
}
const char *name,
int port,
void *user_data)
{
return false;
}
/* Compare user data only if user_data_cmp and both arguments
* are not NULL.
*/
return false;
}
}
return true;
}
return true;
}
return false;
}
{
}
}
const char *name,
int port,
void *user_data)
{
return true;
}
}
return false;
}
struct fo_server *check_list,
const char *service_name)
{
const char *debug_name = NULL;
bool exists;
debug_name = "(no name)";
} else {
}
if (exists) {
return EEXIST;
}
return EOK;
}
struct fo_server *after_server,
struct fo_server_info *servers,
void *user_data,
bool primary,
struct fo_server **_last_server)
{
size_t i;
for (i = 0; i < num_servers; i++) {
return ENOMEM;
}
continue;
}
}
}
if (_last_server != NULL) {
}
return EOK;
}
int
{
if (!server) {
return ENOMEM;
}
}
return ret;
}
{
if (server) {
}
}
static int
{
/* If we already have a working server, use that one. */
goto done;
}
}
/*
* Otherwise iterate through the server list.
*/
/* First, try primary servers after the last one we tried.
* (only if the last one was primary as well)
*/
goto done;
}
/* Go only through primary servers */
if (service_works(server)) {
goto done;
}
}
}
/* If none were found, try at the start, primary first */
/* First iterate only over primary servers */
if (service_works(server)) {
goto done;
}
break;
}
}
/* Now iterate only over backup servers */
if (service_works(server)) {
goto done;
}
}
return ENOENT;
done:
return EOK;
}
static int
{
return 0;
}
static int
struct tevent_req *req)
{
struct resolve_service_request *request;
return ENOMEM;
}
return ENOMEM;
}
return EOK;
}
/*******************************************************************
* Get server to connect to. *
*******************************************************************/
struct resolve_service_state {
struct resolv_ctx *resolv;
struct tevent_context *ev;
struct tevent_timer *timeout_handler;
};
/* Forward declarations for SRV resolving */
static struct tevent_req *
static int
struct tevent_req *
struct fo_service *service)
{
int ret;
struct tevent_req *req;
struct tevent_req *subreq;
struct resolve_service_state *state;
return NULL;
goto done;
}
/* Activate per-service timeout handler */
goto done;
}
if (fo_is_srv_lookup(server)) {
/* Don't know the server yet, must do a SRV lookup */
goto done;
}
req);
return req;
}
/* This is a regular server, just do hostname lookup */
if (fo_resolve_service_server(req)) {
}
done:
}
return req;
}
enum server_status status);
static void
struct tevent_timer *te,
{
}
static errno_t
struct tevent_context *ev,
const unsigned long timeout_seconds)
{
struct resolve_service_state);
tv = tevent_timeval_current();
return ENOMEM;
}
return EOK;
}
/* SRV resolving finished, see if we got server to work with */
static void
{
struct tevent_req);
struct resolve_service_state);
int ret;
if (ret) {
return;
}
}
static bool
{
struct resolve_service_state);
struct tevent_req *subreq;
int ret;
case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */
return true;
}
/* 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. */
return true;
}
break;
default: /* The name is already resolved. Return immediately. */
return true;
}
return false;
}
static void
{
struct server_common);
int resolv_status;
struct resolve_service_request *request;
int ret;
}
/* If the resolver failed to resolve a hostname but did not
* encounter an error, tell the caller to retry another server.
*
* If there are no more servers to try, the next request would
* just shortcut with ENOENT.
*/
}
} else {
}
/* Take care of all requests for this server. */
/* If the request callback decresed refcount on the returned
* server, we would have crashed as common would not be valid
* anymore. Rather schedule the notify for next tev iteration
*/
if (ret) {
} else {
}
}
}
int
{
struct resolve_service_state *state;
/* always return the server if asked for, otherwise the caller
* cannot mark it as faulty in case we return an error */
}
return EOK;
}
/*******************************************************************
* Resolve the server to connect to using a SRV query. *
*******************************************************************/
struct resolve_srv_state {
struct fo_service *service;
struct resolv_ctx *resolv;
struct tevent_context *ev;
};
static struct tevent_req *
{
int ret;
struct tevent_req *req;
struct tevent_req *subreq;
struct resolve_srv_state *state;
int status;
return NULL;
switch(status) {
case SRV_EXPIRED: /* Need a refresh */
/* FALLTHROUGH.
* "server" might be invalid now if the SRV
* query collapsed
* */
case SRV_NEUTRAL: /* Request SRV lookup */
/* A server created by expansion of meta server was marked as
* neutral. We have to collapse the servers and issue new
* SRV resolution. */
}
goto done;
}
goto done;
}
break;
case SRV_RESOLVE_ERROR: /* query could not be resolved but don't retry yet */
/* The port status was reseted to neutral but we still haven't reached
* timeout to try to resolve SRV record again. We will set the port
* status back to not working. */
goto done;
case SRV_RESOLVED: /* The query is resolved and valid. Return. */
return req;
default:
"Unexpected status %d for a SRV server\n", status);
goto done;
}
done:
}
return req;
}
static void
{
struct tevent_req);
struct resolve_srv_state);
size_t num_backup_servers = 0;
char *dns_domain = NULL;
int ret;
switch (ret) {
case EOK:
"no servers\n");
goto done;
}
if (primary_servers != NULL) {
true, &last_server);
goto done;
}
}
if (backup_servers != NULL) {
false, &last_server);
goto done;
}
}
/* SRV lookup returned only those servers
* that are already present. */
"any new server.\n");
goto done;
}
/* At least one new server was inserted.
* We will return the first new server. */
"BUG: state->meta->next is NULL\n");
ret = ERR_INTERNAL;
goto done;
}
/* And remove meta server from the server list. It will be
* inserted again during srv collapse. */
}
break;
case ERR_SRV_NOT_FOUND:
/* fall through */
case ERR_SRV_LOOKUP_ERROR:
/* fall through */
default:
}
done:
return;
}
}
static int
{
struct resolve_srv_state);
/* always return the server if asked for, otherwise the caller
* cannot mark it as faulty in case we return an error */
if (server) {
}
return EOK;
}
/*******************************************************************
* Get Fully Qualified Domain Name of the host machine *
*******************************************************************/
static void
enum server_status status)
{
}
void
{
"Bug: Trying to set server status of a name-less server\n");
return;
}
}
void
{
if (status == PORT_WORKING) {
}
/* It is possible to introduce duplicates when expanding SRV results
* into fo_server structures. Find the duplicates and set the same
* status */
"Marking port %d of duplicate server '%s' as '%s'\n",
}
}
}
{
return service->active_server;
}
{
if (!service) {
return;
}
if (!server) {
return;
}
service->active_server = 0;
}
}
void *
{
}
int
{
}
const char *
{
return NULL;
}
}
{
if (fo_is_srv_lookup(server)) {
return "SRV lookup meta-server";
}
return "unknown name";
}
}
struct resolv_hostent *
{
"Bug: Trying to get hostent from a name-less server\n");
return NULL;
}
}
bool
{
}
{
return 0;
}
}
{
return 0;
}
}
{
}
}
}
}
{
struct fo_service *service;
"Resetting all servers in all services\n");
}
}
{
}
return false;
}
struct fo_service *service,
{
const char **list;
const char *server;
count = 0;
count++;
}
return NULL;
}
count = 0;
/* _srv_ */
continue;
}
return NULL;
}
count++;
}
}
return list;
}
void *pvt)
{
return false;
}
return false;
}
return true;
}