org.jclouds.vagrant.compute.VagrantComputeServiceAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.vagrant.compute.VagrantComputeServiceAdapter.java

Source

/*
 * 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);
            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;
        }
    }

}