/**
* 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.migration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.inria.myriads.snoozecommon.communication.NetworkAddress;
import org.inria.myriads.snoozecommon.communication.localcontroller.LocalControllerDescription;
import org.inria.myriads.snoozecommon.communication.localcontroller.hypervisor.HypervisorSettings;
import org.inria.myriads.snoozecommon.communication.rest.CommunicatorFactory;
import org.inria.myriads.snoozecommon.communication.rest.api.GroupManagerAPI;
import org.inria.myriads.snoozecommon.communication.rest.api.LocalControllerAPI;
import org.inria.myriads.snoozecommon.communication.virtualcluster.VirtualMachineMetaData;
import org.inria.myriads.snoozecommon.communication.virtualcluster.migration.MigrationRequest;
import org.inria.myriads.snoozecommon.communication.virtualcluster.submission.VirtualMachineLocation;
import org.inria.myriads.snoozecommon.guard.Guard;
import org.inria.myriads.snoozenode.database.api.GroupManagerRepository;
import org.inria.myriads.snoozenode.exception.MigrationPlanEnforcerException;
import org.inria.myriads.snoozenode.groupmanager.managerpolicies.reconfiguration.ReconfigurationPlan;
import org.inria.myriads.snoozenode.groupmanager.migration.listener.MigrationListener;
import org.inria.myriads.snoozenode.groupmanager.migration.listener.MigrationPlanListener;
import org.inria.myriads.snoozenode.groupmanager.migration.watchdog.MigrationWatchdog;
import org.inria.myriads.snoozenode.groupmanager.migration.worker.MigrationWorker;
import org.inria.myriads.snoozenode.message.ManagementMessage;
import org.inria.myriads.snoozenode.message.ManagementMessageType;
import org.inria.myriads.snoozenode.message.SystemMessage;
import org.inria.myriads.snoozenode.message.SystemMessageType;
import org.inria.myriads.snoozenode.util.ExternalNotifierUtils;
import org.inria.snoozenode.external.notifier.ExternalNotificationType;
import org.inria.snoozenode.external.notifier.ExternalNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Migration plan enforcer.
*
* @author Eugen Feller
*/
public final class MigrationPlanEnforcer
implements MigrationListener
{
/** Define the logger. */
private static final Logger log_ = LoggerFactory.getLogger(MigrationPlanEnforcer.class);
/** Number of monitoring entries. */
private static final int NUMBER_OF_MONITORING_ENTRIES = 1;
/** Group manager repository. */
private GroupManagerRepository groupManagerRepository_;
/** Migration plan listener. */
private MigrationPlanListener listener_;
/** Finished migrations. */
private List<MigrationRequest> finishedMigrations_;
/** Number of migrations. */
private int numberOfMigrations_;
/** External Sender. */
private ExternalNotifier externalNotifier_;
/**
* Constructor.
*
* @param groupManagerRepository The group manager repository.
* @param listener Migration plan listener.
* @param externalNotifier External notifier.
*/
public MigrationPlanEnforcer(
GroupManagerRepository groupManagerRepository,
MigrationPlanListener listener,
ExternalNotifier externalNotifier
)
{
Guard.check(groupManagerRepository);
log_.debug("Initializing the migration plan enforcer");
externalNotifier_ = externalNotifier;
groupManagerRepository_ = groupManagerRepository;
listener_ = listener;
finishedMigrations_ = new ArrayList<MigrationRequest>();
}
/**
* Proceses the migration request.
*
* @param migrationRequest The migration request
* @return true if everything ok, false otherwise
* @throws MigrationPlanEnforcerException
*/
private boolean processFinishedMigration(MigrationRequest migrationRequest)
throws MigrationPlanEnforcerException
{
String virtualMachineId = migrationRequest.getSourceVirtualMachineLocation().getVirtualMachineId();
log_.debug(String.format("Processing finished virtual machine %s migration", virtualMachineId));
if (!migrationRequest.isMigrated())
{
log_.debug(String.format("Virtual machine: %s was not migrated! Not good!", virtualMachineId));
return false;
}
NetworkAddress destinationAddress =
migrationRequest.getDestinationVirtualMachineLocation().getLocalControllerControlDataAddress();
if (destinationAddress == null)
{
log_.error("Local controller destination address invalid!");
throw new MigrationPlanEnforcerException("Local controller destination address invalid!");
}
String destinationId =
migrationRequest.getDestinationVirtualMachineLocation().getLocalControllerId();
if (destinationId == null)
{
log_.error("Local controller identifier is invalid!");
throw new MigrationPlanEnforcerException("Local controller identifier is invalid!");
}
VirtualMachineLocation oldLocation = migrationRequest.getSourceVirtualMachineLocation();
VirtualMachineLocation newLocation = migrationRequest.getDestinationVirtualMachineLocation();
newLocation.setVirtualMachineId(oldLocation.getVirtualMachineId());
boolean isUpdated = false;
VirtualMachineMetaData metaData = null;
log_.debug("Migration terminated : updating the group manager repository");
if (oldLocation.getGroupManagerId().equals(newLocation.getGroupManagerId()))
{
log_.debug("Migration terminated : updating the LOCAL group manager repository");
isUpdated = groupManagerRepository_.updateVirtualMachineLocation(oldLocation, newLocation);
metaData = groupManagerRepository_.getVirtualMachineMetaData(newLocation, NUMBER_OF_MONITORING_ENTRIES);
}
else
{
log_.debug("Migration terminated : updating the REMOTE group manager repository");
metaData = groupManagerRepository_.getVirtualMachineMetaData(oldLocation, NUMBER_OF_MONITORING_ENTRIES);
NetworkAddress newGroupManager = newLocation.getGroupManagerControlDataAddress();
log_.debug(String.format("Migration terminated : sending a request to update the group manager %s",
newGroupManager.getAddress()
));
boolean isRemoved = groupManagerRepository_.dropVirtualMachineData(oldLocation);
if (!isRemoved)
{
throw new MigrationPlanEnforcerException("Failed to update virtual machine location!");
}
metaData.setVirtualMachineLocation(newLocation);
GroupManagerAPI communicator = CommunicatorFactory.newGroupManagerCommunicator(newGroupManager);
isUpdated = communicator.addVirtualMachineAfterMigration(metaData);
}
if (!isUpdated)
{
throw new MigrationPlanEnforcerException("Failed to update virtual machine location!");
}
if (metaData == null)
{
throw new MigrationPlanEnforcerException("Virtual machine meta data is invalid!");
}
boolean isStarted = startVirtualMachineMonitoring(destinationAddress, metaData);
if (!isStarted)
{
throw new MigrationPlanEnforcerException("Unable to start virtual machine monitoring on destination!");
}
return true;
}
/**
* Starts virtual machine monitoring.
*
* @param localControllerAddress The local controller address
* @param metaData The virtual machine meta data
* @return true if everything ok, false otherwise
*/
private boolean startVirtualMachineMonitoring(NetworkAddress localControllerAddress,
VirtualMachineMetaData metaData)
{
log_.debug(String.format("Sending virtual machine monitoring start request to local controller %s: %d",
localControllerAddress.getAddress(), localControllerAddress.getPort()));
LocalControllerAPI communicator = CommunicatorFactory.newLocalControllerCommunicator(localControllerAddress);
return communicator.startVirtualMachineMonitoring(metaData);
}
/**
* Migration callback.
*
* @param migrationRequest The migration request
*/
@Override
public synchronized void onMigrationEnded(MigrationRequest migrationRequest)
{
log_.debug(String.format("Adding virtual machine %s to finished migrations list",
migrationRequest.getSourceVirtualMachineLocation().getVirtualMachineId()));
finishedMigrations_.add(migrationRequest);
if (numberOfMigrations_ == finishedMigrations_.size())
{
log_.debug("All migrations finished! Starting the processing phase!");
for (MigrationRequest finishedMigration : finishedMigrations_)
{
try
{
processFinishedMigration(finishedMigration);
}
catch (MigrationPlanEnforcerException exception)
{
log_.error("Exception during migration processing", exception);
ExternalNotifierUtils.send(
externalNotifier_,
ExternalNotificationType.MANAGEMENT,
new ManagementMessage(ManagementMessageType.ERROR , finishedMigration),
groupManagerRepository_.getGroupManagerId() + "." +
finishedMigration.getSourceVirtualMachineLocation().getLocalControllerId() + "." +
finishedMigration.getSourceVirtualMachineLocation().getVirtualMachineId() + "." +
"MIGRATION"
);
}
ExternalNotifierUtils.send(
externalNotifier_,
ExternalNotificationType.MANAGEMENT,
new ManagementMessage(ManagementMessageType.PROCESSED , finishedMigration),
groupManagerRepository_.getGroupManagerId() + "." +
finishedMigration.getSourceVirtualMachineLocation().getLocalControllerId() + "." +
finishedMigration.getSourceVirtualMachineLocation().getVirtualMachineId() + "." +
"MIGRATION"
);
}
log_.debug("Migration plan enforced!");
listener_.onMigrationPlanEnforced();
finishedMigrations_.clear();
}
}
/**
* Creates a migration request.
*
* @param sourceVirtualMachineLocation The source virtual machine location
* @param destinationVirtualMachineLocation The destination virtual machine location
* @param remoteHypervisorSettings The remote hypervisor settings
* @return The migration request
*/
private MigrationRequest createMigrationRequest(VirtualMachineLocation sourceVirtualMachineLocation,
VirtualMachineLocation destinationVirtualMachineLocation,
HypervisorSettings remoteHypervisorSettings)
{
log_.debug("Creating migration request");
MigrationRequest migrationRequest = new MigrationRequest();
migrationRequest.setDestinationVirtualMachineLocation(destinationVirtualMachineLocation);
migrationRequest.setSourceVirtualMachineLocation(sourceVirtualMachineLocation);
migrationRequest.setDestinationHypervisorSettings(remoteHypervisorSettings);
return migrationRequest;
}
/**
* Creates new virtual machine location from local controller description.
*
* @param sourceLocation The virtual machine location
* @param localControllerId The local controller identifier
* @param controlDataAddress The control data address
* @return The virtual machine location
*/
private VirtualMachineLocation createNewVirtualMachineLocation(VirtualMachineLocation sourceLocation,
String localControllerId,
NetworkAddress controlDataAddress)
{
VirtualMachineLocation location = new VirtualMachineLocation();
location.setVirtualMachineId(sourceLocation.getVirtualMachineId());
location.setLocalControllerId(localControllerId);
location.setLocalControllerControlDataAddress(controlDataAddress);
location.setGroupManagerControlDataAddress(sourceLocation.getGroupManagerControlDataAddress());
location.setGroupManagerId(sourceLocation.getGroupManagerId());
return location;
}
/**
* Migrates a virtual machine to the specified group manager.
*
* @param migrationRequest The migration request
*/
public void startMigration(MigrationRequest migrationRequest)
{
log_.debug(String.format("Starting to migrate virtual machine %s from local controller %s:%d to %s:%d",
migrationRequest.getSourceVirtualMachineLocation().getVirtualMachineId(),
migrationRequest.getSourceVirtualMachineLocation().getLocalControllerControlDataAddress().getAddress(),
migrationRequest.getSourceVirtualMachineLocation().getLocalControllerControlDataAddress().getPort(),
migrationRequest.getDestinationVirtualMachineLocation().getLocalControllerControlDataAddress().getAddress(),
migrationRequest.getDestinationVirtualMachineLocation().getLocalControllerControlDataAddress().getPort()));
ExternalNotifierUtils.send(
externalNotifier_,
ExternalNotificationType.MANAGEMENT,
new ManagementMessage(ManagementMessageType.PENDING , migrationRequest),
groupManagerRepository_.getGroupManagerId() + "." +
migrationRequest.getSourceVirtualMachineLocation().getLocalControllerId() + "." +
migrationRequest.getSourceVirtualMachineLocation().getVirtualMachineId() + "." +
"MIGRATION"
);
MigrationWorker migrationThread = new MigrationWorker(migrationRequest);
MigrationWatchdog watchdogThread = new MigrationWatchdog(migrationRequest, this);
migrationThread.addMigrationListener(watchdogThread);
migrationThread.addMigrationListener(this);
new Thread(migrationThread, "MigrationThread").start();
new Thread(watchdogThread, "MigratonWatchdog").start();
}
/**
* Enforces the migration plan.
*
* @param migrationPlan The migration plan
* @throws MigrationPlanEnforcerException Exception
*/
public void enforceMigrationPlan(ReconfigurationPlan migrationPlan)
throws MigrationPlanEnforcerException
{
if (migrationPlan == null)
{
throw new MigrationPlanEnforcerException("Migration plan is not available!");
}
if (migrationPlan.getNumberOfReleasedNodes() == 0)
{
throw new MigrationPlanEnforcerException("Migration plan does not yield to less hosts!");
}
numberOfMigrations_ = migrationPlan.getNumberOfMigrations();
if (numberOfMigrations_ == 0)
{
throw new MigrationPlanEnforcerException("The number of migrations is 0!");
}
log_.debug(String.format("Starting to enforce the migration plan. Number of used " +
"and released nodes is: %d / %d",
migrationPlan.getNumberOfUsedNodes(),
migrationPlan.getNumberOfReleasedNodes()));
log_.debug(String.format("Number of migrations: %s", numberOfMigrations_));
ExternalNotifierUtils.send(
externalNotifier_,
ExternalNotificationType.SYSTEM,
new SystemMessage(SystemMessageType.RECONFIGURATION, migrationPlan),
"groupmanager." + groupManagerRepository_.getGroupManagerId()
);
Map<VirtualMachineMetaData, LocalControllerDescription> mapping = migrationPlan.getMapping();
for (Map.Entry<VirtualMachineMetaData, LocalControllerDescription> entry : mapping.entrySet())
{
VirtualMachineMetaData virtualMachine = entry.getKey();
LocalControllerDescription localController = entry.getValue();
VirtualMachineLocation sourceLocation = virtualMachine.getVirtualMachineLocation();
VirtualMachineLocation destinationLocation =
createNewVirtualMachineLocation(sourceLocation,
localController.getId(),
localController.getControlDataAddress());
MigrationRequest migrationRequest = createMigrationRequest(sourceLocation,
destinationLocation,
localController.getHypervisorSettings());
startMigration(migrationRequest);
}
}
/**
*
* Start a migration.
* Call by the client.
*
* @param migrationRequest The migration request.
*/
public void startManualMigration(MigrationRequest migrationRequest)
{
numberOfMigrations_ = 1;
startMigration(migrationRequest);
}
}