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