io.fabric8.jube.process.service.ProcessManagerService Maven / Gradle / Ivy
/**
* Copyright 2005-2014 Red Hat, Inc.
*
* Red Hat licenses this file to you 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 io.fabric8.jube.process.service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.fabric8.jube.process.DownloadStrategy;
import io.fabric8.jube.process.InstallContext;
import io.fabric8.jube.process.InstallOptions;
import io.fabric8.jube.process.InstallTask;
import io.fabric8.jube.process.Installation;
import io.fabric8.jube.process.ProcessController;
import io.fabric8.jube.process.config.ConfigHelper;
import io.fabric8.jube.process.config.ProcessConfig;
import io.fabric8.jube.process.support.DefaultProcessController;
import io.fabric8.jube.process.support.command.Duration;
import io.fabric8.jube.util.FilesHelper;
import io.fabric8.jube.util.InstallHelper;
import io.fabric8.utils.Objects;
import io.fabric8.utils.Strings;
import io.fabric8.utils.Zips;
import io.hawt.aether.OpenMavenURL;
import org.apache.deltaspike.core.api.config.ConfigProperty;
import org.apache.deltaspike.core.api.jmx.JmxManaged;
import org.apache.deltaspike.core.api.jmx.MBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.io.ByteStreams.copy;
import static io.fabric8.jube.process.support.ProcessUtils.findInstallDir;
@Singleton
@MBean(objectName = "io.fabric8.jube:type=LocalProcesses", description = "Manages local processes on this node")
public class ProcessManagerService implements ProcessManagerServiceMBean {
public static final String DEFAULT_MAVEN_REPOS = "http://repo1.maven.org/maven2@id=central,http://repository.jboss.org/nexus/content/groups/public@id=jboss-public";
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessManagerService.class);
private static final String INSTALLED_BINARY = "install.bin";
private final String remoteRepositoryUrls;
private final AvailablePortFinder availablePortFinder;
private Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("jube-process-manager-%s").build());
private File storageLocation;
private int lastId;
private final Duration untarTimeout = Duration.valueOf("1h");
private final Duration postUnpackTimeout = Duration.valueOf("1h");
private final Duration postInstallTimeout = Duration.valueOf("1h");
private SortedMap installations = Maps.newTreeMap();
private MBeanServer mbeanServer;
private boolean isWindows;
@Inject
public ProcessManagerService(@ConfigProperty(name = "JUBE_PROCESS_DIR", defaultValue = "./processes") String storageLocation,
@ConfigProperty(name = "JUBE_REMOTE_MAVEN_REPOS", defaultValue = DEFAULT_MAVEN_REPOS) String remoteRepositoryUrls,
@ConfigProperty(name = "JUBE_PORT_START", defaultValue = "" + AvailablePortFinder.MIN_PORT_NUMBER) int minPort) throws MalformedObjectNameException, IOException {
this(new File(storageLocation), remoteRepositoryUrls, minPort);
}
public ProcessManagerService(File storageLocation, String remoteRepositoryUrls, int minPort) throws MalformedObjectNameException, IOException {
// make sure the install directory path is absolute and compact as there can be troubles with having foo/./bar paths
String path = FilesHelper.compactPath(storageLocation.getAbsolutePath());
this.storageLocation = new File(path);
if (Strings.isNullOrBlank(remoteRepositoryUrls)) {
remoteRepositoryUrls = DEFAULT_MAVEN_REPOS;
}
this.remoteRepositoryUrls = remoteRepositoryUrls;
this.isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
this.availablePortFinder = new AvailablePortFinder(minPort);
LOGGER.info("Using process directory: {}", this.storageLocation);
LOGGER.info("Using port allocation range: {}-{}", minPort, AvailablePortFinder.MAX_PORT_NUMBER);
LOGGER.info("Using remote maven repositories to find Jube image zips: {}", remoteRepositoryUrls);
// lets find the largest number in the current directory as we are on startup
lastId = 0;
File[] files = storageLocation.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
String name = file.getName();
if (name.startsWith(".")) {
LOGGER.debug("Ignoring deleted installation at folder " + name);
continue;
}
if (name.matches("\\d+")) {
try {
int id = Integer.parseInt(name);
if (id > lastId) {
lastId = id;
}
} catch (NumberFormatException e) {
// should never happen :)
}
}
// TODO: we do not have the url this installation was created from
OpenMavenURL url = null;
ProcessConfig config = ConfigHelper.loadProcessConfig(file, isWindows);
createInstallation(url, name, findInstallDir(file), config);
}
}
}
}
@Override
public String toString() {
return "ProcessManager(" + storageLocation + ")";
}
@Override
public ImmutableList listInstallations() {
return ImmutableList.copyOf(installations.values());
}
@Override
public ImmutableMap listInstallationMap() {
return ImmutableMap.copyOf(installations);
}
@Override
public Installation getInstallation(String id) {
return installations.get(id);
}
@JmxManaged(description = "Returns the set of installed processes")
public String getInstallationIds() {
return listInstallationMap().keySet().toString();
}
@JmxManaged(description = "Returns the number of installed processes")
public int getInstallationCount() {
return listInstallationMap().keySet().size();
}
@Override
public Installation install(final InstallOptions options, final InstallTask postInstall) throws Exception {
@SuppressWarnings("serial")
InstallTask installTask = new InstallTask() {
@Override
public void install(InstallContext installContext, ProcessConfig config, String id, File installDir) throws Exception {
config.setName(options.getName());
installDir.mkdirs();
File archive = getDownloadStrategy(options).downloadContent(options.getUrl(), installDir);
if (archive == null) {
archive = new File(installDir, INSTALLED_BINARY);
}
File nestedProcessDirectory = null;
if (archive.exists()) {
Zips.unzip(new FileInputStream(archive), installDir);
InstallHelper.chmodAllScripts(installDir);
nestedProcessDirectory = findInstallDir(installDir);
allocatePorts(options, nestedProcessDirectory);
exportInstallDirEnvVar(options, nestedProcessDirectory, installContext, config);
}
}
};
return installViaScript(options, installTask);
}
protected DownloadStrategy getDownloadStrategy(InstallOptions options) {
DownloadStrategy answer = options.getDownloadStrategy();
if (answer == null) {
answer = createDefaultDownloadStrategy();
}
return answer;
}
protected void allocatePorts(InstallOptions options, File nestedProcessDirectory) throws IOException {
Map ports = InstallHelper.readPortsFromDirectory(nestedProcessDirectory);
Set> entries = ports.entrySet();
if (!entries.isEmpty()) {
// lets allocate ports and add them as env vars
for (Map.Entry entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
if (Strings.isNotBlank(key) && Strings.isNotBlank(value)) {
String envVarName = InstallHelper.portNameToHostEnvVarName(key);
int port = allocatePortNumber(options, nestedProcessDirectory, key, value);
if (port <= 0) {
System.out.println("Could not allocate port " + envVarName + " has value: " + port);
LOGGER.warn("Could not allocate port " + envVarName + " has value: " + port);
continue;
}
options.getEnvironment().put(envVarName, "" + port);
}
}
System.out.println("============ ports " + ports + " mapped to env vars: " + options.getEnvironment());
}
}
/**
* When using the {@link java.net.InetAddress#getHostName()} method in an
* environment where neither a proper DNS lookup nor an /etc/hosts
* entry exists for a given host, the following exception will be thrown:
*
* java.net.UnknownHostException: <hostname>: <hostname>
* at java.net.InetAddress.getLocalHost(InetAddress.java:1425)
* ...
*
* Instead of just throwing an UnknownHostException and giving up, this
* method grabs a suitable hostname from the exception and prevents the
* exception from being thrown. If a suitable hostname cannot be acquired
* from the exception, only then is the UnknownHostException thrown.
*
* @return The hostname
* @throws UnknownHostException
* @see {@link java.net.InetAddress#getLocalHost()}
* @see {@link java.net.InetAddress#getHostName()}
*/
public static String getLocalHostName() throws UnknownHostException {
try {
return (InetAddress.getLocalHost()).getHostName();
} catch (UnknownHostException uhe) {
String host = uhe.getMessage(); // host = "hostname: hostname"
if (host != null) {
int colon = host.indexOf(':');
if (colon > 0) {
return host.substring(0, colon);
}
}
throw uhe;
}
}
protected int allocatePortNumber(InstallOptions options, File nestedProcessDirectory, String key, String value) {
return availablePortFinder.getNextAvailable();
}
protected void exportInstallDirEnvVar(InstallOptions options, File installDir, InstallContext installContext, ProcessConfig config) throws IOException {
options.getEnvironment().put("APP_BASE", installDir.getAbsolutePath());
substituteEnvironmentVariableExpressions(options.getEnvironment(), options.getEnvironment());
config.getEnvironment().putAll(options.getEnvironment());
}
@Override
public void uninstall(Installation installation) {
installation.getController().uninstall();
installations.remove(installation.getId());
}
@Override
public ProcessConfig loadProcessConfig(File installDir, InstallOptions options) throws IOException {
ProcessConfig config = loadControllerJson(installDir, options);
Map configEnv = config.getEnvironment();
Map optionsEnv = options.getEnvironment();
if (optionsEnv != null) {
configEnv.putAll(optionsEnv);
}
return config;
}
// Properties
//-------------------------------------------------------------------------
public File getStorageLocation() {
return storageLocation;
}
public void setStorageLocation(File storageLocation) {
this.storageLocation = storageLocation;
}
@Override
public Executor getExecutor() {
return executor;
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
// Implementation
//-------------------------------------------------------------------------
protected Installation installViaScript(InstallOptions options, InstallTask installTask) throws Exception {
String id = createNextId(options);
File installDir = createInstallDir(id);
installDir.mkdirs();
ProcessConfig config = loadProcessConfig(installDir, options);
InstallContext installContext = new InstallContext(installDir, false);
installTask.install(installContext, config, id, installDir);
ConfigHelper.saveProcessConfig(config, installDir);
Installation installation = createInstallation(options.getUrl(), id, installDir, config);
installation.getController().install();
return installation;
}
protected DownloadStrategy createDefaultDownloadStrategy() {
return new DownloadStrategy() {
@Override
public File downloadContent(final OpenMavenURL sourceUrl, final File installDir) throws IOException {
Objects.notNull(sourceUrl, "sourceUrl");
// copy the URL to the install dir
File archive = new File(installDir, INSTALLED_BINARY);
InputStream from = sourceUrl.getInputStream(remoteRepositoryUrls);
if (from == null) {
throw new FileNotFoundException("Could not open URL: " + sourceUrl);
}
copy(from, new FileOutputStream(archive));
return archive;
}
};
}
protected ProcessConfig loadControllerJson(File installDir, InstallOptions options) throws IOException {
return ConfigHelper.loadProcessConfig(installDir, isWindows);
}
/**
* Returns the next process ID
*/
protected synchronized String createNextId(InstallOptions options) {
String id = options.getId();
if (Strings.isNotBlank(id)) {
return id;
}
// lets double check it doesn't exist already
File dir;
String answer;
do {
lastId++;
answer = "" + lastId;
dir = createInstallDir(answer);
}
while (dir.exists());
return answer;
}
protected File createInstallDir(String id) {
return new File(storageLocation, id);
}
protected Installation createInstallation(OpenMavenURL url, String id, File rootDir, ProcessConfig config) {
// TODO we should support different kinds of controller based on the kind of installation
// we could maybe discover a descriptor file to describe how to control the process?
// or generate this file on installation time?
File installDir = findInstallDir(rootDir);
ProcessController controller = createController(id, config, rootDir, installDir);
// TODO need to read the URL from somewhere...
Installation installation = new Installation(url, id, installDir, controller, config);
installations.put(id, installation);
return installation;
}
protected ProcessController createController(String id, ProcessConfig config, File rootDir, File installDir) {
return new DefaultProcessController(id, config, rootDir, installDir);
}
// TODO. This is been ripped from io.fabric8.container.process.JolokiaAgentHelper.substituteEnvironmentVariableExpressions()
// requires a refactoring to not introduce circular dependencies
public static void substituteEnvironmentVariableExpressions(Map map, Map environmentVariables) {
Set> envEntries = environmentVariables.entrySet();
for (String key : map.keySet()) {
String text = map.get(key);
String oldText = text;
if (Strings.isNotBlank(text)) {
for (Map.Entry envEntry : envEntries) {
String envKey = envEntry.getKey();
String envValue = envEntry.getValue();
if (Strings.isNotBlank(envKey) && Strings.isNotBlank(envValue)) {
text = text.replace("${env:" + envKey + "}", envValue);
}
}
if (!Objects.equal(oldText, text)) {
map.put(key, text);
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy