/** * Copyright (C) 2010-2013 Eugen Feller, INRIA <eugen.feller@inria.fr> * * This file is part of Snooze, a scalable, autonomic, and * energy-aware virtual machine (VM) management framework. * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses>. */ package org.inria.myriads.snoozenode.groupmanager.virtualclustermanager; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.inria.myriads.snoozecommon.communication.NetworkAddress; import org.inria.myriads.snoozecommon.communication.groupmanager.GroupManagerDescription; import org.inria.myriads.snoozecommon.communication.localcontroller.LocalControllerDescription; import org.inria.myriads.snoozecommon.communication.virtualcluster.VirtualMachineMetaData; import org.inria.myriads.snoozecommon.communication.virtualcluster.status.VirtualMachineErrorCode; import org.inria.myriads.snoozecommon.communication.virtualcluster.status.VirtualMachineStatus; import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualClusterSubmissionRequest; import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualClusterSubmissionResponse; import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualMachineTemplate; import org.inria.myriads.snoozecommon.exception.VirtualClusterParserException; import org.inria.myriads.snoozecommon.globals.Globals; import org.inria.myriads.snoozecommon.guard.Guard; import org.inria.myriads.snoozecommon.parser.VirtualClusterParserFactory; import org.inria.myriads.snoozecommon.parser.api.VirtualClusterParser; import org.inria.myriads.snoozecommon.virtualmachineimage.VirtualMachineImage; import org.inria.myriads.snoozeimages.communication.rest.CommunicatorFactory; import org.inria.myriads.snoozeimages.communication.rest.api.ImageRepositoryAPI; import org.inria.myriads.snoozeimages.communication.rest.api.ImagesRepositoryAPI; import org.inria.myriads.snoozenode.configurator.api.NodeConfiguration; import org.inria.myriads.snoozenode.configurator.scheduler.GroupLeaderSchedulerSettings; import org.inria.myriads.snoozenode.database.api.GroupLeaderRepository; import org.inria.myriads.snoozenode.groupmanager.estimator.ResourceDemandEstimator; import org.inria.myriads.snoozenode.groupmanager.leaderpolicies.GroupLeaderPolicyFactory; import org.inria.myriads.snoozenode.groupmanager.leaderpolicies.dispatching.DispatchingPolicy; import org.inria.myriads.snoozenode.groupmanager.leaderpolicies.enums.Dispatching; import org.inria.myriads.snoozenode.groupmanager.virtualclustermanager.listener.VirtualClusterSubmissionListener; import org.inria.myriads.snoozenode.groupmanager.virtualclustermanager.worker.VirtualClusterSubmissionWorker; import org.inria.myriads.snoozenode.groupmanager.virtualnetworkmanager.VirtualNetworkFactory; import org.inria.myriads.snoozenode.groupmanager.virtualnetworkmanager.api.VirtualNetworkManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Virtual Cluster Manager. * * @author Eugen Feller */ public final class VirtualClusterManager implements VirtualClusterSubmissionListener { /** Logging instance. */ private Logger log_ = LoggerFactory.getLogger(VirtualClusterManager.class); /** Map which holds the virtual cluster responses. */ private Map<String, VirtualClusterSubmissionResponse> virtualClusterResponses_; /** Queue for tasks. */ private Queue<VirtualClusterSubmissionWorker> workerQueue_; /** Virtual cluster dispatching. */ private DispatchingPolicy virtualClusterDispatching_; /** Node configuration. */ private NodeConfiguration nodeConfiguration_; /** Resource demand estimator. */ private ResourceDemandEstimator estimator_; /** Group leader repository. */ private GroupLeaderRepository repository_; /** Virtual network manager. */ private VirtualNetworkManager virtualNetworkManager_; /** * Constructor. * * @param nodeConfiguration The node configuration * @param repository The group leader repository * @param estimator The resource demand estimator */ public VirtualClusterManager(NodeConfiguration nodeConfiguration, GroupLeaderRepository repository, ResourceDemandEstimator estimator) { log_.debug("Initializing the virtual cluster manager"); nodeConfiguration_ = nodeConfiguration; repository_ = repository; estimator_ = estimator; virtualClusterResponses_ = new HashMap<String, VirtualClusterSubmissionResponse>(); workerQueue_ = new LinkedList<VirtualClusterSubmissionWorker>(); virtualNetworkManager_ = VirtualNetworkFactory.newVirtualNetworkManager(repository); initializeDispatchingPolicy(); } /** * Initializes the dispatching policy. */ private void initializeDispatchingPolicy() { GroupLeaderSchedulerSettings schedulerSettings = nodeConfiguration_.getGroupLeaderScheduler(); Dispatching virtualClusterDispatch = schedulerSettings.getDispatchingPolicy(); virtualClusterDispatching_ = GroupLeaderPolicyFactory.newVirtualClusterPlacement(virtualClusterDispatch, estimator_); } /** * Generates the virtual machine meta data. * * @param submissionRequest The submission request * @return The virtual machine meta data * @throws VirtualClusterParserException The virtual cluster parser exception */ protected ArrayList<VirtualMachineMetaData> generateVirtualMachineMetaData(VirtualClusterSubmissionRequest submissionRequest) throws VirtualClusterParserException { Guard.check(submissionRequest); log_.debug("Generating virtual machine meta data"); VirtualClusterParser parser = VirtualClusterParserFactory.newVirtualClusterParser(); log_.debug("parser initialized"); ArrayList<VirtualMachineMetaData> metaData = new ArrayList<VirtualMachineMetaData>(); List<VirtualMachineTemplate> virtualMachineDescriptions = submissionRequest.getVirtualMachineTemplates(); for (VirtualMachineTemplate description : virtualMachineDescriptions) { VirtualMachineMetaData virtualMachine; try { // generate VirtualMachineMetaData from description virtualMachine = parser.parseDescription(description); generateDiskInfo(virtualMachine, description); } catch (Exception exception) { log_.error("Unable to generate meta data for virtual machine "); continue; } setVirtualMachineLocation(virtualMachine, description.getHostId()); metaData.add(virtualMachine); } return metaData; } private void generateDiskInfo(VirtualMachineMetaData virtualMachine, VirtualMachineTemplate description) throws Exception { String libvirtTemplate = description.getLibVirtTemplate(); if (StringUtils.isBlank(libvirtTemplate)) { log_.debug("Generate the disk information from parameters"); NetworkAddress address = nodeConfiguration_.getImageRepositorySettings().getImageRepositoryAddress(); ImageRepositoryAPI communicator = CommunicatorFactory.newImageRepositoryCommunicator(address, description.getImageId()); VirtualMachineImage image = communicator.getImage(); if (image == null) { throw new Exception("Unable to fetch image info"); } virtualMachine.setImage(image); log_.debug("Disk information generated"); } else { log_.debug("Generate the disk information from libvirt template"); VirtualClusterParser parser = VirtualClusterParserFactory.newVirtualClusterParser(); VirtualMachineImage image = parser.getFirstDiskImage(description.getLibVirtTemplate()); virtualMachine.setImage(image); } } /** * * Sets the virtual machine location in case of the user force destination for the virtual machine. * * @param virtualMachine virtual machine meta data (under construction). * @param hostId hostId */ protected void setVirtualMachineLocation(VirtualMachineMetaData virtualMachine, String hostId) { Guard.check(virtualMachine, hostId); if (hostId.equals(Globals.DEFAULT_INITIALIZATION) || StringUtils.isBlank(hostId)) { log_.debug("No binding : fallback to default location"); return; } ArrayList<GroupManagerDescription> groupManagers = repository_.getGroupManagerDescriptions(0); for (GroupManagerDescription groupManager : groupManagers) { if (groupManager.getId().equals(hostId)) { log_.debug("Found a binding : the virtual machine will be started on group manager" + hostId); virtualMachine.getVirtualMachineLocation().setGroupManagerId(hostId); return; } else { HashMap<String, LocalControllerDescription> localControllers = groupManager.getLocalControllers(); log_.debug(String.format("Lookup on %d local controllers of the group manager %s ", localControllers.size(), groupManager.getId())); for (LocalControllerDescription localController : localControllers.values()) { if (localController.getId().equals(hostId)) { log_.debug(String.format( "Found a binding : the virtual machine will be started on local controller %s", hostId)); virtualMachine.getVirtualMachineLocation().setGroupManagerId(groupManager.getId()); virtualMachine.getVirtualMachineLocation().setLocalControllerId(hostId); return; } } } } log_.debug("Binding not found : fallback to default location"); virtualMachine.setStatus(VirtualMachineStatus.ERROR); virtualMachine.setErrorCode(VirtualMachineErrorCode.INVALID_HOST_ID); } /** * Dispatches the virtual cluster submission request. * * @param submissionRequest The submission request * @return The task identifier */ public synchronized String startVirtualClusterSubmission(VirtualClusterSubmissionRequest submissionRequest) { Guard.check(submissionRequest); log_.debug("Executing the virtual cluster start request"); String taskIdentifier = null; ArrayList<VirtualMachineMetaData> virtualMachines = null; try { virtualMachines = generateVirtualMachineMetaData(submissionRequest); boolean isAssigned = virtualNetworkManager_.assignIpAddresses(virtualMachines); if (!isAssigned) { log_.error("Failed to assign IP addresses!"); return null; } } catch (Exception exception) { log_.error("Error generating virtual machine meta data", exception); } finally { if (virtualMachines == null) { log_.debug("Returning null taskIdentifier"); return null; } taskIdentifier = startSubmissionWorker(virtualMachines); } return taskIdentifier; } /** * Starts the submission worker. * * @param virtualMachines The virtual machines * @return The task identifier */ private String startSubmissionWorker(ArrayList<VirtualMachineMetaData> virtualMachines) { String taskIdentifier = UUID.randomUUID().toString(); VirtualClusterSubmissionWorker submission = new VirtualClusterSubmissionWorker(taskIdentifier, virtualMachines, nodeConfiguration_, virtualClusterDispatching_, repository_, estimator_, this); if (workerQueue_.size() == 0) { log_.debug(String.format("Starting virtual cluster submission thread for task: %s!", taskIdentifier)); new Thread(submission, "SubmissionWorker : " + taskIdentifier).start(); } workerQueue_.add(submission); return taskIdentifier; } /** * Adds a virtual cluster response. * * @param taskIdentifier The task identifier * @param response The virtual cluster response */ @Override public synchronized void onVirtualClusterSubmissionFinished(String taskIdentifier, VirtualClusterSubmissionResponse response) { Guard.check(taskIdentifier, response); log_.debug(String.format("Adding virtual cluster response for: %s", taskIdentifier)); postVirtualClusterSubmission(response.getVirtualMachineMetaData()); virtualClusterResponses_.put(taskIdentifier, response); workerQueue_.poll(); VirtualClusterSubmissionWorker submissionWorker = workerQueue_.peek(); if (submissionWorker != null) { new Thread(submissionWorker, "SubmissionWorker : " + taskIdentifier).start(); } } /** * Called post virtual cluster submission. * * @param virtualMachines The virtual machine meta data */ private void postVirtualClusterSubmission(List<VirtualMachineMetaData> virtualMachines) { for (VirtualMachineMetaData metaData : virtualMachines) { boolean isRunning = metaData.getStatus().equals(VirtualMachineStatus.RUNNING); if (!isRunning && metaData.getErrorCode() != VirtualMachineErrorCode.NOT_ENOUGH_IP_ADDRESSES) { log_.debug("Releasing IP address!"); virtualNetworkManager_.releaseIpAddress(metaData); } } } /** * Returns virtual cluster response if available. * * @param taskIdentifier The task identifier * @return The virtual cluster response */ public VirtualClusterSubmissionResponse getVirtualClusterResponse(String taskIdentifier) { Guard.check(taskIdentifier); VirtualClusterSubmissionResponse virtualClusterResponse = virtualClusterResponses_.get(taskIdentifier); if (virtualClusterResponse != null) { virtualClusterResponses_.remove(taskIdentifier); } return virtualClusterResponse; } }