Index: trunk/packages/invirt-remote/python/remote/__init__.py
===================================================================
--- trunk/packages/invirt-remote/python/remote/__init__.py	(revision 2045)
+++ trunk/packages/invirt-remote/python/remote/__init__.py	(revision 2495)
@@ -1,1 +1,2 @@
 from bcast import bcast
+from monocast import monocast
Index: trunk/packages/invirt-remote/python/remote/monocast.py
===================================================================
--- trunk/packages/invirt-remote/python/remote/monocast.py	(revision 2495)
+++ trunk/packages/invirt-remote/python/remote/monocast.py	(revision 2495)
@@ -0,0 +1,34 @@
+from subprocess import PIPE, Popen
+from invirt.config import structs as config
+import random
+
+hostnames = [ h.hostname for h in config.hosts ]
+
+def monocast(args, hosts = hostnames, randomize = True):
+    """
+    Given a command and a list of hostnames or IPs, issue the command to each
+    node until it connects to one of the nodes.
+
+    Returns:
+        host the command ran on
+        hosts that could not be contacted
+        returncode of remctl
+        stdout of remctl
+        stderr of remctl
+    """
+    if(randomize):
+        hosts = random.sample(hosts, len(hosts))
+    hostsdown = []
+    for host in hosts:
+        pipe = Popen(['remctl', host, 'remote', 'web'] + args, stdout=PIPE, stderr=PIPE)
+        output = pipe.communicate()
+        if pipe.returncode != 0:
+            if output[1].startswith('remctl: cannot connect to %s' % host):
+                hostsdown.append(host)
+            else:
+                #raise RuntimeError("remctl to host %s returned non-zero exit status %d; stderr:\n%s"
+                #                   % (host, pipe.returncode, output[1]))
+                return (host, hostsdown, pipe.returncode,) + output
+        else:
+            return (host, hostsdown, pipe.returncode,) + output
+    raise RuntimeError("Failed to contact any hosts: tried %s" % (hostsdown, ))
