org.jclouds.virtualbox.functions.MastersLoadingCache.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.virtualbox.functions.MastersLoadingCache.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.virtualbox.functions;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.compute.options.RunScriptOptions.Builder.runAsRoot;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_DEFAULT_DIR;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_IMAGE_PREFIX;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_NODE_NAME_SEPARATOR;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_PRECONFIGURATION_URL;
import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_WORKINGDIR;
import static org.jclouds.virtualbox.util.MachineUtils.machineNotFoundException;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.jclouds.compute.callables.RunScriptOnNode.Factory;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.location.Provider;
import org.jclouds.logging.Logger;
import org.jclouds.rest.annotations.BuildVersion;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.StatementList;
import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.virtualbox.domain.HardDisk;
import org.jclouds.virtualbox.domain.IsoSpec;
import org.jclouds.virtualbox.domain.Master;
import org.jclouds.virtualbox.domain.MasterSpec;
import org.jclouds.virtualbox.domain.NetworkAdapter;
import org.jclouds.virtualbox.domain.NetworkInterfaceCard;
import org.jclouds.virtualbox.domain.NetworkSpec;
import org.jclouds.virtualbox.domain.StorageController;
import org.jclouds.virtualbox.domain.VmSpec;
import org.jclouds.virtualbox.domain.YamlImage;
import org.jclouds.virtualbox.functions.admin.PreseedCfgServer;
import org.jclouds.virtualbox.predicates.RetryIfSocketNotYetOpen;
import org.jclouds.virtualbox.statements.Md5;
import org.jclouds.virtualbox.util.NetworkUtils;
import org.virtualbox_4_2.CleanupMode;
import org.virtualbox_4_2.IMachine;
import org.virtualbox_4_2.NetworkAttachmentType;
import org.virtualbox_4_2.StorageBus;
import org.virtualbox_4_2.VBoxException;
import org.virtualbox_4_2.VirtualBoxManager;

import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.cache.AbstractLoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.net.HostAndPort;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * A {@link LoadingCache} for masters. If the requested master has been
 * previously created this returns it, if not it coordinates its creation
 * including downloading isos and creating cache/config directories. This also
 * implements {@link Supplier} in order to provide jetty with the current image
 * (only one master can be created at a time).
 * 
 * @author dralves, andrea turli
 * 
 */
@Singleton
public class MastersLoadingCache extends AbstractLoadingCache<Image, Master> {

    @Resource
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
    protected Logger logger = Logger.NULL;

    private final Map<String, Master> masters = Maps.newHashMap();
    private final Function<MasterSpec, IMachine> masterCreatorAndInstaller;
    private final Map<String, YamlImage> imageMapping;
    private final String workingDir;
    private final String isosDir;
    private final Supplier<VirtualBoxManager> manager;
    private final String version;
    private final String preconfigurationUrl;

    private final Factory runScriptOnNodeFactory;
    private final RetryIfSocketNotYetOpen socketTester;
    private final Supplier<NodeMetadata> host;
    private final Supplier<URI> providerSupplier;
    private final HardcodedHostToHostNodeMetadata hardcodedHostToHostNodeMetadata;

    @Inject
    public MastersLoadingCache(@BuildVersion String version,
            @Named(VIRTUALBOX_PRECONFIGURATION_URL) String preconfigurationUrl,
            @Named(VIRTUALBOX_WORKINGDIR) String workingDir, Function<MasterSpec, IMachine> masterLoader,
            Supplier<Map<Image, YamlImage>> yamlMapper, Supplier<VirtualBoxManager> manager,
            Factory runScriptOnNodeFactory, RetryIfSocketNotYetOpen socketTester, Supplier<NodeMetadata> host,
            @Provider Supplier<URI> providerSupplier,
            HardcodedHostToHostNodeMetadata hardcodedHostToHostNodeMetadata) {
        this.manager = checkNotNull(manager, "vboxmanager can't be null");
        this.masterCreatorAndInstaller = masterLoader;
        this.workingDir = workingDir == null ? VIRTUALBOX_DEFAULT_DIR : workingDir;
        this.isosDir = workingDir + File.separator + "isos";
        this.imageMapping = Maps.newLinkedHashMap();
        for (Entry<Image, YamlImage> entry : yamlMapper.get().entrySet()) {
            this.imageMapping.put(entry.getKey().getId(), entry.getValue());
        }
        this.version = Iterables.get(Splitter.on('r').split(checkNotNull(version, "version")), 0);
        this.preconfigurationUrl = preconfigurationUrl;

        this.runScriptOnNodeFactory = checkNotNull(runScriptOnNodeFactory, "runScriptOnNodeFactory");
        this.socketTester = checkNotNull(socketTester, "socketTester");
        this.socketTester.seconds(3L);
        this.host = checkNotNull(host, "host");
        this.providerSupplier = checkNotNull(providerSupplier, "endpoint to virtualbox websrvd is needed");
        this.hardcodedHostToHostNodeMetadata = hardcodedHostToHostNodeMetadata;
    }

    @PostConstruct
    public void createCacheDirStructure() {
        if (!new File(workingDir).exists()) {
            new File(workingDir, "isos").mkdirs();
        }
    }

    @Override
    public synchronized Master get(Image key) throws ExecutionException {
        // check if we have loaded this machine before
        if (masters.containsKey(key.getId())) {
            return masters.get(key.getId());
        }
        checkState(!key.getId().contains(VIRTUALBOX_NODE_NAME_SEPARATOR),
                "master image names cannot contain \"" + VIRTUALBOX_NODE_NAME_SEPARATOR + "\"");
        String vmName = VIRTUALBOX_IMAGE_PREFIX + key.getId();
        IMachine masterMachine;
        Master master;
        // ready the preseed file server
        PreseedCfgServer server = new PreseedCfgServer();
        try {
            // try and find a master machine in vbox
            masterMachine = manager.get().getVBox().findMachine(vmName);
            master = Master.builder().machine(masterMachine).build();
        } catch (VBoxException e) {
            if (machineNotFoundException(e)) {
                // machine was not found try to build one from a yaml file
                YamlImage currentImage = checkNotNull(imageMapping.get(key.getId()), "currentImage");
                URI preseedServer;
                try {
                    preseedServer = new URI(preconfigurationUrl);
                    if (!socketTester
                            .apply(HostAndPort.fromParts(preseedServer.getHost(), preseedServer.getPort()))) {
                        server.start(preconfigurationUrl, currentImage.preseed_cfg);
                    }
                } catch (URISyntaxException e1) {
                    logger.error("Cannot start the preseed server", e);
                    throw e;
                }

                MasterSpec masterSpec = buildMasterSpecFromYaml(currentImage, vmName);
                masterMachine = masterCreatorAndInstaller.apply(masterSpec);
                master = Master.builder().machine(masterMachine).spec(masterSpec).build();
            } else {
                logger.error("Problem during master creation", e);
                throw e;
            }
        } finally {
            server.stop();
        }

        masters.put(key.getId(), master);
        return master;
    }

    private MasterSpec buildMasterSpecFromYaml(YamlImage currentImage, String vmName) throws ExecutionException {
        String guestAdditionsFileName = String.format("VBoxGuestAdditions_%s.iso", version);
        String guestAdditionsIso = String.format("%s/%s", isosDir, guestAdditionsFileName);
        String guestAdditionsUri = "http://download.virtualbox.org/virtualbox/" + version + "/"
                + guestAdditionsFileName;
        if (!new File(guestAdditionsIso).exists()) {
            getFilePathOrDownload(guestAdditionsUri, null);
        }
        // check if the iso is here, download if not
        String localIsoUrl = checkNotNull(getFilePathOrDownload(currentImage.iso, currentImage.iso_md5),
                "distro iso");
        String adminDisk = workingDir + File.separator + vmName + ".vdi";
        HardDisk hardDisk = HardDisk.builder().diskpath(adminDisk).autoDelete(true).controllerPort(0).deviceSlot(1)
                .build();

        StorageController ideController = StorageController.builder().name("IDE Controller").bus(StorageBus.IDE)
                .attachISO(0, 0, localIsoUrl).attachHardDisk(hardDisk).build();

        VmSpec vmSpecification = VmSpec.builder().id(currentImage.id).name(vmName).memoryMB(512)
                .osTypeId(getOsTypeId(currentImage.os_family, currentImage.os_64bit)).controller(ideController)
                .forceOverwrite(true).guestUser(currentImage.username).guestPassword(currentImage.credential)
                .cleanUpMode(CleanupMode.Full).build();

        NetworkAdapter networkAdapter = NetworkAdapter.builder().networkAttachmentType(NetworkAttachmentType.NAT)
                .tcpRedirectRule(providerSupplier.get().getHost(), NetworkUtils.MASTER_PORT, "", 22).build();

        NetworkInterfaceCard networkInterfaceCard = NetworkInterfaceCard.builder().addNetworkAdapter(networkAdapter)
                .slot(0L).build();

        NetworkSpec networkSpec = NetworkSpec.builder().addNIC(networkInterfaceCard).build();

        String installationSequence = currentImage.keystroke_sequence.replace("HOSTNAME",
                vmSpecification.getVmName());
        return MasterSpec.builder().vm(vmSpecification)
                .iso(IsoSpec.builder().sourcePath(localIsoUrl).installationScript(installationSequence).build())
                .network(networkSpec).credentials(LoginCredentials.builder().user(currentImage.username)
                        .password(currentImage.credential).authenticateSudo(true).build())
                .build();
    }

    @Override
    public synchronized Master getIfPresent(Object key) {
        checkArgument(key instanceof Image, "this cache is for entries who's keys are Images");
        Image image = Image.class.cast(key);
        if (masters.containsKey(image.getId())) {
            return masters.get(image.getId());
        }
        return null;
    }

    private String getFilePathOrDownload(String httpUrl, String expectedMd5) throws ExecutionException {
        String fileName = httpUrl.substring(httpUrl.lastIndexOf('/') + 1, httpUrl.length());
        URI provider = providerSupplier.get();
        if (!socketTester.apply(HostAndPort.fromParts(provider.getHost(), provider.getPort()))) {
            throw new RuntimeException("could not connect to virtualbox");
        }
        File file = new File(isosDir, fileName);
        List<Statement> statements = new ImmutableList.Builder<Statement>()
                .add(Statements.saveHttpResponseTo(URI.create(httpUrl), isosDir, fileName)).build();
        StatementList statementList = new StatementList(statements);
        NodeMetadata hostNode = checkNotNull(hardcodedHostToHostNodeMetadata.apply(host.get()), "hostNode");
        ListenableFuture<ExecResponse> future = runScriptOnNodeFactory.submit(hostNode, statementList,
                runAsRoot(false));
        Futures.getUnchecked(future);

        if (expectedMd5 != null) {
            String filePath = isosDir + File.separator + fileName;
            ListenableFuture<ExecResponse> md5future = runScriptOnNodeFactory.submit(hostNode, new Md5(filePath),
                    runAsRoot(false));

            ExecResponse responseMd5 = Futures.getUnchecked(md5future);
            assert responseMd5.getExitStatus() == 0 : hostNode.getId() + ": " + responseMd5;
            checkNotNull(responseMd5.getOutput(), "iso_md5 missing");
            String actualMd5 = responseMd5.getOutput().trim();
            checkState(actualMd5.equals(expectedMd5), "md5 of %s is %s but expected %s", filePath, actualMd5,
                    expectedMd5);
        }
        return file.getAbsolutePath();
    }

    private String getOsTypeId(String os_family, boolean os_64bit) {
        String osFamily = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, os_family);
        return os_64bit ? osFamily + "_64" : osFamily;
    }
}