summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSeth Vidal <skvidal@fedoraproject.org>2007-09-26 16:13:58 -0400
committerSeth Vidal <skvidal@fedoraproject.org>2007-09-26 16:13:58 -0400
commit7dc50faf68fd3d20691263792dae609ec1cee13b (patch)
treece51d02c2415fbf37ddff1e9ce90b66ca57dc9d0
parentd535d52a58ae719aa1b5c60e7d8febacdaf8cebd (diff)
parent835b143c095aa7bd09ed050790a331e152599105 (diff)
downloadfunc-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/.gitignore2
-rw-r--r--func.spec4
-rwxr-xr-xminion/logger.py4
-rwxr-xr-xminion/server.py95
-rwxr-xr-xoverlord/client.py105
-rw-r--r--overlord/sslclient.py19
-rw-r--r--overlord/test_func.py10
-rw-r--r--version2
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
diff --git a/func.spec b/func.spec
index 07b34c2..a81f207 100644
--- a/func.spec
+++ b/func.spec
@@ -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")
diff --git a/version b/version
index 325e5e2..6cd8779 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.11 4
+0.11 5