All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gridkit.vicluster.telecontrol.ssh.ConfigurableSshReplicator Maven / Gradle / Ivy

There is a newer version: 1.0.0-beta
Show newest version
/**
 * Copyright 2012 Alexey Ragozin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gridkit.vicluster.telecontrol.ssh;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.gridkit.vicluster.Hooks;
import org.gridkit.vicluster.ViConfigurable;
import org.gridkit.vicluster.ViNode;
import org.gridkit.vicluster.ViNodeConfig;
import org.gridkit.vicluster.ViNodeProvider;
import org.gridkit.vicluster.WildProps;
import org.gridkit.vicluster.telecontrol.StreamCopyService;
import org.gridkit.vicluster.telecontrol.jvm.JvmNodeProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Alexey Ragozin ([email protected])
 */
public class ConfigurableSshReplicator implements ViNodeProvider {

	private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurableSshReplicator.class);

	private final StreamCopyService streamCopyService;
	private Map sshConfCache = new HashMap();
	private Map sessions = new HashMap();
	private ViNodeConfig defaultConfig = new ViNodeConfig();

	public ConfigurableSshReplicator(StreamCopyService streamCopyService) {
	    this.streamCopyService = streamCopyService;
		RemoteNodeProps.setRemoteJarCachePath(defaultConfig, ".gridagent");
	}

	public ViConfigurable getDefaultConfig() {
		return defaultConfig;
	}
	
	@Override
	public boolean verifyNodeConfig(ViNodeConfig config) {
		// TODO config verification
		return true;
	}

	@Override
	public ViNode createNode(String name, ViNodeConfig config) {
		
		ViNodeConfig effectiveConfig = new ViNodeConfig();
		defaultConfig.apply(effectiveConfig);
		config.apply(effectiveConfig);

		SessionInfo session;

		synchronized(this) {
			
			SshSessionConfig sc = resolveSsh(name, effectiveConfig);
			
			RemoteJmvReplicator proto = getReplicatorProto(sc);
			
			String fp = proto.getFingerPrint();
			session = sessions.get(fp);
			
			if (session == null) {
				session = new SessionInfo();
				session.config = sc;
				sessions.put(fp, session);
			}
		}
		
		synchronized (session) {
			if (session.replicator == null) {
				session.replicator = getReplicatorProto(session.config);
				
				try {
					LOGGER.info("Establishing connection " + session.config.getConnectionSummary());
					session.replicator.init();
				} catch (Exception e) {
					session.replicator = null;
					throw new RuntimeException("SSH connection failed. Host [" + session.config.host + "] Error [" + e.getMessage() + "]", e);
				}
			}
			
			final SessionInfo context = session;
			final ViNode node = new JvmNodeProvider(session.replicator).createNode(name, effectiveConfig);			
			node.setConfigElement("hook:release-ssh", new Hooks.PostShutdownHook(new Runnable() {
				@Override
				public void run() {
					releaseConnection(context, node);
				}
			}));
			session.processes.add(node);
			
			return node;			
		}		
	}

	@Override
	public void shutdown() {
		// TODO implement shutdown()
	}

	private RemoteJmvReplicator getReplicatorProto(SshSessionConfig sc) {
//		RemoteJmvReplicator rep = new LegacySshJvmReplicator();
		RemoteJmvReplicator rep = new TunnellerJvmReplicator(streamCopyService);
		rep.configure(sc.toConfig());
		return rep;
	}

	private void releaseConnection(SessionInfo session, ViNode connection) {
		synchronized(session) {
			session.processes.remove(connection);
			if (session.processes.isEmpty()) {
				LOGGER.info("Session " + session + " is not used");
				session.replicator.dispose();
				session.replicator = null;
			}
			else {
				return;
			}
		}
	}

	private synchronized SshSessionConfig resolveSsh(String name, ViNodeConfig nodeConfig) {
		SshSessionConfig s = new SshSessionConfig();
		s.host = nodeConfig.getProp(RemoteNodeProps.HOST);
		if (s.host == null) {
			throw new IllegalArgumentException("Remote host is not specified for node '" + name + "'");
		}
		if (s.host.startsWith("~")) {
			s.host = transform(s.host, name);
		}
		WildProps sshconf = getConf(nodeConfig.getProp(RemoteNodeProps.SSH_CREDENTIAL_FILE));
		if (sshconf != null) {
			s.account = sshconf.get(s.host);
			s.account = overrideUser(s.account, nodeConfig.getProp(RemoteNodeProps.ACCOUNT));
			s.password = sshconf.get(s.account + "@" + s.host + "!password");
			s.keyFile = sshconf.get(s.account + "@" + s.host + "!private-key");
			s.authMethods = sshconf.get(s.account + "@" + s.host + "!auth-methods");
			String hostOverride = sshconf.get(s.account + "@" + s.host + "!hostname");
			if (hostOverride != null) {
				s.host = hostOverride;
			}
		}
		
		s.account = overrideUser(s.account, nodeConfig.getProp(RemoteNodeProps.ACCOUNT));
		s.password = override(s.password, nodeConfig.getProp(RemoteNodeProps.PASSWORD));
		s.keyFile = override(s.keyFile, nodeConfig.getProp(RemoteNodeProps.SSH_KEY_FILE));
		s.javaExec = override(s.javaExec, nodeConfig.getProp(RemoteNodeProps.JAVA_EXEC));
		s.jarCachePath = override(s.jarCachePath, nodeConfig.getProp(RemoteNodeProps.JAR_CACHE_PATH));
		
		if (s.host == null) {
			throw new IllegalArgumentException("Remote host is not specified for node '" + name + "'");
		}
		
		if (s.account == null) {
			LOGGER.debug("Use default account for [" + name + "]");
			s.account = System.getProperty("user.name");
			if (s.account == null || s.account.trim().length() == 0) {
				throw new IllegalArgumentException("No account found for node '" + name + "'");
			}
		}
		
		if (s.password == null && s.keyFile == null) {
			if (s.account.equals(System.getProperty("user.name"))) {
				LOGGER.debug("Use default SSH keys [" + name + "]");
				s.keyFile = "~/.ssh/id_dsa|~/.ssh/id_rsa";
			}
			else {
				throw new IllegalArgumentException("No creadetials found for node '" + name + "'");
			}
		}

		if (s.javaExec == null) {
			throw new IllegalArgumentException("Java command is not specified for '" + name + "'");
		}

		if (s.jarCachePath == null) {
			throw new IllegalArgumentException("Jar cache location is not specified for '" + name + "'");
		}		
		
		return s;
	}
	
	private String override(String def, String override) {
		if (override != null) {
			return override;
		}
		else {
			return def;
		}
	}

	private String overrideUser(String def, String override) {
		if (override != null) {
			return override;
		}
		else if (def == null){
			return System.getProperty("user.name");
		}
		else {
			return def;
		}
	}

	private synchronized WildProps getConf(String path) {
		if (path == null) {
			return null;
		}
		else {
			if (sshConfCache.containsKey(path)) {
				return sshConfCache.get(path);
			}
			else {
				boolean optional = false;
				if (path.startsWith("?")) {
					optional = true;
					path = path.substring(1);
				}			
				try {
					InputStream is = null;
					if (path.startsWith("~/")) {
						String userHome = System.getProperty("user.home");
						File cpath = new File(new File(userHome), path.substring(2));
						is = new FileInputStream(cpath);
					}
					else if (path.startsWith("resource:")) {
						String rpath = path.substring("resource:".length());
						ClassLoader cl = Thread.currentThread().getContextClassLoader();
						is = cl.getResourceAsStream(rpath);
						if (is == null) {
							throw new FileNotFoundException("Resource not found '" + path + "'");
						}
					}
					else {
						if (new File(path).exists()) {
							is = new FileInputStream(new File(path));
						}
						else {
							try {
								is = new URL(path).openStream();
							}
							catch(IOException e) {
								// ignore
							}
							if (is == null) {
								throw new FileNotFoundException("Cannot resolve path '" + path + "'");
							}
						}
					}
					WildProps wp = new WildProps();
					wp.load(is);
					sshConfCache.put(path, wp);
					return wp;
				} catch (IOException e) {
					if (optional) {
						LOGGER.info("SSH config [" + path + "] is not found");
						sshConfCache.put("?" + path, null);
						return null;
					}
					else {
						throw new RuntimeException(e);
					}
				}
			}
		}
	}

	static String transform(String pattern, String name) {
		int n = pattern.indexOf('!');
		if (n < 0) {
			throw new IllegalArgumentException("Invalid host extractor [" + pattern + "]");
		}
		String format = pattern.substring(1, n);
		Matcher m = Pattern.compile(pattern.substring(n + 1)).matcher(name);
		if (!m.matches()) {
			throw new IllegalArgumentException("Host extractor [" + pattern + "] is not applicable to name '" + name + "'");
		}
		else {
			Object[] groups = new Object[m.groupCount()];
			for(int i = 0; i != groups.length; ++i) {
				groups[i] = m.group(i + 1);
				try {
					groups[i] = new Long((String)groups[i]);
				}
				catch(NumberFormatException e) {
					// ignore
				}				
			}
			try {
				return String.format(format, groups);
			}
			catch(IllegalArgumentException e) {
				throw new IllegalArgumentException("Host extractor [" + pattern + "] is not applicable to name '" + name + "'");
			}
		}
	}
	
	private static class SshSessionConfig {
		
		String host;
		String account;
		String password;
		String keyFile;
		String authMethods;
		String javaExec;
		String jarCachePath;
		
		public Map toConfig() {
			Map config = new HashMap();
			config.put(RemoteNodeProps.HOST, host);
			config.put(RemoteNodeProps.ACCOUNT, account);
			config.put(RemoteNodeProps.PASSWORD, password);
			config.put(RemoteNodeProps.SSH_KEY_FILE, keyFile);
			config.put(RemoteNodeProps.SSH_AUTH_METHODS, authMethods);
			config.put(RemoteNodeProps.JAVA_EXEC, javaExec);
			config.put(RemoteNodeProps.JAR_CACHE_PATH, jarCachePath);
			return config;
		}
		
		public String getConnectionSummary() {
			return account + "@" + host + " - " 
					+ (keyFile == null ? "" : " pk-auth(" + keyFile +")") 
					+ (password == null ? "" : " password-auth");
		}
		
		public String toString() {
			return host + "|" + account + "|" + password + "|" + keyFile + "|" + javaExec + "|" + jarCachePath;
		}		
	}
	
	private static class SessionInfo {
		SshSessionConfig config;
		RemoteJmvReplicator replicator;
		List processes = new ArrayList();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy