Package org.jclouds.virtualbox.functions

Source Code of org.jclouds.virtualbox.functions.MastersLoadingCache

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

Related Classes of org.jclouds.virtualbox.functions.MastersLoadingCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.