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 com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.io.Files; import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.NodeMetadataBuilder; import org.jclouds.compute.options.RunScriptOptions; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.domain.LoginCredentials; import org.jclouds.logging.Logger; import org.jclouds.util.Strings2; import org.jclouds.virtualbox.config.VirtualBoxComputeServiceContextModule; import org.jclouds.virtualbox.domain.CloneSpec; import org.jclouds.virtualbox.domain.Master; import org.jclouds.virtualbox.domain.NetworkInterfaceCard; import org.jclouds.virtualbox.domain.NetworkSpec; import org.jclouds.virtualbox.domain.NodeSpec; import org.jclouds.virtualbox.domain.VmSpec; import org.jclouds.virtualbox.statements.DeleteGShadowLock; import org.jclouds.virtualbox.statements.PasswordlessSudo; import org.jclouds.virtualbox.util.MachineController; import org.jclouds.virtualbox.util.MachineUtils; import org.jclouds.virtualbox.util.NetworkUtils; import org.virtualbox_4_2.CleanupMode; import org.virtualbox_4_2.IMachine; import org.virtualbox_4_2.IProgress; import org.virtualbox_4_2.ISession; import org.virtualbox_4_2.LockType; import org.virtualbox_4_2.NetworkAttachmentType; import org.virtualbox_4_2.VirtualBoxManager; import com.google.common.collect.ImmutableList; import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.File; import java.io.IOException; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.jclouds.virtualbox.config.VirtualBoxConstants.GUEST_OS_PASSWORD; import static org.jclouds.virtualbox.config.VirtualBoxConstants.GUEST_OS_USER; import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_GUEST_MEMORY; 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_NODE_PREFIX; import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_WORKINGDIR; /** * Creates nodes, by cloning a master vm and based on the provided {@link NodeSpec}. Must be * synchronized mainly because of snapshot creation (must be synchronized on a per-master-basis). * * @author David Alves, Andrea Turli * */ @Singleton public class NodeCreator implements Function<NodeSpec, NodeAndInitialCredentials<IMachine>> { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; private final Supplier<VirtualBoxManager> manager; private final Function<CloneSpec, IMachine> cloner; private final MachineUtils machineUtils; private final MachineController machineController; private final NetworkUtils networkUtils; private final int ram; private final String workingDir; @Inject public NodeCreator(Supplier<VirtualBoxManager> manager, Function<CloneSpec, IMachine> cloner, MachineUtils machineUtils, MachineController machineController, NetworkUtils networkUtils, @Named(VIRTUALBOX_GUEST_MEMORY) String ram, @Named(VIRTUALBOX_WORKINGDIR) String workingDir) { this.manager = checkNotNull(manager, "manager"); this.cloner = checkNotNull(cloner, "cloner"); this.networkUtils = checkNotNull(networkUtils, "networkUtils"); this.machineUtils = checkNotNull(machineUtils, "machineUtils"); this.machineController = checkNotNull(machineController, "machineController"); this.ram = checkNotNull(Integer.valueOf(ram), "ram"); this.workingDir = checkNotNull(workingDir, "workingDir"); } @Override public synchronized NodeAndInitialCredentials<IMachine> apply(NodeSpec nodeSpec) { checkNotNull(nodeSpec, "NodeSpec"); Master master = checkNotNull(nodeSpec.getMaster(), "Master"); IMachine masterMachine = master.getMachine(); String guestOsUser = masterMachine.getExtraData(GUEST_OS_USER); String guestOsPassword = masterMachine.getExtraData(GUEST_OS_PASSWORD); cleanUpMaster(master); CloneSpec cloneSpec = configureCloneSpec(nodeSpec, guestOsUser, guestOsPassword); IMachine clone = cloner.apply(cloneSpec); String cloneName = cloneSpec.getVmSpec().getVmName(); logger.debug("<< cloned a vm(%s) from master(%s)", cloneName, nodeSpec.getMaster().getMachine().getName()); machineController.ensureMachineIsLaunched(cloneName); logger.debug("<< cloned vm(%s) is up and running", cloneName); reconfigureNetworkInterfaces(masterMachine, guestOsUser, guestOsPassword, cloneSpec.getNetworkSpec(), clone); postConfigurations(clone, guestOsUser, guestOsPassword); LoginCredentials credentials = LoginCredentials.builder().user(guestOsUser).password(guestOsPassword) .authenticateSudo(true).build(); return new NodeAndInitialCredentials<IMachine>(clone, cloneName, credentials); } private void reconfigureNetworkInterfaces(IMachine masterMachine, String guestOsUser, String guestOsPassword, NetworkSpec networkSpec, IMachine clone) { reconfigureHostOnlyInterfaceIfNeeded(guestOsUser, guestOsPassword, clone.getName(), masterMachine.getOSTypeId()); logger.debug("<< reconfigured hostOnly interface of node(%s)", clone.getName()); reconfigureNatInterfaceIfNeeded(guestOsUser, guestOsPassword, clone.getOSTypeId(), clone, networkSpec); logger.debug("<< reconfigured NAT interface of node(%s)", clone.getName()); } /** * {@see DeleteGShadowLock} and {@see PasswordlessSudo} for a detailed explanation * * @param clone the target machine * @param guestOsUser the user to access the target machine * @param guestOsPassword the password to access the target machine */ private void postConfigurations(IMachine clone, String guestOsUser, String guestOsPassword) { NodeMetadata partialNodeMetadata = buildPartialNodeMetadata(clone, guestOsUser, guestOsPassword); machineUtils.runScriptOnNode(partialNodeMetadata, new DeleteGShadowLock(), RunScriptOptions.NONE); machineUtils.runScriptOnNode(partialNodeMetadata, new PasswordlessSudo(partialNodeMetadata.getCredentials().identity), RunScriptOptions.Builder.runAsRoot(true)); } private CloneSpec configureCloneSpec(NodeSpec nodeSpec, String guestOsUser, String guestOsPassword) { String cloneName = generateCloneName(nodeSpec); VmSpec cloneVmSpec = VmSpec.builder().id(cloneName).name(cloneName).memoryMB(ram) .osTypeId(nodeSpec.getMaster().getMachine().getOSTypeId()).guestUser(guestOsUser) .guestPassword(guestOsPassword).cleanUpMode(CleanupMode.Full).forceOverwrite(true).build(); // case 'vbox host is localhost': NAT + HOST-ONLY NetworkSpec networkSpec = networkUtils.createNetworkSpecWhenVboxIsLocalhost(); return CloneSpec.builder().linked(true).master(nodeSpec.getMaster().getMachine()).network(networkSpec) .vm(cloneVmSpec).build(); } private void cleanUpMaster(Master master) { deleteExistingSnapshot(master); } private void reconfigureHostOnlyInterfaceIfNeeded(final String username, final String password, String vmName, String osTypeId) { final String scriptName = "hostOnly"; if (osTypeId.contains("RedHat")) { File scriptFile = copyScriptToWorkingDir("redHatAndDerivatives", scriptName); copyToNodeAndExecScript(username, password, vmName, scriptFile); } } private void reconfigureNatInterfaceIfNeeded(final String guestOsUser, final String guestOsPassword, String osTypeId, IMachine clone, NetworkSpec networkSpec) { final String scriptName = "nat"; final String folder = "redHatAndDerivatives"; if (osTypeId.contains("RedHat")) { File scriptFile = copyScriptToWorkingDir(folder, scriptName); copyToNodeAndExecScript(guestOsUser, guestOsPassword, clone.getName(), scriptFile); } else if (osTypeId.contains("Ubuntu") || osTypeId.contains("Debian")) { NodeMetadata partialNodeMetadata = buildPartialNodeMetadata(clone, guestOsUser, guestOsPassword); Optional<NetworkInterfaceCard> optionalNatIfaceCard = Iterables .tryFind(networkSpec.getNetworkInterfaceCards(), new Predicate<NetworkInterfaceCard>() { @Override public boolean apply(NetworkInterfaceCard nic) { return nic.getNetworkAdapter().getNetworkAttachmentType() .equals(NetworkAttachmentType.NAT); } }); checkState(networkUtils.enableNetworkInterface(partialNodeMetadata, optionalNatIfaceCard.get()), "cannot enable NAT Interface on vm(%s)", clone.getName()); } } private File copyScriptToWorkingDir(String folder, String scriptName) { File scriptFile = new File(workingDir + "/conf/" + "/" + folder + "/" + scriptName); scriptFile.getParentFile().mkdirs(); if (!scriptFile.exists()) { try { Files.write( Strings2.toStringAndClose(getClass().getResourceAsStream("/" + folder + "/" + scriptName)), scriptFile, Charsets.UTF_8); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return scriptFile; } private void copyToNodeAndExecScript(final String username, final String password, String vmName, final File scriptFile) { machineUtils.sharedLockMachineAndApplyToSession(vmName, new Function<ISession, Void>() { @Override public Void apply(ISession session) { String scriptName = scriptFile.getName(); manager.get().getSessionObject().getConsole().getGuest() .createSession(username, password, null, null) .copyTo(scriptFile.getAbsolutePath(), "/tmp/" + scriptName, null); manager.get().getSessionObject().getConsole().getGuest() .createSession(username, password, null, null).processCreate("/bin/chmod", ImmutableList.of("777", "/tmp/" + scriptName), null, null, 5 * 1000l); manager.get().getSessionObject().getConsole().getGuest() .createSession(username, password, null, null) .processCreate("/bin/sh", ImmutableList.of("/tmp/" + scriptName), null, null, 5 * 1000l); return null; } }); } private String generateCloneName(NodeSpec nodeSpec) { String masterNameWithoutPrefix = nodeSpec.getMaster().getMachine().getName() .replace(VIRTUALBOX_IMAGE_PREFIX, ""); return VIRTUALBOX_NODE_PREFIX + masterNameWithoutPrefix + VIRTUALBOX_NODE_NAME_SEPARATOR + nodeSpec.getTag() + VIRTUALBOX_NODE_NAME_SEPARATOR + nodeSpec.getName(); } private void deleteExistingSnapshot(Master master) { if (master.getMachine().getCurrentSnapshot() != null) { ISession session; try { session = manager.get().getSessionObject(); master.getMachine().lockMachine(session, LockType.Write); IProgress progress = session.getConsole() .deleteSnapshot(master.getMachine().getCurrentSnapshot().getId()); progress.waitForCompletion(-1); session.unlockMachine(); } catch (Exception e) { throw new RuntimeException("error opening vbox machine session: " + e.getMessage(), e); } logger.debug("<< deleted an existing snapshot of vm(%s)", master.getMachine().getName()); } } private NodeMetadata buildPartialNodeMetadata(IMachine clone, String guestOsUser, String guestOsPassword) { NodeMetadataBuilder nodeMetadataBuilder = new NodeMetadataBuilder(); nodeMetadataBuilder.id(clone.getName()); nodeMetadataBuilder .status(VirtualBoxComputeServiceContextModule.toPortableNodeStatus.get(clone.getState())); nodeMetadataBuilder .publicAddresses(ImmutableSet.of(networkUtils.getValidHostOnlyIpFromVm(clone.getName()))); nodeMetadataBuilder.credentials(LoginCredentials.builder().user(guestOsUser).password(guestOsPassword) .authenticateSudo(true).build()); return nodeMetadataBuilder.build(); } }