/*
* 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.compute;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
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.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.HardwareBuilder;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.Location;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.logging.Logger;
import org.jclouds.virtualbox.domain.Master;
import org.jclouds.virtualbox.domain.NodeSpec;
import org.jclouds.virtualbox.domain.YamlImage;
import org.jclouds.virtualbox.functions.admin.UnregisterMachineIfExistsAndForceDeleteItsMedia;
import org.jclouds.virtualbox.util.MachineController;
import org.virtualbox_4_2.IMachine;
import org.virtualbox_4_2.IProgress;
import org.virtualbox_4_2.ISession;
import org.virtualbox_4_2.MachineState;
import org.virtualbox_4_2.VBoxException;
import org.virtualbox_4_2.VirtualBoxManager;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.Singleton;
/**
* Defines the connection between the {@link org.virtualbox_4_2.VirtualBoxManager} implementation
* and the jclouds {@link org.jclouds.compute.ComputeService}
*
* @author Mattias Holmqvist, Andrea Turli, David Alves
*/
@Singleton
public class VirtualBoxComputeServiceAdapter implements ComputeServiceAdapter<IMachine, Hardware, Image, Location> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final Supplier<VirtualBoxManager> manager;
private final Map<Image, YamlImage> imagesToYamlImages;
private final LoadingCache<Image, Master> mastersLoader;
private final Function<NodeSpec, NodeAndInitialCredentials<IMachine>> cloneCreator;
private final Function<IMachine, Image> imachineToImage;
private final MachineController machineController;
@Inject
public VirtualBoxComputeServiceAdapter(Supplier<VirtualBoxManager> manager,
Supplier<Map<Image, YamlImage>> imagesMapper, LoadingCache<Image, Master> mastersLoader,
Function<NodeSpec, NodeAndInitialCredentials<IMachine>> cloneCreator,
Function<IMachine, Image> imachineToImage,
MachineController machineController) {
this.manager = checkNotNull(manager, "virtualbox manager can't be null");
this.imagesToYamlImages = imagesMapper.get();
this.mastersLoader = mastersLoader;
this.cloneCreator = cloneCreator;
this.imachineToImage = imachineToImage;
this.machineController = machineController;
}
@Override
public NodeAndInitialCredentials<IMachine> createNodeWithGroupEncodedIntoName(String tag, String name,
Template template) {
try {
checkState(!tag.contains(VIRTUALBOX_NODE_NAME_SEPARATOR), "tag names cannot contain \""
+ VIRTUALBOX_NODE_NAME_SEPARATOR + "\"");
checkState(!name.contains(VIRTUALBOX_NODE_NAME_SEPARATOR), "node names cannot contain \""
+ VIRTUALBOX_NODE_NAME_SEPARATOR + "\"");
Master master = mastersLoader.get(template.getImage());
checkState(master != null, "could not find a master for image: " + template.getImage());
NodeSpec nodeSpec = NodeSpec.builder().master(master).name(name).tag(tag).template(template).build();
return cloneCreator.apply(nodeSpec);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public Iterable<IMachine> listNodes() {
return Iterables.filter(manager.get().getVBox().getMachines(), new Predicate<IMachine>() {
@Override
public boolean apply(IMachine arg0) {
return arg0.getName().startsWith(VIRTUALBOX_NODE_PREFIX);
}
});
}
@Override
public Iterable<IMachine> listNodesByIds(final Iterable<String> ids) {
return filter(listNodes(), new Predicate<IMachine>() {
@Override
public boolean apply(IMachine machine) {
return contains(ids, machine.getId());
}
});
}
@Override
public Iterable<Hardware> listHardwareProfiles() {
Set<Hardware> hardware = Sets.newLinkedHashSet();
hardware.add(new HardwareBuilder().ids("t1.micro").hypervisor("VirtualBox").name("t1.micro").ram(512).build());
hardware.add(new HardwareBuilder().ids("m1.small").hypervisor("VirtualBox").name("m1.small").ram(1024).build());
hardware.add(new HardwareBuilder().ids("m1.medium").hypervisor("VirtualBox").name("m1.medium").ram(3840).build());
hardware.add(new HardwareBuilder().ids("m1.large").hypervisor("VirtualBox").name("m1.large").ram(7680).build());
return hardware;
}
@Override
public Iterable<Image> listImages() {
// the set of image vm names that were (or could be) built from the yaml file
final Set<String> imagesFromYamlNames = Sets.newHashSet(Iterables.transform(imagesToYamlImages.keySet(),
new Function<Image, String>() {
@Override
public String apply(Image input) {
return VIRTUALBOX_IMAGE_PREFIX + input.getId();
}
}));
// IMachines that were not built from the yaml file transformed to Images
Set<Image> imagesFromCloning = Sets.newHashSet(Iterables.transform(
Iterables.filter(imageMachines(), new Predicate<IMachine>() {
@Override
public boolean apply(IMachine input) {
return !imagesFromYamlNames.contains(input.getName());
}
}), imachineToImage));
// final set of images are those from yaml and those from vbox that were not a transformation
// of the yaml ones
return Sets.union(imagesToYamlImages.keySet(), imagesFromCloning);
}
private Iterable<IMachine> imageMachines() {
final Predicate<? super IMachine> imagePredicate = new Predicate<IMachine>() {
@Override
public boolean apply(@Nullable IMachine iMachine) {
return iMachine.getName().startsWith(VIRTUALBOX_IMAGE_PREFIX);
}
};
final Iterable<IMachine> imageMachines = filter(manager.get().getVBox().getMachines(), imagePredicate);
return imageMachines;
}
@Override
public Iterable<Location> listLocations() {
// Not using the adapter to determine locations
return ImmutableSet.<Location> of();
}
@Override
public IMachine getNode(String vmName) {
try {
return manager.get().getVBox().findMachine(vmName);
} catch (VBoxException e) {
if (e.getMessage().contains("Could not find a registered machine named")) {
return null;
}
throw Throwables.propagate(e);
}
}
@Override
public Image getImage(String vmName) {
IMachine image = getNode(vmName);
if (image == null)
return null;
return imachineToImage.apply(image);
}
@Override
public synchronized void destroyNode(String vmName) {
IMachine machine = manager.get().getVBox().findMachine(vmName);
powerDownMachine(machine);
try {
new UnregisterMachineIfExistsAndForceDeleteItsMedia().apply(machine);
} catch (Exception e) {
logger.error("Machine (%s) not unregistered!", vmName);
}
}
@Override
public void rebootNode(String vmName) {
IMachine machine = manager.get().getVBox().findMachine(vmName);
powerDownMachine(machine);
launchVMProcess(machine, manager.get().getSessionObject());
}
@Override
public void resumeNode(String vmName) {
IMachine machine = manager.get().getVBox().findMachine(vmName);
ISession machineSession;
try {
machineSession = manager.get().openMachineSession(machine);
machineSession.getConsole().resume();
machineSession.unlockMachine();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public void suspendNode(String vmName) {
IMachine machine = manager.get().getVBox().findMachine(vmName);
ISession machineSession;
try {
machineSession = manager.get().openMachineSession(machine);
machineSession.getConsole().pause();
machineSession.unlockMachine();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
private void launchVMProcess(IMachine machine, ISession session) {
IProgress prog = machine.launchVMProcess(session, "gui", "");
prog.waitForCompletion(-1);
session.unlockMachine();
}
private void powerDownMachine(IMachine machine) {
try {
if (machine.getState() == MachineState.PoweredOff) {
logger.debug("vm was already powered down: ", machine.getId());
return;
}
logger.debug("<< powering down vm(%s)", machine.getName());
machineController.ensureMachineHasPowerDown(machine.getName());
} catch (Exception e) {
logger.error(e, "problem in powering down vm(%s)", machine.getName());
throw Throwables.propagate(e);
}
}
}