diff options
author | Seth Vidal <skvidal@fedoraproject.org> | 2007-09-26 16:13:58 -0400 |
---|---|---|
committer | Seth Vidal <skvidal@fedoraproject.org> | 2007-09-26 16:13:58 -0400 |
commit | 7dc50faf68fd3d20691263792dae609ec1cee13b (patch) | |
tree | ce51d02c2415fbf37ddff1e9ce90b66ca57dc9d0 | |
parent | d535d52a58ae719aa1b5c60e7d8febacdaf8cebd (diff) | |
parent | 835b143c095aa7bd09ed050790a331e152599105 (diff) | |
download | func-7dc50faf68fd3d20691263792dae609ec1cee13b.tar.gz func-7dc50faf68fd3d20691263792dae609ec1cee13b.tar.xz func-7dc50faf68fd3d20691263792dae609ec1cee13b.zip |
Merge branch 'master' of ssh://git.fedoraproject.org/git/hosted/func
* 'master' of ssh://git.fedoraproject.org/git/hosted/func:
missed an import somewhere, fix
Generalize test code.
Merge with alikins.
Adding a noglobs=True/False parameter to the client. When set to True, the return codes assume
use the FuncServer class from sslclient instead of the
Enable ssl cert useage by default for funcd
Add gitignore for compressed man pages in docs
Two things. First Client("*").hardware.info() and the like now works, due to some clever magic with getattr. You have one client object that can address multiples and returns a hash with the results for each machine. Second, results are hashes, not lists, and we are a bit more clever in returning a return code the CLI .. the highest int wins if there's an int, for instance. Still, return codes are relatively meaningless for multi-control ... the true power is in scripting things.
add a simple FuncServer class that sets up most of the pki related bits
requires pyOpenSSL otherwise certmaster won't start
-rw-r--r-- | docs/.gitignore | 2 | ||||
-rw-r--r-- | func.spec | 4 | ||||
-rwxr-xr-x | minion/logger.py | 4 | ||||
-rwxr-xr-x | minion/server.py | 95 | ||||
-rwxr-xr-x | overlord/client.py | 105 | ||||
-rw-r--r-- | overlord/sslclient.py | 19 | ||||
-rw-r--r-- | overlord/test_func.py | 10 | ||||
-rw-r--r-- | version | 2 |
8 files changed, 183 insertions, 58 deletions
diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..46952a3 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +# ignore compressed man pages +*.gz @@ -12,6 +12,7 @@ Group: Applications/System Requires: python >= 2.3 Requires: rhpl Requires: yum-utils +Requires: pyOpenSSL BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot BuildArch: noarch Url: https://hosted.fedoraproject.org/projects/func/ @@ -68,6 +69,9 @@ fi %changelog +* Wed Sep 26 2007 Jesus Rodriguez <jesusr@redhat.com> - 0.0.11-5 +- fixed Requires to include pyOpenSSL for use by certmaster + * Tue Sep 25 2007 Michael DeHaan <mdehaan@redhat.com> - 0.0.11-4 - Added manpage documentation - Renamed minion config file diff --git a/minion/logger.py b/minion/logger.py index 7747824..f6f9c0f 100755 --- a/minion/logger.py +++ b/minion/logger.py @@ -63,9 +63,9 @@ class AuditLogger(Singleton): if self._no_handlers: self._setup_handlers(logfilepath=logfilepath) - def log_call(self, method, params): + def log_call(self, CN, cert_hash, method, params): # square away a good parseable format at some point -akl - self.logger.info("%s called with %s" % (method, params)) + self.logger.info("%s %s %s called with %s" % (CN, cert_hash, method, params)) def _setup_logging(self): diff --git a/minion/server.py b/minion/server.py index 3762095..0208f46 100755 --- a/minion/server.py +++ b/minion/server.py @@ -24,17 +24,16 @@ from rhpl.translate import _, N_, textdomain, utf8 I18N_DOMAIN = "func" # our modules +import AuthedXMLRPCServer import codes import config_data import logger import module_loader import utils -# ====================================================================================== - class XmlRpcInterface(object): - def __init__(self, modules={}, server=None): + def __init__(self): """ Constructor. @@ -42,13 +41,12 @@ class XmlRpcInterface(object): config_obj = config_data.Config() self.config = config_obj.get() - self.modules = modules self.logger = logger.Logger().logger self.audit_logger = logger.AuditLogger() self.__setup_handlers() # need a reference so we can log ip's, certs, etc - self.server = server +# self.server = server def __setup_handlers(self): @@ -74,8 +72,6 @@ class XmlRpcInterface(object): def list_methods(self): return self.handlers.keys() - - def get_dispatch_method(self, method): if method in self.handlers: @@ -85,24 +81,8 @@ class XmlRpcInterface(object): self.logger.info("Unhandled method call for method: %s " % method) raise codes.InvalidMethodException - def _dispatch(self, method, params): - - """ - the SimpleXMLRPCServer class will call _dispatch if it doesn't - find a handler method - """ - - # Recognize ipython's tab completion calls - if method == 'trait_names' or method == '_getAttributeNames': - return self.handlers.keys() - - # XXX FIXME - need to figure out how to dig into the server base classes - # so we can get client ip, and eventually cert id info -akl - self.audit_logger.log_call(method, params) + - return self.get_dispatch_method(method)(*params) - -# ====================================================================================== class FuncApiMethod: @@ -144,7 +124,7 @@ class FuncApiMethod: return rc -# ====================================================================================== + def serve(): @@ -152,27 +132,70 @@ def serve(): Code for starting the XMLRPC service. FIXME: make this HTTPS (see RRS code) and make accompanying Rails changes.. """ - - modules = module_loader.load_modules() - - server =FuncXMLRPCServer(('', 51234)) + server =FuncSSLXMLRPCServer(('', 51234)) server.logRequests = 0 # don't print stuff to console - - websvc = XmlRpcInterface(modules=modules,server=server) - - server.register_instance(websvc) server.serve_forever() -# ====================================================================================== -class FuncXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): + +class FuncXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, XmlRpcInterface): def __init__(self, args): self.allow_reuse_address = True + + self.modules = module_loader.load_modules() SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args) + XmlRpcInterface.__init__(self) + + + +class FuncSSLXMLRPCServer(AuthedXMLRPCServer.AuthedSSLXMLRPCServer, + XmlRpcInterface): + def __init__(self, args): + self.allow_reuse_address = True + # is this right? + self.key = "/etc/pki/func/slave.pem" + self.cert = "/etc/pki/func/slave.cert" + self.ca = "/etc/pki/func/ca/funcmaster.crt" + + self.modules = module_loader.load_modules() + + + XmlRpcInterface.__init__(self) + AuthedXMLRPCServer.AuthedSSLXMLRPCServer.__init__(self, ("", 51234), + self.key, self.cert, + self.ca) + + def _dispatch(self, method, params): + + """ + the SimpleXMLRPCServer class will call _dispatch if it doesn't + find a handler method + """ + + # Recognize ipython's tab completion calls + if method == 'trait_names' or method == '_getAttributeNames': + return self.handlers.keys() + + if hasattr(self, '_this_request'): + r,a = self._this_request + p = r.get_peer_certificate() + cn = p.get_subject().CN + sub_hash = p.subject_name_hash() + else: + print 'no cert' + + # XXX FIXME - need to figure out how to dig into the server base classes + # so we can get client ip, and eventually cert id info -akl + self.audit_logger.log_call(cn, sub_hash, method, params) + + return self.get_dispatch_method(method)(*params) + + def auth_cb(self, request, client_address): + peer_cert = request.get_peer_certificate() + return peer_cert.get_subject().CN -# ====================================================================================== def main(argv): diff --git a/overlord/client.py b/overlord/client.py index 3427e20..e16b198 100755 --- a/overlord/client.py +++ b/overlord/client.py @@ -22,6 +22,9 @@ import traceback import glob import os + +import sslclient + # =================================== # defaults # TO DO: some of this may want to come from config later @@ -33,19 +36,49 @@ FUNC_USAGE = "Usage: %s [ --help ] [ --verbose ] target.example.org module metho # =================================== +class CommandAutomagic(): + """ + This allows a client object to act as if it were one machine, when in + reality it represents many. + """ + + def __init__(self, clientref, base): + self.base = base + self.clientref = clientref + + def __getattr__(self,name): + base2 = self.base[:] + base2.append(name) + return CommandAutomagic(self.clientref, base2) + + def __call__(self, *args): + if not self.base: + raise AttributeError("something wrong here") + if len(self.base) < 2: + raise AttributeError("no method called: %s" % ".".join(self.base)) + module = self.base[0] + method = ".".join(self.base[1:]) + return self.clientref.run(module,method,args) + +# =================================== + class Client(): - def __init__(self, server_spec, port=DEFAULT_PORT, verbose=False, silent=False): + def __init__(self, server_spec, port=DEFAULT_PORT, verbose=False, silent=False, noglobs=False): """ Constructor. - server_spec is something like "*.example.org" or "foosball" - everything else optional and mostly self explanatory. + @server_spec -- something like "*.example.org" or "foosball" + @port -- is the port where all funcd processes should be contacted + @verbose -- whether to print unneccessary things + @silent -- whether to print anything + @noglobs -- specifies server_spec is not a glob, and run should return single values """ self.server_spec = server_spec self.port = port self.verbose = verbose self.silent = silent + self.noglobs = noglobs self.servers = self.expand_servers(self.server_spec) # ----------------------------------------------- @@ -56,6 +89,9 @@ class Client(): of server ids. """ + if self.noglobs: + return [ "https://%s:%s" % (spec, self.port) ] + all_hosts = [] all_certs = [] seperate_gloobs = spec.split(";") @@ -72,28 +108,48 @@ class Client(): all_urls = [] for x in all_hosts: - all_urls.append("http://%s:%s" % (x, self.port)) + all_urls.append("https://%s:%s" % (x, self.port)) if self.verbose and len(all_urls) == 0: sys.stderr.write("no hosts matched\n") return all_urls + # ----------------------------------------------- + + def __getattr__(self, name): + """ + This getattr allows manipulation of the object as if it were + a XMLRPC handle to a single machine, when in reality it is a handle + to an unspecified number of machines. + + So, it enables stuff like this: + + Client("*.example.org").yum.install("foo") + + # WARNING: any missing values in Client's source will yield + # strange errors with this engaged. Be aware of that. + """ + + return CommandAutomagic(self, [name]) + # ----------------------------------------------- def run(self, module, method, args): """ Invoke a remote method on one or more servers. + Run returns a hash, the keys are server names, the values are the returns. + The returns may include exception objects. + If Client() was constructed with noglobs=True, the return is instead just + a single value, not a hash. """ - count = len(self.servers) - results = [] + results = {} for server in self.servers: - # FIXME: add SSL - - conn = xmlrpclib.ServerProxy(server) + conn = sslclient.FuncServer(server) + # conn = xmlrpclib.ServerProxy(server) if self.verbose: sys.stderr.write("on %s running %s %s (%s)\n" % (server, module, method, ",".join(args))) @@ -114,7 +170,13 @@ class Client(): if not self.silent: sys.stderr.write("remote exception on %s: %s\n" % (server, str(e))) - results.append(retval) + if self.noglobs: + return retval + else: + left = server.rfind("/") + right = server.rfind(":") + server_name = server[left:right] + results[server_name] = retval return results @@ -126,17 +188,26 @@ class Client(): and all sorts of crazy stuff, reduce it down to a simple integer return. It may not be useful but we need one. """ - nonzeros = [] - for x in results: + numbers = [] + for x in results.keys(): # faults are the most important if type(x) == Exception: return -911 - # then pay attention to non-zeros + # then pay attention to numbers if type(x) == int: - nonzeros.append(x) - if len(nonzeros) > 0: - return nonzeros[1] - return 0 + numbers.append(x) + + # if there were no numbers, assume 0 + if len(numbers) == 0: + return 0 + + # if there were numbers, return the highest + # (presumably the worst error code + max = -9999 + for x in numbers: + if x > max: + max = x + return max # =================================================================== diff --git a/overlord/sslclient.py b/overlord/sslclient.py index 9439c4a..928d6bb 100644 --- a/overlord/sslclient.py +++ b/overlord/sslclient.py @@ -35,10 +35,25 @@ class SSLXMLRPCServerProxy(xmlrpclib.ServerProxy): xmlrpclib.ServerProxy.__init__(self, uri, SSL_Transport(ssl_context=self.ctx, timeout=timeout)) +class FuncServer(SSLXMLRPCServerProxy): + def __init__(self, uri): + self.pem = "/etc/pki/func/slave.pem" + self.crt = "/etc/pki/func/slave.cert" + self.ca = "/etc/pki/func/ca/funcmaster.crt" + + SSLXMLRPCServerProxy.__init__(self, uri, + self.pem, + self.crt, + self.ca) + if __name__ == "__main__": - s = SSLXMLRPCServerProxy('https://localhost:51234/', '/etc/pki/func/slave.pem', '/etc/pki/func/slave.crt', '/etc/pki/func/ca/funcmaster.crt') + s = SSLXMLRPCServerProxy('https://localhost:51234/', '/etc/pki/func/slave.pem', '/etc/pki/func/slave.cert', '/etc/pki/func/ca/funcmaster.crt') f = s.ping(1, 2) print f + + + + + -
\ No newline at end of file diff --git a/overlord/test_func.py b/overlord/test_func.py index bcce45d..0ee21db 100644 --- a/overlord/test_func.py +++ b/overlord/test_func.py @@ -4,13 +4,23 @@ # FIXME: should import the client lib, not XMLRPC lib, when we are done import xmlrpclib +import sys +import socket +TEST_GETATTR = True TEST_PROCESS = False TEST_VIRT = False TEST_SERVICES = False TEST_HARDWARE = False TEST_SMART = True +if TEST_GETATTR: + import func.overlord.client as func_client + print func_client.Client("*").hardware.info() + print func_client.Client("*").run("hardware","info",[]) + print func_client.Client(socket.gethostname(),silent=True,noglobs=True).test.add("1","2") + sys.exit(1) + # get a connecton (to be replaced by client lib logic) s = xmlrpclib.ServerProxy("http://127.0.0.1:51234") @@ -1 +1 @@ -0.11 4 +0.11 5 |