summaryrefslogtreecommitdiffstats
path: root/ipalib/rpc.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2010-07-26 17:54:38 -0400
committerRob Crittenden <rcritten@redhat.com>2010-08-16 10:35:27 -0400
commit1df10a88cd8b36be8b9b4d47c49dd9e7d1d12bc0 (patch)
tree965da3c4c157e0aaba6b876b578ebcf8a7dc190d /ipalib/rpc.py
parent3e6f0f5721f76977475792f09758f6b8dcc4ed4e (diff)
downloadfreeipa-1df10a88cd8b36be8b9b4d47c49dd9e7d1d12bc0.tar.gz
freeipa-1df10a88cd8b36be8b9b4d47c49dd9e7d1d12bc0.tar.xz
freeipa-1df10a88cd8b36be8b9b4d47c49dd9e7d1d12bc0.zip
Add support for client failover to the ipa command-line.
This adds a new global option to the ipa command, -f/--no-fallback. If this is included then just the server configured in /etc/ipa/default.conf is used. Otherwise that is tried first then all servers in DNS with the ldap SRV record are tried. Create a new Local() Command class for local-only commands. The help command is one of these. It shouldn't need a remote connection to execute. ticket #15
Diffstat (limited to 'ipalib/rpc.py')
-rw-r--r--ipalib/rpc.py86
1 files changed, 73 insertions, 13 deletions
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 686df3cba..472e0628b 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -37,13 +37,14 @@ import errno
from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, Transport, ProtocolError
import kerberos
from ipalib.backend import Connectible
-from ipalib.errors import public_errors, PublicError, UnknownError, NetworkError
+from ipalib.errors import public_errors, PublicError, UnknownError, NetworkError, KerberosError
from ipalib import errors
from ipalib.request import context
-from ipapython import ipautil
+from ipapython import ipautil, dnsclient
import httplib
from ipapython.nsslib import NSSHTTPS
from nss.error import NSPRError
+from urllib2 import urlparse
# Some Kerberos error definitions from krb5.h
KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN = (-1765328377L)
@@ -255,12 +256,70 @@ class xmlclient(Connectible):
super(xmlclient, self).__init__()
self.__errors = dict((e.errno, e) for e in public_errors)
- def create_connection(self, ccache=None, verbose=False):
- kw = dict(allow_none=True, encoding='UTF-8')
- if self.env.xmlrpc_uri.startswith('https://'):
- kw['transport'] = KerbTransport()
- kw['verbose'] = verbose
- return ServerProxy(self.env.xmlrpc_uri, **kw)
+ def reconstruct_url(self):
+ """
+ The URL directly isn't stored in the ServerProxy. We can't store
+ it in the connection object itself but we can reconstruct it
+ from the ServerProxy.
+ """
+ if not hasattr(self.conn, '_ServerProxy__transport'):
+ return None
+ if isinstance(self.conn._ServerProxy__transport, KerbTransport):
+ scheme = "https"
+ else:
+ scheme = "http"
+ server = '%s://%s%s' % (scheme, self.conn._ServerProxy__host, self.conn._ServerProxy__handler)
+ return server
+
+ def get_url_list(self):
+ """
+ Create a list of urls consisting of the available IPA servers.
+ """
+ # the configured URL defines what we use for the discovered servers
+ (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(self.env.xmlrpc_uri)
+ servers = []
+ name = '_ldap._tcp.%s.' % self.env.domain
+ rs = dnsclient.query(name, dnsclient.DNS_C_IN, dnsclient.DNS_T_SRV)
+ for r in rs:
+ if r.dns_type == dnsclient.DNS_T_SRV:
+ rsrv = r.rdata.server.rstrip('.')
+ servers.append('https://%s%s' % (rsrv, path))
+ servers = list(set(servers))
+ # the list/set conversion won't preserve order so stick in the
+ # local config file version here.
+ servers.insert(0, self.env.xmlrpc_uri)
+ return servers
+
+ def create_connection(self, ccache=None, verbose=False, fallback=True):
+ servers = self.get_url_list()
+ serverproxy = None
+ for server in servers:
+ kw = dict(allow_none=True, encoding='UTF-8')
+ kw['verbose'] = verbose
+ if server.startswith('https://'):
+ kw['transport'] = KerbTransport()
+ self.log.info('trying %s' % server)
+ serverproxy = ServerProxy(server, **kw)
+ if len(servers) == 1 or not fallback:
+ # if we have only 1 server to try then let the main
+ # requester handle any errors
+ return serverproxy
+ try:
+ command = getattr(serverproxy, 'ping')
+ response = command()
+ # We don't care about the response, just that we got one
+ break
+ except KerberosError, krberr:
+ # kerberos error on one server is likely on all
+ raise errors.KerberosError(major=str(krberr), minor='')
+ except Exception, e:
+ if not fallback:
+ raise e
+ serverproxy = None
+
+ if serverproxy is None:
+ raise NetworkError(uri='any of the configured servers', error=', '.join(servers))
+ return serverproxy
def destroy_connection(self):
pass
@@ -280,7 +339,8 @@ class xmlclient(Connectible):
raise ValueError(
'%s.forward(): %r not in api.Command' % (self.name, name)
)
- self.info('Forwarding %r to server %r', name, self.env.xmlrpc_uri)
+ server = self.reconstruct_url()
+ self.info('Forwarding %r to server %r', name, server)
command = getattr(self.conn, name)
params = [args, kw]
try:
@@ -289,16 +349,16 @@ class xmlclient(Connectible):
except Fault, e:
e = decode_fault(e)
self.debug('Caught fault %d from server %s: %s', e.faultCode,
- self.env.xmlrpc_uri, e.faultString)
+ server, e.faultString)
if e.faultCode in self.__errors:
error = self.__errors[e.faultCode]
raise error(message=e.faultString)
raise UnknownError(
code=e.faultCode,
error=e.faultString,
- server=self.env.xmlrpc_uri,
+ server=server,
)
except NSPRError, e:
- raise NetworkError(uri=self.env.xmlrpc_uri, error=str(e))
+ raise NetworkError(uri=server, error=str(e))
except ProtocolError, e:
- raise NetworkError(uri=self.env.xmlrpc_uri, error=e.errmsg)
+ raise NetworkError(uri=server, error=e.errmsg)