changeset 1317:b8b5934f7977

Agent Proxy client This patch implements the client side of the Agent Proxy that integrates into Thermostat. The Agent's activator now registers an AgentProxyFilter with the VmBlacklist service from the previous patch. This Filter causes Thermostat to not monitor the Agent Proxy process for reasons outlined in the previous email. The bulk of the functionality is in the AgentProxyClient class, which is responsible for creating the Agent Proxy process and communicating with it using RMI. This class performs a sort of handshake with the Agent Proxy server, using the AgentProxyListener interface, in order to determine when the server is up and exported to the RMI registry. Once the server is available, the client retrieves its AgentProxyLogin interface and attempts to log in. If the login is successful, an AgentProxyControl interface is returned that allows the client to perform privileged operations on the server. These operations are: attach, isAttached, getConnectorAddress, and detach. The MXBeanConnector class now delegates much of its previous functionality to the AgentProxyClient class. The MXBeanConnectionPoolImpl class now manages a new RMIRegistry class, which encapsulates functionality like creating the registry, exporting and unexporting objects. Typically these are done with static methods, so this RMIRegistry class makes testing much easier, in addition to managing the lifecycle of the actual RMI registry. The RMI registry is created with a custom ServerSocket factory that binds to the loopback address, making sure only local connections are permitted. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-November/008704.html PR1460
author Elliott Baron <ebaron@redhat.com>
date Thu, 14 Nov 2013 11:30:11 -0500
parents f9acec89f53f
children 247cd7edff41
files agent/core/pom.xml agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/AgentProxyClient.java agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/AgentProxyFilter.java agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnectionPoolImpl.java agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnector.java agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/RMIRegistry.java agent/pom.xml agent/proxy/common/pom.xml agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyControl.java agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyListener.java agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyLogin.java agent/proxy/pom.xml config/src/main/java/com/redhat/thermostat/shared/config/Configuration.java
diffstat 14 files changed, 832 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/agent/core/pom.xml	Thu Nov 14 11:28:44 2013 -0500
+++ b/agent/core/pom.xml	Thu Nov 14 11:30:11 2013 -0500
@@ -75,6 +75,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-agent-proxy-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-launcher</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -85,6 +90,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-osgi-process-handler</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common-test</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java	Thu Nov 14 11:28:44 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java	Thu Nov 14 11:30:11 2013 -0500
@@ -41,22 +41,36 @@
 
 import com.redhat.thermostat.agent.VmBlacklist;
 import com.redhat.thermostat.utils.management.MXBeanConnectionPool;
+import com.redhat.thermostat.utils.management.internal.AgentProxyFilter;
 import com.redhat.thermostat.utils.management.internal.MXBeanConnectionPoolImpl;
 import com.redhat.thermostat.utils.username.UserNameUtil;
 import com.redhat.thermostat.utils.username.internal.UserNameUtilImpl;
 
 public class Activator implements BundleActivator {
+    
+    private final MXBeanConnectionPoolImpl pool;
+    
+    public Activator() {
+        this(new MXBeanConnectionPoolImpl());
+    }
+    
+    Activator(MXBeanConnectionPoolImpl pool) {
+        this.pool = pool;
+    }
 
     @Override
     public void start(BundleContext context) throws Exception {
-        context.registerService(MXBeanConnectionPool.class, new MXBeanConnectionPoolImpl(), null);
+        context.registerService(MXBeanConnectionPool.class, pool, null);
         context.registerService(UserNameUtil.class, new UserNameUtilImpl(), null);
-        context.registerService(VmBlacklist.class, new VmBlacklistImpl(), null);
+        VmBlacklistImpl blacklist = new VmBlacklistImpl();
+        blacklist.addVmFilter(new AgentProxyFilter());
+        context.registerService(VmBlacklist.class, blacklist, null);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
         // Services automatically unregistered by framework
+        pool.shutdown();
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/AgentProxyClient.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.utils.management.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.Registry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.proxy.common.AgentProxyControl;
+import com.redhat.thermostat.agent.proxy.common.AgentProxyListener;
+import com.redhat.thermostat.agent.proxy.common.AgentProxyLogin;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.utils.LoggedExternalProcess;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.Configuration;
+
+class AgentProxyClient implements AgentProxyListener {
+    
+    private static final long SERVER_TIMEOUT_MS = 5000L;
+    private static final String SERVER_NAME = "thermostat-agent-proxy";
+    private static final Logger logger = LoggingUtils.getLogger(AgentProxyClient.class);
+    
+    private final RMIRegistry registry;
+    private final int pid;
+    private final ProcessCreator procCreator;
+    private final Configuration config;
+    private final CountDownLatch started;
+    
+    private AgentProxyControl proxy;
+    private Exception serverError;
+    
+    AgentProxyClient(RMIRegistry registry, int pid) {
+        this(registry, pid, new Configuration(), new CountDownLatch(1), 
+                new ProcessCreator());
+    }
+
+    AgentProxyClient(RMIRegistry registry, int pid, Configuration config,
+            CountDownLatch started, ProcessCreator procCreator) {
+        this.registry = registry;
+        this.pid = pid;
+        this.config = config;
+        this.started = started;
+        this.procCreator = procCreator;
+    }
+
+    void createProxy() throws IOException, ApplicationException {
+        // Export our listener
+        AgentProxyListener stub = (AgentProxyListener) registry.export(this);
+        String listenerName = REMOTE_PREFIX + String.valueOf(pid);
+        Registry reg = registry.getRegistry();
+        reg.rebind(listenerName, stub);
+        logger.fine("Registered proxy listener for " + pid);
+
+        // Start the agent proxy, and wait until it exports itself
+        try {
+            startProcess();
+        } finally {
+            // Got started event or timed out, unregister our listener
+            try {
+                reg.unbind(listenerName);
+                registry.unexport(this);
+            } catch (NotBoundException e) {
+                throw new RemoteException("Error unregistering listener", e);
+            }
+        }
+
+        // Check if server started successfully
+        if (serverError != null) {
+            throw new RemoteException("Server failed to start", serverError);
+        }
+
+        // Lookup server
+        String serverName = AgentProxyLogin.REMOTE_PREFIX + String.valueOf(pid);
+        try {
+            // Need to authenticate in order to obtain proxy object
+            AgentProxyLogin proxyLogin = (AgentProxyLogin) reg.lookup(serverName);
+            proxy = proxyLogin.login();
+        } catch (NotBoundException e) {
+            throw new RemoteException("Unable to find remote interface", e);
+        }
+    }
+
+    private void startProcess() throws IOException, ApplicationException {
+        String serverPath = config.getSystemBinRoot() + File.separator + SERVER_NAME;
+        procCreator.createAndRunProcess(new String[] { serverPath, String.valueOf(pid) });
+        try {
+            boolean result = started.await(SERVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            if (!result) {
+                throw new RemoteException("Timeout while waiting for server");
+            }
+        } catch (InterruptedException e) {
+            // Restore interrupted status
+            Thread.currentThread().interrupt();
+        }
+    }
+    
+    void attach() throws RemoteException {
+        proxy.attach();
+    }
+    
+    boolean isAttached() throws RemoteException {
+        return proxy.isAttached();
+    }
+    
+    String getConnectorAddress() throws RemoteException {
+        return proxy.getConnectorAddress();
+    }
+    
+    void detach() throws RemoteException {
+        proxy.detach();
+    }
+    
+    @Override
+    public void serverStarted() throws RemoteException {
+        started.countDown();
+    }
+
+    @Override
+    public void serverFailedToStart(Exception error) throws RemoteException {
+        serverError = error;
+        started.countDown();
+    }
+    
+    /*
+     * For testing purposes only.
+     */
+    AgentProxyControl getProxy() {
+        return proxy;
+    }
+    
+    static class ProcessCreator {
+        Process createAndRunProcess(String[] args) throws IOException, ApplicationException {
+            LoggedExternalProcess process = new LoggedExternalProcess(args);
+            return process.runAndReturnProcess();
+        }
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/AgentProxyFilter.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.utils.management.internal;
+
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.storage.core.VmRef;
+
+/**
+ * Prevents Agent Proxies from being monitored, which would create
+ * an infinite chain of agent proxies being created.
+ */
+public class AgentProxyFilter extends Filter<VmRef> {
+    
+    private static final String AGENT_PROXY_CLASS = "com.redhat.thermostat.agent.proxy.server.AgentProxy";
+
+    @Override
+    public boolean matches(VmRef toMatch) {
+        String mainClass = toMatch.getName();
+        return AGENT_PROXY_CLASS.equals(mainClass);
+    }
+
+}
--- a/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnectionPoolImpl.java	Thu Nov 14 11:28:44 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnectionPoolImpl.java	Thu Nov 14 11:30:11 2013 -0500
@@ -36,37 +36,65 @@
 
 package com.redhat.thermostat.utils.management.internal;
 
+import java.io.IOException;
+import java.rmi.RemoteException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import com.redhat.thermostat.common.Pair;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.utils.management.MXBeanConnection;
 import com.redhat.thermostat.utils.management.MXBeanConnectionPool;
 
 public class MXBeanConnectionPoolImpl implements MXBeanConnectionPool {
 
+    private static final Logger logger = LoggingUtils.getLogger(MXBeanConnectionPoolImpl.class);
+    
     // pid -> (usageCount, actualObject)
     private Map<Integer, Pair<Integer, MXBeanConnectionImpl>> pool = new HashMap<>();
 
-    private ConnectorCreator creator;
+    private final ConnectorCreator creator;
+    private final RMIRegistry registry;
 
     public MXBeanConnectionPoolImpl() {
-        this(new ConnectorCreator());
+        this(new ConnectorCreator(), new RMIRegistry());
+    }
+    
+    // For testing purposes only
+    public MXBeanConnectionPoolImpl(RMIRegistry registry) {
+        this(new ConnectorCreator(), registry);
     }
 
-    MXBeanConnectionPoolImpl(ConnectorCreator connectorCreator) {
+    MXBeanConnectionPoolImpl(ConnectorCreator connectorCreator, RMIRegistry registry) {
         this.creator = connectorCreator;
+        this.registry = registry;
+        
+        // Start RMI registry
+        try {
+            registry.start();
+        } catch (RemoteException e) {
+            logger.log(Level.SEVERE, "Unable to start RMI registry", e);
+        }
     }
 
     @Override
     public synchronized MXBeanConnection acquire(int pid) throws Exception {
         Pair<Integer, MXBeanConnectionImpl> data = pool.get(pid);
         if (data == null) {
-            MXBeanConnector connector = creator.create(pid);
-            connector.attach();
-            MXBeanConnectionImpl connection = connector.connect();
-            connector.close();
-            data = new Pair<Integer, MXBeanConnectionImpl>(1, connection);
+            MXBeanConnector connector = null;
+            try {
+                connector = creator.create(registry, pid);
+                connector.attach();
+                MXBeanConnectionImpl connection = connector.connect();
+                data = new Pair<Integer, MXBeanConnectionImpl>(1, connection);
+            } finally {
+                if (connector != null) {
+                    connector.close();
+                }
+            }
         } else {
             data = new Pair<>(data.getFirst() + 1, data.getSecond());
         }
@@ -88,10 +116,19 @@
             pool.put(pid, data);
         }
     }
+    
+    public void shutdown() {
+        try {
+            registry.stop();
+        } catch (RemoteException e) {
+            logger.log(Level.SEVERE, "Unable to stop RMI registry", e);
+        }
+    }
 
     static class ConnectorCreator {
-        public MXBeanConnector create(int pid) {
-            return new MXBeanConnector(pid);
+        public MXBeanConnector create(RMIRegistry registry, int pid) throws IOException, ApplicationException {
+            MXBeanConnector connector = new MXBeanConnector(registry, pid);
+            return connector;
         }
     }
 }
--- a/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnector.java	Thu Nov 14 11:28:44 2013 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/MXBeanConnector.java	Thu Nov 14 11:30:11 2013 -0500
@@ -37,64 +37,38 @@
 package com.redhat.thermostat.utils.management.internal;
 
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
-import java.util.Properties;
+import java.rmi.RemoteException;
 
 import javax.management.MBeanServerConnection;
 import javax.management.remote.JMXConnector;
 import javax.management.remote.JMXConnectorFactory;
 import javax.management.remote.JMXServiceURL;
 
-import com.redhat.thermostat.storage.core.VmRef;
-import com.sun.tools.attach.VirtualMachine;
+import com.redhat.thermostat.common.tools.ApplicationException;
 
 class MXBeanConnector implements Closeable {
-
-    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
-    private String connectorAddress;
-    
-    private VirtualMachine vm;
     
-    private boolean attached;
+    private final AgentProxyClient client;
+    private final JMXConnectionCreator jmxCreator;
     
-    private int pid;
-    
-    public MXBeanConnector(int pid) {
-        this.pid = pid;
+    public MXBeanConnector(RMIRegistry registry, int pid) throws IOException, ApplicationException {
+        this(new AgentProxyClient(registry, pid), new JMXConnectionCreator());
     }
     
-    public MXBeanConnector(VmRef reference) {
-        this.pid = reference.getPid();
+    MXBeanConnector(AgentProxyClient client, JMXConnectionCreator jmxCreator) throws IOException, ApplicationException {
+        this.client = client;
+        this.jmxCreator = jmxCreator;
+        client.createProxy();
     }
     
     public synchronized void attach() throws Exception {
-        if (attached)
-            throw new IOException("Already attached");
-        
-        vm = VirtualMachine.attach(String.valueOf(pid));
-        attached = true;
-        
-        Properties props = vm.getAgentProperties();
-        connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-        if (connectorAddress == null) {
-           props = vm.getSystemProperties();
-           String home = props.getProperty("java.home");
-           String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
-           vm.loadAgent(agent);
-           
-           props = vm.getAgentProperties();
-           connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-        }
+        client.attach();
     }
     
     public synchronized MXBeanConnectionImpl connect() throws IOException {
-        
-        if (!attached)
-            throw new IOException("Agent not attached to target VM");
-        
-        JMXServiceURL url = new JMXServiceURL(connectorAddress);
-        JMXConnector connection = JMXConnectorFactory.connect(url);
+        JMXServiceURL url = new JMXServiceURL(client.getConnectorAddress());
+        JMXConnector connection = jmxCreator.create(url);
         MBeanServerConnection mbsc = null;
         try {
             mbsc = connection.getMBeanServerConnection();
@@ -107,15 +81,18 @@
         return new MXBeanConnectionImpl(connection, mbsc);
     }
     
-    public boolean isAttached() {
-        return attached;
+    public boolean isAttached() throws RemoteException {
+        return client.isAttached();
     }
     
     @Override
     public synchronized void close() throws IOException {
-        if (attached) {
-            vm.detach();
-            attached = false;
+        client.detach();
+    }
+
+    static class JMXConnectionCreator {
+        JMXConnector create(JMXServiceURL url) throws IOException {
+            return JMXConnectorFactory.connect(url);
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/management/internal/RMIRegistry.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.utils.management.internal;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.NoSuchObjectException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class RMIRegistry {
+    
+    private static final Logger logger = LoggingUtils.getLogger(RMIRegistry.class);
+
+    private RegistryCreator registryCreator;
+    private ServerSocketCreator serverSockCreator;
+    private Registry registry;
+    
+    public RMIRegistry() {
+        this(new RegistryCreator(), new ServerSocketCreator());
+    }
+    
+    RMIRegistry(RegistryCreator registryCreator, ServerSocketCreator serverSockCreator) {
+        this.registryCreator = registryCreator;
+        this.serverSockCreator = serverSockCreator;
+    }
+    
+    public void start() throws RemoteException {
+        this.registry = registryCreator.createRegistry(Registry.REGISTRY_PORT /* TODO customize */,
+                RMISocketFactory.getDefaultSocketFactory(),
+                new RMIServerSocketFactory() {
+                    
+                    @Override
+                    public ServerSocket createServerSocket(int port) throws IOException {
+                        // Allow only local connections
+                        return serverSockCreator.createSocket(port, 0, InetAddress.getLoopbackAddress());
+                    }
+                });
+        logger.fine("Starting RMI registry");
+    }
+    
+    public Registry getRegistry() throws RemoteException {
+        // We get a class loading problem when returning the local registry reference,
+        // this returns a remote stub reference instead
+        return registryCreator.getRegistry();
+    }
+    
+    public void stop() throws RemoteException {
+        if (registry != null) {
+            registryCreator.destroyRegistry(registry);
+            registry = null;
+            logger.fine("Shutting down RMI registry");
+        }
+    }
+    
+    public Remote export(Remote obj) throws RemoteException {
+        if (registry == null) {
+            throw new RemoteException("RMI registry is not running");
+        }
+        return UnicastRemoteObject.exportObject(obj, 0);
+    }
+    
+    public void unexport(Remote obj) throws RemoteException {
+        if (registry == null) {
+            throw new RemoteException("RMI registry is not running");
+        }
+        UnicastRemoteObject.unexportObject(obj, true);
+    }
+    
+    /*
+     * For testing purposes only.
+     */
+    Registry getRegistryImpl() {
+        return registry;
+    }
+    
+    static class RegistryCreator {
+        Registry createRegistry(int port, RMIClientSocketFactory csf,
+                RMIServerSocketFactory ssf) throws RemoteException {
+            return LocateRegistry.createRegistry(port, csf, ssf);
+        }
+        
+        Registry getRegistry() throws RemoteException {
+            return LocateRegistry.getRegistry(InetAddress.getLoopbackAddress().getHostName());
+        }
+        
+        void destroyRegistry(Registry registry) throws NoSuchObjectException {
+            // Shuts down RMI registry
+            UnicastRemoteObject.unexportObject(registry, true);
+        }
+    }
+    
+    static class ServerSocketCreator {
+        ServerSocket createSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
+            return new ServerSocket(port, backlog, bindAddr);
+        }
+    }
+
+}
--- a/agent/pom.xml	Thu Nov 14 11:28:44 2013 -0500
+++ b/agent/pom.xml	Thu Nov 14 11:30:11 2013 -0500
@@ -62,6 +62,7 @@
     <module>cli</module>
     <module>core</module>
     <module>command</module>
+    <module>proxy</module>
   </modules>
 
 </project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/proxy/common/pom.xml	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012, 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-agent-proxy</artifactId>
+    <version>0.16.0-SNAPSHOT</version>
+  </parent>
+  
+  <artifactId>thermostat-agent-proxy-common</artifactId>
+  <name>Thermostat Agent Proxy Common</name>
+  <packaging>bundle</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-SymbolicName>com.redhat.thermostat.agent.proxy.common</Bundle-SymbolicName>
+            <Export-Package>
+              com.redhat.thermostat.agent.proxy.common,
+            </Export-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.sun</groupId>
+      <artifactId>tools</artifactId>
+      <scope>system</scope>
+      <systemPath>${java.home}/../lib/tools.jar</systemPath>
+    </dependency>  
+  </dependencies>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyControl.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.proxy.common;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import com.sun.tools.attach.VirtualMachine;
+
+/**
+ * Remote interface to allow control of a JVM using the Hotspot attach
+ * mechanism.
+ * 
+ * This interface invokes remote methods of a delegate Java process
+ * which acts as a proxy between Thermostat and the target JVM. This
+ * delegate is necessary in order to assume the same user and group IDs
+ * as the target JVM.
+ */
+public interface AgentProxyControl extends Remote {
+    
+    /**
+     * Attach to the target JVM using {@link VirtualMachine#attach}.
+     * @throws RemoteException if the attach fails
+     */
+    void attach() throws RemoteException;
+    
+    /**
+     * @return whether the delegate is currently attached to the target
+     * JVM.
+     * @throws RemoteException if this method fails for any reason
+     */
+    boolean isAttached() throws RemoteException;
+    
+    /**
+     * @return an address that can be used to establish a JMX connection
+     * to the target JVM.
+     * @throws RemoteException if the delegate is not attached to the target
+     * VM
+     */
+    String getConnectorAddress() throws RemoteException;
+    
+    /**
+     * Detaches from the target JVM that was attached previously using 
+     * {@link #attach()}, and terminates the remote connection to the
+     * delegate Java process.
+     * @throws RemoteException if the delegate failed to detach from the VM,
+     * or failed to terminate the remote connection
+     */
+    void detach() throws RemoteException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyListener.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.proxy.common;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote interface to allow an implementer of {@link AgentProxyControl}
+ * to notify clients when it is available.
+ */
+public interface AgentProxyListener extends Remote {
+    
+    /**
+     * By appending the PID of the target JVM, forms the name of
+     * the exported remote object implementing this interface for
+     * that JVM.
+     */
+    public static final String REMOTE_PREFIX = "AgentProxyListener";
+    
+    /**
+     * To be called when an implementer of {@link AgentProxyControl}
+     * has exported itself for use by clients.
+     * @throws RemoteException if this method fails for any reason
+     */
+    void serverStarted() throws RemoteException;
+    
+    /**
+     * To be called when an implementer of {@link AgentProxyControl}
+     * has failed to start.
+     * @param error - the cause of the failure
+     * @throws RemoteException if this method fails for any reason
+     */
+    void serverFailedToStart(Exception error) throws RemoteException;
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/proxy/common/src/main/java/com/redhat/thermostat/agent/proxy/common/AgentProxyLogin.java	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.proxy.common;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface AgentProxyLogin extends Remote {
+    
+    /**
+     * By appending the PID of the target JVM, forms the name of
+     * the exported remote object implementing this interface for
+     * that JVM.
+     */
+    public static final String REMOTE_PREFIX = "AgentProxy";
+
+    /**
+     * Authenticates using the currently logged in system user, and
+     * if successful, returns an object to control the agent proxy.
+     * @return a control object for the agent proxy
+     * @throws RemoteException if this method fails for some reason
+     * @throws SecurityException if the currently logged in user
+     * is not authorized to log in
+     */
+    AgentProxyControl login() throws RemoteException, SecurityException;
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/proxy/pom.xml	Thu Nov 14 11:30:11 2013 -0500
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012, 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-agent</artifactId>
+    <version>0.16.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-agent-proxy</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Thermostat Agent Proxy</name>
+
+  <modules>
+    <module>common</module>
+    <module>server</module>
+  </modules>
+
+</project>
+
--- a/config/src/main/java/com/redhat/thermostat/shared/config/Configuration.java	Thu Nov 14 11:28:44 2013 -0500
+++ b/config/src/main/java/com/redhat/thermostat/shared/config/Configuration.java	Thu Nov 14 11:30:11 2013 -0500
@@ -132,6 +132,10 @@
     public File getSystemLibRoot() throws InvalidConfigurationException {
         return new File(home, "libs");
     }
+    
+    public File getSystemBinRoot() throws InvalidConfigurationException {
+        return new File(home, "bin");
+    }
 
     public File getSystemNativeLibsRoot() throws InvalidConfigurationException {
         return new File(getSystemLibRoot(), "native");