/**
* 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.virtualmachinemanager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.inria.myriads.snoozecommon.communication.NetworkAddress;
import org.inria.myriads.snoozecommon.communication.rest.CommunicatorFactory;
import org.inria.myriads.snoozecommon.communication.rest.api.LocalControllerAPI;
import org.inria.myriads.snoozecommon.communication.virtualcluster.VirtualMachineMetaData;
import org.inria.myriads.snoozecommon.communication.virtualcluster.status.VirtualMachineStatus;
import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualMachineLocation;
import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualMachineSubmissionRequest;
import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualMachineSubmissionResponse;
import org.inria.myriads.snoozecommon.communication.virtualmachine.ResizeRequest;
import org.inria.myriads.snoozecommon.guard.Guard;
import org.inria.myriads.snoozenode.configurator.api.NodeConfiguration;
import org.inria.myriads.snoozenode.configurator.scheduler.GroupManagerSchedulerSettings;
import org.inria.myriads.snoozenode.database.api.GroupManagerRepository;
import org.inria.myriads.snoozenode.groupmanager.estimator.ResourceDemandEstimator;
import org.inria.myriads.snoozenode.groupmanager.managerpolicies.GroupManagerPolicyFactory;
import org.inria.myriads.snoozenode.groupmanager.managerpolicies.placement.PlacementPolicy;
import org.inria.myriads.snoozenode.groupmanager.statemachine.VirtualMachineCommand;
import org.inria.myriads.snoozenode.groupmanager.statemachine.api.StateMachine;
import org.inria.myriads.snoozenode.groupmanager.virtualmachinemanager.listener.VirtualMachineManagerListener;
import org.inria.myriads.snoozenode.groupmanager.virtualmachinemanager.worker.VirtualMachineSubmissionWorker;
import org.inria.snoozenode.external.notifier.ExternalNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Virtual machine manager.
*
* @author Eugen Feller
*/
public final class VirtualMachineManager
implements VirtualMachineManagerListener
{
/** Define the logger. */
private static final Logger log_ = LoggerFactory.getLogger(VirtualMachineManager.class);
/** Group manager repository. */
private GroupManagerRepository repository_;
/** Virtual machine placement policy. */
private PlacementPolicy placementPolicy_;
/** State machine. */
private StateMachine stateMachine_;
/** Finsished submissions. */
private Map<String, VirtualMachineSubmissionResponse> submissionResponses_;
/** Number of monitoring entries. */
private int numberOfMonitoringEntries_;
/** Resource demand estimator. */
private ResourceDemandEstimator estimator_;
/** External Notifier. */
private ExternalNotifier externalNotifier_;
/**
* Constructor.
*
* @param nodeConfiguration The node configuration
* @param estimator The group manager repository
* @param groupManagerRepository The number of monitoring entries
* @param stateMachine The state machine
*/
public VirtualMachineManager(
NodeConfiguration nodeConfiguration,
ResourceDemandEstimator estimator,
GroupManagerRepository groupManagerRepository,
StateMachine stateMachine)
{
Guard.check(estimator);
log_.debug("Initializing virtual machine management");
GroupManagerSchedulerSettings schedulerSettings = nodeConfiguration.getGroupManagerScheduler();
externalNotifier_ = new ExternalNotifier(nodeConfiguration);
numberOfMonitoringEntries_ = estimator.getNumberOfMonitoringEntries();
repository_ = groupManagerRepository;
stateMachine_ = stateMachine;
submissionResponses_ = new HashMap<String, VirtualMachineSubmissionResponse>();
placementPolicy_ = GroupManagerPolicyFactory.newVirtualMachinePlacement(schedulerSettings,
estimator);
estimator_ = estimator;
}
/**
* Starts a virtual machine.
*
* @param submissionRequest The virtual machine description
* @return The task identifier
*/
public String start(VirtualMachineSubmissionRequest submissionRequest)
{
Guard.check(submissionRequest);
String taskIdentifier = UUID.randomUUID().toString();
VirtualMachineSubmissionWorker worker = new VirtualMachineSubmissionWorker(taskIdentifier,
numberOfMonitoringEntries_,
submissionRequest,
repository_,
placementPolicy_,
stateMachine_,
estimator_,
this,
externalNotifier_
);
new Thread(worker, "VirtualMachineManager : " + taskIdentifier).start();
return taskIdentifier;
}
/**
* Suspend a virtual machine.
*
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
private boolean suspend(VirtualMachineLocation location)
{
log_.debug(String.format("Suspending virtual machine %s", location.getVirtualMachineId()));
if (!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.RUNNING))
{
return false;
}
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
location.getVirtualMachineId()));
return false;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
boolean returnValue = communicator.suspendVirtualMachineOnRequest(location.getVirtualMachineId());
if (!returnValue)
{
log_.error(String.format("Unable to suspend virtual machine: %s", location.getVirtualMachineId()));
return false;
}
returnValue = repository_.changeVirtualMachineStatus(location, VirtualMachineStatus.PAUSED);
if (!returnValue)
{
log_.error("Unable to change the virtual machine status");
return false;
}
return true;
}
/**
* Resume a virtual machine.
*
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
private boolean resume(VirtualMachineLocation location)
{
log_.debug(String.format("Resuming virtual machine %s", location.getVirtualMachineId()));
if (!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.PAUSED))
{
return false;
}
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
location.getVirtualMachineId()));
return false;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
boolean isResumed = communicator.resumeVirtualMachine(location.getVirtualMachineId());
if (!isResumed)
{
log_.error(String.format("Unable to resume virtual machine: %s", location.getVirtualMachineId()));
return false;
}
repository_.changeVirtualMachineStatus(location, VirtualMachineStatus.RUNNING);
return true;
}
/**
* Shutdown a virtual machine.
*
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
private boolean shutdown(VirtualMachineLocation location)
{
String virtualMachineId = location.getVirtualMachineId();
log_.debug(String.format("Shutdown down virtual machine %s", virtualMachineId));
if (!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.RUNNING) &&
!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.SHUTDOWN_PENDING))
{
return false;
}
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
virtualMachineId));
return false;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
boolean isShutdown = communicator.shutdownVirtualMachine(virtualMachineId);
if (!isShutdown)
{
log_.error(String.format("Unable to shutdown virtual machine: %s", virtualMachineId));
return false;
}
boolean isChanged = repository_.changeVirtualMachineStatus(location, VirtualMachineStatus.SHUTDOWN_PENDING);
if (!isChanged)
{
log_.error("Failed to change virtual machine status");
return false;
}
return true;
}
/**
* Reboot a virtual machine.
*
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
private boolean reboot(VirtualMachineLocation location)
{
String virtualMachineId = location.getVirtualMachineId();
log_.debug(String.format("Rebooting virtual machine %s", virtualMachineId));
if (!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.RUNNING))
{
return false;
}
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
virtualMachineId));
return false;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
boolean isRebooted = communicator.rebootVirtualMachine(virtualMachineId);
if (!isRebooted)
{
log_.error(String.format("Unable to reboot virtual machine: %s", virtualMachineId));
return false;
}
boolean isChanged = repository_.changeVirtualMachineStatus(location, VirtualMachineStatus.RUNNING);
if (!isChanged)
{
log_.error("Failed to change virtual machine status");
return false;
}
return true;
}
/**
* Destroy a virtual machine.
*
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
private boolean destroy(VirtualMachineLocation location)
{
log_.debug(String.format("Destroying virtual machine %s", location.getVirtualMachineId()));
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
location.getVirtualMachineId()));
return false;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
boolean returnValue = communicator.destroyVirtualMachine(location.getVirtualMachineId());
if (!returnValue)
{
log_.error(String.format("Unable to destroy virtual machine: %s", location.getVirtualMachineId()));
return false;
}
boolean isChanged = repository_.changeVirtualMachineStatus(location, VirtualMachineStatus.SHUTDOWN_PENDING);
if (!isChanged)
{
log_.error("Failed to change virtual machine status");
return false;
}
log_.debug("Destroy was successfull!");
return true;
}
/**
* Adds a virtual cluster response.
*
* @param taskIdentifier The task identifier
* @param submissionResponse The virtual machine submisson response
*/
@Override
public void onSubmissionFinished(String taskIdentifier, VirtualMachineSubmissionResponse submissionResponse)
{
Guard.check(taskIdentifier, submissionResponse);
log_.debug(String.format("Adding submission %s response", taskIdentifier));
submissionResponses_.put(taskIdentifier, submissionResponse);
stateMachine_.onVirtualMachineSubmissionFinished();
}
/**
* Returns virtual cluster response if available.
*
* @param taskIdentifier The submission task identifier
* @return The virtual machine submission response
*/
public VirtualMachineSubmissionResponse getVirtualMachineSubmissionResponse(String taskIdentifier)
{
Guard.check(taskIdentifier);
VirtualMachineSubmissionResponse submissionResponse = submissionResponses_.get(taskIdentifier);
if (submissionResponse != null)
{
submissionResponses_.remove(taskIdentifier);
}
log_.debug(String.format("Returning virtual machine submission response: %s", submissionResponse));
return submissionResponse;
}
/**
* Processes the control command.
*
* @param command The control command
* @param location The virtual machine location
* @return true if everything ok, false otherwise
*/
public boolean processControlCommand(VirtualMachineCommand command, VirtualMachineLocation location)
{
Guard.check(command, location);
log_.debug(String.format("Starting virtual machine control command: %s processing", command));
boolean isProcessed = false;
switch (command)
{
case SUSPEND:
isProcessed = suspend(location);
break;
case RESUME:
isProcessed = resume(location);
break;
case SHUTDOWN:
isProcessed = shutdown(location);
break;
case REBOOT:
isProcessed = reboot(location);
break;
case DESTROY:
isProcessed = destroy(location);
break;
default:
log_.error(String.format("Wrong command specified: %s", command));
}
return isProcessed;
}
/**
*
* Resizes a resize request.
*
* @param resizeRequest The resize request.
* @return True if everything's fine, false otherwise.
*/
public VirtualMachineMetaData resizeVirtualMachine(ResizeRequest resizeRequest)
{
//we can only request for less resources.
VirtualMachineLocation location = resizeRequest.getVirtualMachineLocation();
String virtualMachineId = location.getVirtualMachineId();
log_.debug(String.format("Resizing virtual machine %s", virtualMachineId));
if (!repository_.checkVirtualMachineStatus(location, VirtualMachineStatus.RUNNING))
{
return null;
}
NetworkAddress localController = repository_.getLocalControllerControlDataAddress(location);
if (localController == null)
{
log_.debug(String.format("Unable to get local controller description from virtual machine: %s",
virtualMachineId));
return null;
}
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localController);
VirtualMachineMetaData newVirtualMachineMetaData =
communicator.resizeVirtualMachine(resizeRequest);
return newVirtualMachineMetaData;
}
}