blob: 838338d0e9d2738ab63ba11301cc5908736df060 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.jclouds.vagrant.compute;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.Volume;
import org.jclouds.compute.domain.Volume.Type;
import org.jclouds.compute.util.AutomaticHardwareIdSpec;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.location.suppliers.all.JustProvider;
import org.jclouds.logging.Logger;
import org.jclouds.vagrant.api.VagrantApiFacade;
import org.jclouds.vagrant.domain.VagrantNode;
import org.jclouds.vagrant.internal.MachineConfig;
import org.jclouds.vagrant.internal.VagrantNodeRegistry;
import org.jclouds.vagrant.reference.VagrantConstants;
import org.jclouds.vagrant.util.VagrantUtils;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
public class VagrantComputeServiceAdapter implements ComputeServiceAdapter<VagrantNode, Hardware, Image, Location> {
private static final Pattern PATTERN_IP_ADDR = Pattern.compile("inet ([0-9\\.]+)/(\\d+)");
private static final Pattern PATTERN_IPCONFIG = Pattern.compile("IPv4 Address[ .]+: ([0-9\\.]+)");
@Resource
protected Logger logger = Logger.NULL;
private final File home;
private final JustProvider locationSupplier;
private final VagrantNodeRegistry nodeRegistry;
private final MachineConfig.Factory machineConfigFactory;
private final VagrantApiFacade.Factory cliFactory;
private final Supplier<? extends Map<String, Hardware>> hardwareSupplier;
private final Supplier<Collection<Image>> imageListSupplier;
private final Function<String, Image> imageIdToImage;
@Inject
VagrantComputeServiceAdapter(@Named(VagrantConstants.JCLOUDS_VAGRANT_HOME) String home,
JustProvider locationSupplier,
VagrantNodeRegistry nodeRegistry,
MachineConfig.Factory machineConfigFactory,
VagrantApiFacade.Factory cliFactory,
Supplier<? extends Map<String, Hardware>> hardwareSupplier,
Supplier<Collection<Image>> imageListSupplier,
Function<String, Image> imageIdToImage) {
this.home = new File(home);
this.locationSupplier = locationSupplier;
this.nodeRegistry = nodeRegistry;
this.machineConfigFactory = machineConfigFactory;
this.cliFactory = cliFactory;
this.hardwareSupplier = hardwareSupplier;
this.imageListSupplier = imageListSupplier;
this.imageIdToImage = imageIdToImage;
this.home.mkdirs();
}
@Override
public NodeAndInitialCredentials<VagrantNode> createNodeWithGroupEncodedIntoName(String group, String name, Template template) {
String machineName = removeFromStart(name, group);
File nodePath = new File(home, group);
init(nodePath, machineName, template);
NodeAndInitialCredentials<VagrantNode> node = startMachine(nodePath, group, machineName,
template.getImage(), template.getHardware());
nodeRegistry.add(node.getNode());
return node;
}
private NodeAndInitialCredentials<VagrantNode> startMachine(File path, String group, String name, Image image, Hardware hardware) {
String provider = image.getUserMetadata().get(VagrantConstants.USER_META_PROVIDER);
VagrantApiFacade vagrant = cliFactory.create(path);
String rawOutput = vagrant.up(name, provider);
String output = normalizeOutput(name, rawOutput);
OsFamily osFamily = image.getOperatingSystem().getFamily();
String id = group + "/" + name;
VagrantNode node = VagrantNode.builder()
.setPath(path)
.setId(id)
.setGroup(group)
.setName(name)
.setImage(image)
.setHardware(hardware)
.setNetworks(getNetworks(output, getOsInterfacePattern(osFamily)))
.setHostname(getHostname(output))
.build();
node.setMachineState(Status.RUNNING);
LoginCredentials loginCredentials = null;
if (osFamily != OsFamily.WINDOWS) {
loginCredentials = vagrant.sshConfig(name);
}
// PrioritizeCredentialsFromTemplate will overwrite loginCredentials with image credentials
// AdaptingComputeServiceStrategies saves the merged credentials in credentialStore
return new NodeAndInitialCredentials<VagrantNode>(node, node.id(), loginCredentials);
}
private String normalizeOutput(String name, String output) {
return output
.replaceAll("(?m)^([^,]*,){4}", "")
.replace("==> " + name + ": ", "")
// Vagrant shows some of the \n verbatim in provisioning command results.
.replace("\\n", "\n");
}
private Pattern getOsInterfacePattern(OsFamily osFamily) {
if (osFamily == OsFamily.WINDOWS) {
return PATTERN_IPCONFIG;
} else {
return PATTERN_IP_ADDR;
}
}
private Collection<String> getNetworks(String output, Pattern ifPattern) {
String networks = getDelimitedString(
output,
VagrantConstants.DELIMITER_NETWORKS_START,
VagrantConstants.DELIMITER_NETWORKS_END);
Matcher m = ifPattern.matcher(networks);
Collection<String> ips = new ArrayList<String>();
while (m.find()) {
String network = m.group(1);
// TODO figure out a more generic approach to ignore unreachable networkds (this one is the NAT'd address).
if (network.startsWith("10.")) continue;
ips.add(network);
}
return ips;
}
private String getHostname(String output) {
return getDelimitedString(
output,
VagrantConstants.DELIMITER_HOSTNAME_START,
VagrantConstants.DELIMITER_HOSTNAME_END);
}
private String getDelimitedString(String value, String delimStart, String delimEnd) {
int startPos = value.indexOf(delimStart);
int endPos = value.indexOf(delimEnd);
if (startPos == -1) {
throw new IllegalStateException("Delimiter " + delimStart + " not found in output \n" + value);
}
if (endPos == -1) {
throw new IllegalStateException("Delimiter " + delimEnd + " not found in output \n" + value);
}
return value.substring(startPos + delimStart.length(), endPos).trim();
}
private void init(File path, String name, Template template) {
try {
writeVagrantfile(path);
initMachineConfig(path, name, template);
} catch (IOException e) {
throw new IllegalStateException("Unable to initialize Vagrant configuration at " +
path + " for machine " + name, e);
}
}
private void writeVagrantfile(File path) throws IOException {
path.mkdirs();
VagrantUtils.write(
new File(path, VagrantConstants.VAGRANTFILE),
getClass().getClassLoader().getResourceAsStream(VagrantConstants.VAGRANTFILE));
}
private void initMachineConfig(File path, String name, Template template) {
MachineConfig config = machineConfigFactory.newInstance(path, name);
List<? extends Volume> volumes = template.getHardware().getVolumes();
if (volumes != null) {
if (volumes.size() == 1) {
Volume volume = Iterables.getOnlyElement(volumes);
if (volume.getType() != Type.LOCAL || volume.getSize() != null) {
throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes);
}
} else if (volumes.size() > 1) {
throw new IllegalStateException("Custom volume settings not supported. Volumes required: " + volumes);
}
}
config.save(ImmutableMap.<String, Object>of(
VagrantConstants.CONFIG_BOX, template.getImage().getName(),
VagrantConstants.CONFIG_OS_FAMILY, template.getImage().getOperatingSystem().getFamily(),
VagrantConstants.CONFIG_HARDWARE_ID, getHardwareId(template),
VagrantConstants.CONFIG_MEMORY, Integer.toString(template.getHardware().getRam()),
VagrantConstants.CONFIG_CPUS, Integer.toString(countProcessors(template))));
}
private String getHardwareId(Template template) {
String id = template.getHardware().getId();
if (AutomaticHardwareIdSpec.isAutomaticId(id)) {
return VagrantConstants.MACHINES_AUTO_HARDWARE;
} else {
return id;
}
}
private int countProcessors(Template template) {
int cnt = 0;
for (Processor p : template.getHardware().getProcessors()) {
cnt += p.getCores();
}
return cnt;
}
@Override
public Iterable<Hardware> listHardwareProfiles() {
return hardwareSupplier.get().values();
}
@Override
public Iterable<Image> listImages() {
return imageListSupplier.get();
}
@Override
public Image getImage(String id) {
return imageIdToImage.apply(id);
}
@Override
public Iterable<Location> listLocations() {
Location provider = Iterables.getOnlyElement(locationSupplier.get());
return ImmutableList.of(
new LocationBuilder().id("localhost").description("localhost").parent(provider).scope(LocationScope.HOST).build());
}
@Override
public VagrantNode getNode(String id) {
// needed for BaseComputeServiceLiveTest.testAScriptExecutionAfterBootWithBasicTemplate()
// waits for the thread updating the credentialStore to execute
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw Throwables.propagate(e);
}
return nodeRegistry.get(id);
}
@Override
public void destroyNode(String id) {
VagrantNode node = nodeRegistry.get(id);
node.setMachineState(Status.TERMINATED);
getMachine(node).destroy(node.name());
nodeRegistry.onTerminated(node);
deleteMachine(node);
}
private void deleteMachine(VagrantNode node) {
File nodeFolder = node.path();
File machinesFolder = new File(nodeFolder, VagrantConstants.MACHINES_CONFIG_SUBFOLDER);
String filePattern = node.name() + ".";
logger.debug("Deleting machine %s", node.id());
VagrantUtils.deleteFiles(machinesFolder, filePattern);
// No more machines in this group, remove everything
if (machinesFolder.list().length == 0) {
logger.debug("Machine %s is last in group, deleting Vagrant folder %s", node.id(), nodeFolder.getAbsolutePath());
VagrantUtils.deleteFolder(nodeFolder);
}
}
@Override
public void rebootNode(String id) {
halt(id);
VagrantNode node = nodeRegistry.get(id);
String provider = node.image().getUserMetadata().get(VagrantConstants.USER_META_PROVIDER);
String name = node.name();
VagrantApiFacade vagrant = getMachine(node);
vagrant.up(name, provider);
node.setMachineState(Status.RUNNING);
}
private void halt(String id) {
VagrantNode node = nodeRegistry.get(id);
String name = node.name();
VagrantApiFacade vagrant = getMachine(node);
try {
vagrant.halt(name);
node.setMachineState(Status.SUSPENDED);
} catch (IllegalStateException e) {
logger.warn(e, "Failed graceful shutdown of machine " + id + ". Will try to halt it forcefully instead.");
vagrant.haltForced(name);
}
}
@Override
public void resumeNode(String id) {
VagrantNode node = nodeRegistry.get(id);
String provider = node.image().getUserMetadata().get(VagrantConstants.USER_META_PROVIDER);
String name = node.name();
VagrantApiFacade vagrant = getMachine(node);
vagrant.up(name, provider);
node.setMachineState(Status.RUNNING);
}
@Override
public void suspendNode(String id) {
halt(id);
VagrantNode node = nodeRegistry.get(id);
node.setMachineState(Status.SUSPENDED);
}
@Override
public Iterable<VagrantNode> listNodes() {
return nodeRegistry.list();
}
@Override
public Iterable<VagrantNode> listNodesByIds(final Iterable<String> ids) {
return Iterables.filter(listNodes(), new Predicate<VagrantNode>() {
@Override
public boolean apply(VagrantNode input) {
return Iterables.contains(ids, input.id());
}
});
}
private VagrantApiFacade getMachine(VagrantNode node) {
File nodePath = node.path();
return cliFactory.create(nodePath);
}
private String removeFromStart(String name, String group) {
if (name.startsWith(group)) {
String machineName = name.substring(group.length());
// Can't pass names starting with dash on the command line
if (machineName.startsWith("-")) {
return machineName.substring(1);
} else {
return machineName;
}
} else {
return name;
}
}
}