summaryrefslogtreecommitdiffstats
path: root/func/overlord/client.py
diff options
context:
space:
mode:
authorDevan Goodwin <dgoodwin@dangerouslyinc.com>2007-10-02 21:42:47 -0300
committerJames Bowes <jbowes@redhat.com>2007-10-02 21:33:49 -0400
commit1ce955ec36f775d8fde2cb9d7943178e8b9d60da (patch)
tree4c69d218fde87091d4e5d1f3138a435b9164dbf4 /func/overlord/client.py
parent3c13a4f30f247f4aa75c02c65e6bb6e575e30d01 (diff)
downloadthird_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.tar.gz
third_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.tar.xz
third_party-func-1ce955ec36f775d8fde2cb9d7943178e8b9d60da.zip
Moved code under the func namespace.
Previously we had overlord, minion, modules, and func all at the root of the source tree. After install these would all be shuffled below func. Relocated them in the source tree to reflect this.
Diffstat (limited to 'func/overlord/client.py')
-rwxr-xr-xfunc/overlord/client.py295
1 files changed, 295 insertions, 0 deletions
diff --git a/func/overlord/client.py b/func/overlord/client.py
new file mode 100755
index 0000000..3c60148
--- /dev/null
+++ b/func/overlord/client.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+
+##
+## func command line interface & client lib
+##
+## Copyright 2007, Red Hat, Inc
+## Michael DeHaan <mdehaan@redhat.com>
+## +AUTHORS
+##
+## This software may be freely redistributed under the terms of the GNU
+## general public license.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+##
+
+import optparse
+import sys
+import glob
+import pprint
+
+from func.commonconfig import CMConfig
+from func.config import read_config, CONFIG_FILE
+import sslclient
+
+import command
+
+# ===================================
+# defaults
+# TO DO: some of this may want to come from config later
+
+DEFAULT_PORT = 51234
+FUNC_USAGE = "Usage: %s [ --help ] [ --verbose ] target.example.org module method arg1 [...]"
+
+# ===================================
+
+class CommandAutomagic(object):
+ """
+ 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(object):
+
+ def __init__(self, server_spec, port=DEFAULT_PORT, interactive=False,
+ verbose=False, noglobs=False, config=None):
+ """
+ Constructor.
+ @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
+ @noglobs -- specifies server_spec is not a glob, and run should return single values
+ @config -- optional config object
+ """
+ self.config = config
+ if config is None:
+ self.config = read_config(CONFIG_FILE, CMConfig)
+
+ self.server_spec = server_spec
+ self.port = port
+ self.verbose = verbose
+ self.interactive = interactive
+ self.noglobs = noglobs
+ self.servers = self.expand_servers(self.server_spec)
+
+ # default cert/ca/key is the same as the certmaster ca - need to
+ # be able to change that on the cli
+ self.key = '%s/funcmaster.key' % self.config.cadir
+ self.cert = '%s/funcmaster.crt' % self.config.cadir
+ # yes, they're the same, that's the point
+ self.ca = '%s/funcmaster.crt' % self.config.cadir
+
+ # -----------------------------------------------
+
+ def expand_servers(self,spec):
+ """
+ Given a regex/blob of servers, expand to a list
+ of server ids.
+ """
+
+ if self.noglobs:
+ return [ "https://%s:%s" % (spec, self.port) ]
+
+ all_hosts = []
+ all_certs = []
+ seperate_gloobs = spec.split(";")
+ for each_gloob in seperate_gloobs:
+ actual_gloob = "%s/%s.cert" % (self.config.certroot, each_gloob)
+ certs = glob.glob(actual_gloob)
+ for cert in certs:
+ all_certs.append(cert)
+ host = cert.replace(self.config.certroot,"")[1:-5]
+ all_hosts.append(host)
+
+ all_urls = []
+ for x in all_hosts:
+ 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.
+ """
+
+ results = {}
+
+ for server in self.servers:
+
+ conn = sslclient.FuncServer(server, self.key, self.cert, self.ca )
+ # conn = xmlrpclib.ServerProxy(server)
+
+ if self.interactive:
+ sys.stderr.write("on %s running %s %s (%s)\n" % (server,
+ module, method, ",".join(args)))
+
+ # FIXME: support userland command subclassing only if a module
+ # is present, otherwise run as follows. -- MPD
+
+ try:
+ # thats some pretty code right there aint it? -akl
+ # we can't call "call" on s, since thats a rpc, so
+ # we call gettatr around it.
+ meth = "%s.%s" % (module, method)
+ retval = getattr(conn, meth)(*args[:])
+ if self.interactive:
+ pprint.pprint(retval)
+ except Exception, e:
+ retval = e
+ if self.interactive:
+ sys.stderr.write("remote exception on %s: %s\n" %
+ (server, str(e)))
+
+ if self.noglobs:
+ return retval
+ else:
+ left = server.rfind("/")+1
+ right = server.rfind(":")
+ server_name = server[left:right]
+ results[server_name] = retval
+
+ return results
+
+ # -----------------------------------------------
+
+ def cli_return(self,results):
+ """
+ As the return code list could return strings and exceptions
+ and all sorts of crazy stuff, reduce it down to a simple
+ integer return. It may not be useful but we need one.
+ """
+ numbers = []
+ for x in results.keys():
+ # faults are the most important
+ if type(x) == Exception:
+ return -911
+ # then pay attention to numbers
+ if type(x) == int:
+ 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
+
+# ===================================================================
+
+class Call(command.Command):
+ name = "call"
+ useage = "call nodule method name arg1 arg2..."
+ def addOptions(self):
+ self.parser.add_option("-v", "--verbose", dest="verbose",
+ action="store_true")
+ self.parser.add_option("-p", "--port", dest="port",
+ default=DEFAULT_PORT)
+
+ def handleOptions(self, options):
+ self.options = options
+
+ self.verbose = options.verbose
+ self.port = options.port
+ # I'm not really a fan of the "module methodname" approach
+ # but we'll keep it for now -akl
+
+ def do(self, args):
+
+ # I'm not really a fan of the "module methodname" approach
+ # but we'll keep it for now -akl
+
+ self.server_spec = args[0]
+ self.module = args[1]
+ self.method = args[2]
+ self.method_args = args[3:]
+
+ client = Client(self.server_spec,port=self.port,interactive=True,
+ verbose=self.verbose, config=self.config)
+ results = client.run(self.module, self.method, self.method_args)
+
+ # TO DO: add multiplexer support
+ # probably as a higher level module.
+
+ return client.cli_return(results)
+
+class FuncCommandLine(command.Command):
+ name = "client"
+ useage = "func is the commandline interface to a func minion"
+
+ subCommandClasses = [Call]
+
+ def __init__(self):
+
+ command.Command.__init__(self)
+
+ def do(self, args):
+ pass
+
+ def addOptions(self):
+ self.parser.add_option('', '--version', action="store_true",
+ help="show version information")
+ self.parser.add_option("--list-minions", dest="list_minions",
+ action="store_true", help="list all available minions")
+
+ def handleOptions(self, options):
+ if options.version:
+ #FIXME
+ print "version is NOT IMPLEMENTED YET"
+ if options.list_minions:
+ self.list_minions()
+
+ sys.exit(0) # stop execution
+
+ def list_minions(self):
+ print "Minions:"
+ gloob = "%s/%s.cert" % (self.config.certroot, "*")
+ certs = glob.glob(gloob)
+ for cert in certs:
+ host = cert.replace(self.config.certroot, "")[1:-5]
+ print " %s" % host