| /** |
| * Licensed to jclouds, Inc. (jclouds) under one or more |
| * contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. jclouds 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.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_IMAGE_PREFIX; |
| import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_NODE_NAME_SEPARATOR; |
| import static org.jclouds.virtualbox.config.VirtualBoxConstants.VIRTUALBOX_NODE_PREFIX; |
| |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| import javax.inject.Singleton; |
| |
| import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials; |
| import org.jclouds.compute.callables.RunScriptOnNode; |
| import org.jclouds.compute.callables.RunScriptOnNode.Factory; |
| import org.jclouds.compute.domain.ExecResponse; |
| import org.jclouds.compute.domain.NodeMetadata; |
| import org.jclouds.compute.domain.NodeMetadataBuilder; |
| import org.jclouds.compute.options.RunScriptOptions; |
| import org.jclouds.domain.LoginCredentials; |
| import org.jclouds.scriptbuilder.domain.Statements; |
| import org.jclouds.virtualbox.config.VirtualBoxComputeServiceContextModule; |
| import org.jclouds.virtualbox.domain.CloneSpec; |
| import org.jclouds.virtualbox.domain.Master; |
| import org.jclouds.virtualbox.domain.NetworkAdapter; |
| 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.EnableNetworkInterface; |
| import org.jclouds.virtualbox.util.MachineController; |
| import org.jclouds.virtualbox.util.MachineUtils; |
| import org.virtualbox_4_1.CleanupMode; |
| import org.virtualbox_4_1.HostNetworkInterfaceType; |
| import org.virtualbox_4_1.IDHCPServer; |
| import org.virtualbox_4_1.IHostNetworkInterface; |
| import org.virtualbox_4_1.IMachine; |
| import org.virtualbox_4_1.IProgress; |
| import org.virtualbox_4_1.ISession; |
| import org.virtualbox_4_1.NetworkAttachmentType; |
| import org.virtualbox_4_1.VirtualBoxManager; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| |
| /** |
| * 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>> { |
| |
| private final Supplier<VirtualBoxManager> manager; |
| private final Function<CloneSpec, IMachine> cloner; |
| private final MachineUtils machineUtils; |
| private final MachineController machineController; |
| private final Factory runScriptOnNodeFactory; |
| private final Supplier<NodeMetadata> host; |
| |
| |
| @Inject |
| public NodeCreator(Supplier<VirtualBoxManager> manager, Function<CloneSpec, IMachine> cloner, Factory runScriptOnNodeFactory, |
| Supplier<NodeMetadata> host, |
| MachineUtils machineUtils, RunScriptOnNode.Factory scriptRunnerFactory, MachineController machineController) { |
| this.manager = manager; |
| this.cloner = cloner; |
| this.runScriptOnNodeFactory = checkNotNull(runScriptOnNodeFactory, "runScriptOnNodeFactory"); |
| this.host = checkNotNull(host, "host"); |
| |
| this.machineUtils = machineUtils; |
| this.machineController = machineController; |
| } |
| |
| @Override |
| public synchronized NodeAndInitialCredentials<IMachine> apply(NodeSpec nodeSpec) { |
| |
| checkNotNull(nodeSpec, "NodeSpec"); |
| |
| Master master = nodeSpec.getMaster(); |
| checkNotNull(master, "Master"); |
| |
| if (master.getMachine().getCurrentSnapshot() != null) { |
| ISession session; |
| try { |
| session = manager.get().openMachineSession(master.getMachine()); |
| } catch (Exception e) { |
| throw new RuntimeException("error opening vbox machine session: " + e.getMessage(), e); |
| } |
| IProgress progress = session.getConsole().deleteSnapshot(master.getMachine().getCurrentSnapshot().getId()); |
| progress.waitForCompletion(-1); |
| session.unlockMachine(); |
| } |
| String masterNameWithoutPrefix = master.getSpec().getVmSpec().getVmName().replace(VIRTUALBOX_IMAGE_PREFIX, ""); |
| |
| String cloneName = VIRTUALBOX_NODE_PREFIX + masterNameWithoutPrefix + VIRTUALBOX_NODE_NAME_SEPARATOR |
| + nodeSpec.getTag() + VIRTUALBOX_NODE_NAME_SEPARATOR + nodeSpec.getName(); |
| |
| VmSpec cloneVmSpec = VmSpec.builder().id(cloneName).name(cloneName).memoryMB(512).cleanUpMode(CleanupMode.Full) |
| .forceOverwrite(true).build(); |
| |
| // CASE NAT + HOST-ONLY |
| NetworkAdapter natAdapter = NetworkAdapter.builder().networkAttachmentType(NetworkAttachmentType.NAT) |
| .build(); |
| NetworkInterfaceCard natIfaceCard = NetworkInterfaceCard.builder().addNetworkAdapter(natAdapter).slot(1L).build(); |
| |
| NetworkAdapter hostOnlyAdapter = NetworkAdapter.builder().networkAttachmentType(NetworkAttachmentType.HostOnly) |
| .build(); |
| |
| // create new hostOnly interface if needed, otherwise use the one already there with dhcp enabled ... |
| String hostOnlyIfName = getHostOnlyIfOrCreate(); |
| |
| NetworkInterfaceCard hostOnlyIfaceCard = NetworkInterfaceCard.builder().addNetworkAdapter(hostOnlyAdapter) |
| .addHostInterfaceName(hostOnlyIfName).slot(0L).build(); |
| |
| NetworkSpec networkSpec = createNetworkSpecForHostOnlyNATNICs(natIfaceCard, hostOnlyIfaceCard); |
| |
| CloneSpec cloneSpec = CloneSpec.builder().linked(true).master(master.getMachine()).network(networkSpec) |
| .vm(cloneVmSpec).build(); |
| |
| IMachine cloned = cloner.apply(cloneSpec); |
| |
| machineController.ensureMachineIsLaunched(cloneVmSpec.getVmName()); |
| |
| // IMachineToNodeMetadata produces the final ip's but these need to be set before so we build a |
| // NodeMetadata just for the sake of running the gshadow and setip scripts |
| NodeMetadata partialNodeMetadata = buildPartialNodeMetadata(cloned); |
| |
| // see DeleteGShadowLock for a detailed explanation |
| machineUtils.runScriptOnNode(partialNodeMetadata, new DeleteGShadowLock(), RunScriptOptions.NONE); |
| |
| // CASE NAT + HOST-ONLY |
| machineUtils.runScriptOnNode(partialNodeMetadata, new EnableNetworkInterface(natIfaceCard), RunScriptOptions.NONE); |
| |
| |
| // TODO get credentials from somewhere else (they are also HC in |
| // IMachineToSshClient) |
| NodeAndInitialCredentials<IMachine> nodeAndInitialCredentials = new NodeAndInitialCredentials<IMachine>(cloned, |
| cloneName, LoginCredentials.builder().user("toor").password("password").authenticateSudo(true).build()); |
| |
| return nodeAndInitialCredentials; |
| } |
| |
| private NodeMetadata buildPartialNodeMetadata(IMachine clone) { |
| NodeMetadataBuilder nodeMetadataBuilder = new NodeMetadataBuilder(); |
| nodeMetadataBuilder.id(clone.getName()); |
| nodeMetadataBuilder.state(VirtualBoxComputeServiceContextModule.machineToNodeState.get(clone.getState())); |
| nodeMetadataBuilder.publicAddresses(ImmutableSet.of(machineUtils.getIpAddressFromHostOnlyNIC(clone.getName()))); |
| |
| LoginCredentials loginCredentials = new LoginCredentials("toor", "password", null, true); |
| nodeMetadataBuilder.credentials(loginCredentials); |
| |
| return nodeMetadataBuilder.build(); |
| } |
| |
| private NetworkSpec createNetworkSpecForHostOnlyNATNICs(NetworkInterfaceCard natIfaceCard, |
| NetworkInterfaceCard hostOnlyIfaceCard) { |
| return NetworkSpec.builder().addNIC(natIfaceCard).addNIC(hostOnlyIfaceCard).build(); |
| } |
| |
| /** |
| * @return |
| */ |
| private String getHostOnlyIfOrCreate() { |
| |
| IHostNetworkInterface availableHostInterfaceIf = returnExistingHostNetworkInterfaceWithDHCPenabledOrNull(manager |
| .get().getVBox().getHost().getNetworkInterfaces()); |
| if (availableHostInterfaceIf==null) { |
| final String hostOnlyIfName = createHostOnlyIf(); |
| assignDHCPtoHostOnlyInterface(hostOnlyIfName); |
| return hostOnlyIfName; |
| } else { |
| return availableHostInterfaceIf.getName(); |
| } |
| |
| } |
| |
| private void assignDHCPtoHostOnlyInterface(final String hostOnlyIfName) { |
| List<IHostNetworkInterface> availableNetworkInterfaces = manager.get().getVBox().getHost() |
| .getNetworkInterfaces(); |
| |
| IHostNetworkInterface iHostNetworkInterfaceWithHostOnlyIfName = Iterables.getOnlyElement(Iterables.filter(availableNetworkInterfaces, new Predicate<IHostNetworkInterface>() { |
| |
| @Override |
| public boolean apply(IHostNetworkInterface iHostNetworkInterface) { |
| return iHostNetworkInterface.getName().equals(hostOnlyIfName); |
| } |
| })); |
| |
| String hostOnlyIfIpAddress = iHostNetworkInterfaceWithHostOnlyIfName.getIPAddress(); |
| String dhcpIpAddress = hostOnlyIfIpAddress.substring(0, hostOnlyIfIpAddress.lastIndexOf(".")) + ".254"; |
| String dhcpNetmask = "255.255.255.0"; |
| String dhcpLowerIp = hostOnlyIfIpAddress.substring(0, hostOnlyIfIpAddress.lastIndexOf(".")) + ".2"; |
| String dhcpUpperIp = hostOnlyIfIpAddress.substring(0, hostOnlyIfIpAddress.lastIndexOf(".")) + ".253"; |
| |
| ExecResponse response = runScriptOnNodeFactory |
| .create(host.get(), |
| Statements.exec(String |
| .format("VBoxManage dhcpserver add --ifname %s --ip %s --netmask %s --lowerip %s --upperip %s --enable", |
| hostOnlyIfName, dhcpIpAddress, dhcpNetmask, dhcpLowerIp, dhcpUpperIp)), runAsRoot(false).wrapInInitScript(false)).init().call(); |
| checkState(response.getExitStatus()==0); |
| /* |
| runScriptOnNodeFactory |
| .create(host.get(), |
| Statements.exec(String.format("VBoxManage hostonlyif ipconfig %s --ip %s", |
| hostOnlyIfName, hostOnlyIfIpAddress)), runAsRoot(false).wrapInInitScript(false)).init().call(); |
| */ |
| } |
| |
| private String createHostOnlyIf() { |
| final String hostOnlyIfName; |
| ExecResponse createHostOnyResponse = runScriptOnNodeFactory |
| .create(host.get(), Statements.exec("VBoxManage hostonlyif create"), |
| runAsRoot(false).wrapInInitScript(false)).init().call(); |
| String output = createHostOnyResponse.getOutput(); |
| checkState(createHostOnyResponse.getExitStatus()==0); |
| checkState(output.contains("'"), "cannot create hostonlyif"); |
| hostOnlyIfName = output.substring(output.indexOf("'") + 1, output.lastIndexOf("'")); |
| return hostOnlyIfName; |
| } |
| |
| private IHostNetworkInterface returnExistingHostNetworkInterfaceWithDHCPenabledOrNull(Iterable<IHostNetworkInterface> availableNetworkInterfaces) { |
| checkNotNull(availableNetworkInterfaces); |
| return Iterables.getFirst(filterAvailableNetworkInterfaceByHostOnlyAndDHCPenabled(availableNetworkInterfaces), null); |
| } |
| |
| /** |
| * @param availableNetworkInterfaces |
| * @param hostOnlyIfIpAddress |
| * @return |
| */ |
| private Iterable<IHostNetworkInterface> filterAvailableNetworkInterfaceByHostOnlyAndDHCPenabled(Iterable<IHostNetworkInterface> availableNetworkInterfaces) { |
| Iterable<IHostNetworkInterface> filteredNetworkInterfaces = Iterables.filter(availableNetworkInterfaces, new Predicate<IHostNetworkInterface>() { |
| @Override |
| public boolean apply(IHostNetworkInterface iHostNetworkInterface) { |
| // this is an horrible workaround cause iHostNetworkInterface.getDhcpEnabled is working only for windows host |
| boolean match = false; |
| List<IDHCPServer> availableDHCPservers = manager.get().getVBox().getDHCPServers(); |
| for (IDHCPServer idhcpServer : availableDHCPservers) { |
| if(idhcpServer.getEnabled() && idhcpServer.getNetworkName().equals(iHostNetworkInterface.getNetworkName())) |
| match = true; |
| } |
| return iHostNetworkInterface.getInterfaceType().equals(HostNetworkInterfaceType.HostOnly) && |
| match; |
| } |
| }); |
| return filteredNetworkInterfaces; |
| } |
| |
| } |