All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.roboconf.dm.internal.api.impl.InstancesMngrImpl Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/**
 * Copyright 2015-2016 Linagora, Université Joseph Fourier, Floralis
 *
 * The present code is developed in the scope of the joint LINAGORA -
 * Université Joseph Fourier - Floralis research program and is designated
 * as a "Result" pursuant to the terms and conditions of the LINAGORA
 * - Université Joseph Fourier - Floralis research program. Each copyright
 * holder of Results enumerated here above fully & independently holds complete
 * ownership of the complete Intellectual Property rights applicable to the whole
 * of said Results, and may freely exploit it in any manner which does not infringe
 * the moral rights of the other copyright holders.
 *
 * Licensed 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 net.roboconf.dm.internal.api.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;

import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.beans.Instance.InstanceStatus;
import net.roboconf.core.model.helpers.InstanceHelpers;
import net.roboconf.core.utils.ResourceUtils;
import net.roboconf.core.utils.Utils;
import net.roboconf.dm.internal.api.IRandomMngr;
import net.roboconf.dm.internal.utils.ConfigurationUtils;
import net.roboconf.dm.management.ManagedApplication;
import net.roboconf.dm.management.api.IAutonomicMngr;
import net.roboconf.dm.management.api.IInstancesMngr;
import net.roboconf.dm.management.api.IMessagingMngr;
import net.roboconf.dm.management.api.INotificationMngr;
import net.roboconf.dm.management.api.ITargetHandlerResolver;
import net.roboconf.dm.management.api.ITargetsMngr;
import net.roboconf.dm.management.events.EventType;
import net.roboconf.dm.management.exceptions.ImpossibleInsertionException;
import net.roboconf.dm.management.exceptions.UnauthorizedActionException;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdAddInstance;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdChangeInstanceState;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdRemoveInstance;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdResynchronize;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdSendInstances;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdSetScopedInstance;
import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdUpdateProbeConfiguration;
import net.roboconf.target.api.TargetException;
import net.roboconf.target.api.TargetHandler;

/**
 * @author Noël - LIG
 * @author Pierre-Yves Gibello - Linagora
 * @author Vincent Zurczak - Linagora
 * @author Pierre Bourret - Université Joseph Fourier
 */
public class InstancesMngrImpl implements IInstancesMngr {

	private static final Object LOCK = new Object();
	private final Logger logger = Logger.getLogger( getClass().getName());

	private final IMessagingMngr messagingMngr;
	private final INotificationMngr notificationMngr;
	private final ITargetsMngr targetsMngr;
	private final IRandomMngr randomMngr;
	private IAutonomicMngr autonomicMngr;

	private ITargetHandlerResolver targetHandlerResolver;


	/**
	 * Constructor.
	 * @param targetsMngr
	 * @param messagingMngr
	 * @param notificationMngr
	 * @param randomMngr
	 */
	public InstancesMngrImpl(
			IMessagingMngr messagingMngr,
			INotificationMngr notificationMngr,
			ITargetsMngr targetsMngr,
			IRandomMngr randomMngr ) {

		this.targetsMngr = targetsMngr;
		this.messagingMngr = messagingMngr;
		this.notificationMngr = notificationMngr;
		this.randomMngr = randomMngr;
	}


	/**
	 * @param targetHandlerResolver the targetHandlerResolver to set
	 */
	public void setTargetHandlerResolver( ITargetHandlerResolver targetHandlerResolver ) {
		this.targetHandlerResolver = targetHandlerResolver;
	}


	/**
	 * @param autonomicMngr the autonomicMngr to set
	 */
	public void setRuleBasedHandler( IAutonomicMngr autonomicMngr ) {
		this.autonomicMngr = autonomicMngr;
	}


	@Override
	public void addInstance( ManagedApplication ma, Instance parentInstance, Instance instance )
	throws ImpossibleInsertionException, IOException {

		// Insert it, if possible
		this.messagingMngr.checkMessagingConfiguration();
		if( ! InstanceHelpers.tryToInsertChildInstance( ma.getApplication(), parentInstance, instance ))
			throw new ImpossibleInsertionException( instance.getName());

		// Generate values for random variables, if any
		this.randomMngr.generateRandomValues( ma.getApplication(), instance );

		// Send it to the agent
		this.logger.fine( "Instance " + InstanceHelpers.computeInstancePath( instance ) + " was successfully added in " + ma.getName() + "." );
		Instance scopedInstance = InstanceHelpers.findScopedInstance( instance );

		// Store the message because we want to make sure the message is not lost
		ma.storeAwaitingMessage( instance, new MsgCmdAddInstance( scopedInstance ));

		ConfigurationUtils.saveInstances( ma );
		this.notificationMngr.instance( instance, ma.getApplication(), EventType.CREATED );
	}


	@Override
	public void instanceWasUpdated( Instance instance, ManagedApplication ma ) {

		this.notificationMngr.instance( instance, ma.getApplication(), EventType.CHANGED );
		ConfigurationUtils.saveInstances( ma );
	}


	@Override
	public void removeInstance( ManagedApplication ma, Instance instance )
	throws UnauthorizedActionException, IOException {

		this.messagingMngr.checkMessagingConfiguration();
		for( Instance i : InstanceHelpers.buildHierarchicalList( instance )) {
			if( i.getStatus() != InstanceStatus.NOT_DEPLOYED )
				throw new UnauthorizedActionException( "Instances are still deployed or running. They cannot be removed in " + ma.getName() + "." );
		}

		// Whatever is the state of the agent, we try to send a message.
		MsgCmdRemoveInstance message = new MsgCmdRemoveInstance( instance );
		this.messagingMngr.sendMessageSafely( ma, instance, message );

		// Remove it from the model
		if( instance.getParent() == null ) {
			ma.getApplication().getRootInstances().remove( instance );
			this.autonomicMngr.notifyVmWasDeletedByHand( instance );

		} else {
			instance.getParent().getChildren().remove( instance );
		}

		// Release random values, if any
		this.randomMngr.releaseRandomValues( ma.getApplication(), instance );

		// Persist the model and notify
		this.logger.fine( "Instance " + InstanceHelpers.computeInstancePath( instance ) + " was successfully removed in " + ma.getName() + "." );
		ConfigurationUtils.saveInstances( ma );
		this.notificationMngr.instance( instance, ma.getApplication(), EventType.DELETED );
	}


	@Override
	public void restoreInstanceStates( ManagedApplication ma, TargetHandler targetHandler ) {

		for( Instance scopedInstance : InstanceHelpers.findAllScopedInstances( ma.getApplication())) {
			try {
				// Not associated with a VM? => Everything must be not deployed.
				String machineId = scopedInstance.data.get( Instance.MACHINE_ID );
				if( machineId == null ) {
					markScopedInstanceAsNotDeployed( scopedInstance );
					continue;
				}

				// Find the target handler
				String scopedInstancePath = InstanceHelpers.computeInstancePath( scopedInstance );
				Map targetProperties = this.targetsMngr.findRawTargetProperties( ma.getApplication(), scopedInstancePath );
				targetProperties.putAll( scopedInstance.data );

				TargetHandler readTargetHandler = null;
				try {
					readTargetHandler = this.targetHandlerResolver.findTargetHandler( targetProperties );

				} catch( Exception e ) {
					// nothing
				}

				// If it is not the right handler, ignore this scoped instance.
				if( readTargetHandler == null
						|| ! Objects.equals( targetHandler.getTargetId(), readTargetHandler.getTargetId()))
					continue;

				// Not a running VM? => Everything must be not deployed.
				if( ! targetHandler.isMachineRunning( targetProperties, machineId )) {
					markScopedInstanceAsNotDeployed( scopedInstance );
				}

				// Otherwise, ask the agent to resent the states under this scoped instance
				else {
					scopedInstance.setStatus( InstanceStatus.DEPLOYED_STARTED );
					this.messagingMngr.sendMessageDirectly( ma, scopedInstance, new MsgCmdSendInstances());
				}

			} catch( Exception e ) {
				this.logger.severe( "Could not request states for agent " + scopedInstance.getName() + " (I/O exception)." );
				Utils.logException( this.logger, e );
			}
		}
	}


	@Override
	public void resynchronizeAgents( ManagedApplication ma ) throws IOException {

		this.messagingMngr.checkMessagingConfiguration();
		this.logger.fine( "Resynchronizing agents in " + ma.getName() + "..." );
		for( Instance rootInstance : ma.getApplication().getRootInstances()) {
			if( rootInstance.getStatus() == InstanceStatus.DEPLOYED_STARTED )
				this.messagingMngr.sendMessageDirectly( ma, rootInstance, new MsgCmdResynchronize());
		}

		this.logger.fine( "Requests were sent to resynchronize agents in " + ma.getName() + "." );
	}


	@Override
	public void changeInstanceState( ManagedApplication ma, Instance instance, InstanceStatus newStatus )
	throws IOException, TargetException {

		this.messagingMngr.checkMessagingConfiguration();
		String instancePath = InstanceHelpers.computeInstancePath( instance );
		this.logger.fine( "Trying to change the state of " + instancePath + " to " + newStatus + " in " + ma.getName() + "..." );

		if( InstanceHelpers.isTarget( instance )) {
			List es = Arrays.asList(
					InstanceStatus.DEPLOYED_STARTED,
					InstanceStatus.DEPLOYING,
					InstanceStatus.STARTING,
					InstanceStatus.PROBLEM );

			if( newStatus == InstanceStatus.NOT_DEPLOYED
					&& es.contains( instance.getStatus()))
				undeployTarget( ma, instance );

			else if( instance.getStatus() == InstanceStatus.NOT_DEPLOYED
					&& newStatus == InstanceStatus.DEPLOYED_STARTED )
				deployTarget( ma, instance );

			else
				this.logger.warning( "Ignoring a request to update a scoped instance's state. New state was " + newStatus );

		} else {
			Map instanceResources = null;
			if( newStatus == InstanceStatus.DEPLOYED_STARTED
					|| newStatus == InstanceStatus.DEPLOYED_STOPPED )
				instanceResources = ResourceUtils.storeInstanceResources( ma.getTemplateDirectory(), instance );

			MsgCmdChangeInstanceState message = new MsgCmdChangeInstanceState( instance, newStatus, instanceResources );
			this.messagingMngr.sendMessageSafely( ma, instance, message );
			this.logger.fine( "A message was (or will be) sent to the agent to change the state of " + instancePath + " in " + ma.getName() + "." );
		}
	}


	@Override
	public void deployAndStartAll( ManagedApplication ma, Instance instance ) throws IOException {

		this.messagingMngr.checkMessagingConfiguration();
		Collection initialInstances;
		if( instance != null )
			initialInstances = Collections.singletonList(instance);
		else
			initialInstances = ma.getApplication().getRootInstances();

		List exceptions = new ArrayList<> ();
		for( Instance initialInstance : initialInstances ) {
			for( Instance i : InstanceHelpers.buildHierarchicalList( initialInstance )) {
				try {
					changeInstanceState( ma, i, InstanceStatus.DEPLOYED_STARTED );

				} catch( Exception e ) {
					exceptions.add( e );
				}
			}
		}

		processExceptions( this.logger, exceptions, "One or several errors occurred while deploying and starting instances." );
	}


	@Override
	public void stopAll( ManagedApplication ma, Instance instance ) throws IOException {

		this.messagingMngr.checkMessagingConfiguration();
		Collection initialInstances;
		if( instance != null )
			initialInstances = Collections.singletonList(instance);
		else
			initialInstances = ma.getApplication().getRootInstances();

		// We do not need to stop all the instances, just the first children.
		// Stop does not mean anything for targetsMngr.
		List exceptions = new ArrayList<> ();
		for( Instance initialInstance : initialInstances ) {
			try {
				if( ! InstanceHelpers.isTarget( initialInstance ))
					changeInstanceState( ma, initialInstance, InstanceStatus.DEPLOYED_STOPPED );
				else for( Instance i : initialInstance.getChildren())
					changeInstanceState( ma, i, InstanceStatus.DEPLOYED_STOPPED );

			} catch( Exception e ) {
				exceptions.add( e );
			}
		}

		processExceptions( this.logger, exceptions, "One or several errors occurred while stopping instances." );
	}


	@Override
	public void undeployAll( ManagedApplication ma, Instance instance ) throws IOException {

		this.messagingMngr.checkMessagingConfiguration();
		Collection initialInstances;
		if( instance != null )
			initialInstances = Collections.singletonList(instance);
		else
			initialInstances = ma.getApplication().getRootInstances();

		// We do not need to undeploy all the instances, just the first instance
		List exceptions = new ArrayList<> ();
		for( Instance initialInstance : initialInstances ) {
			try {
				changeInstanceState( ma, initialInstance, InstanceStatus.NOT_DEPLOYED );

			} catch( Exception e ) {
				exceptions.add( e );
			}
		}

		processExceptions( this.logger, exceptions, "One or several errors occurred while undeploying instances." );
	}


	/**
	 * Deploys a scoped instance.
	 * @param ma the managed application
	 * @param scopedInstance the instance to deploy (not null)
	 * @throws IOException if an error occurred with the messaging
	 * @throws TargetException if an error occurred with the target handler
	 */
	private void deployTarget( ManagedApplication ma, Instance scopedInstance ) throws TargetException, IOException {

		String path = InstanceHelpers.computeInstancePath( scopedInstance );
		this.logger.fine( "Deploying scoped instance '" + path + "' in " + ma.getName() + "..." );

		// We must prevent the concurrent creation of several VMs for a same root instance.
		// See #80.
		synchronized( LOCK ) {
			if( scopedInstance.data.get( Instance.TARGET_ACQUIRED ) == null ) {
				scopedInstance.data.put( Instance.TARGET_ACQUIRED, "yes" );
			} else {
				this.logger.finer( "Scoped instance '" + path + "' is already under deployment. This redundant request is dropped." );
				return;
			}
		}

		// If the VM creation was already done, then its machine ID has already been set.
		// It does not mean the VM is already configured, it may take some time.
		String machineId = scopedInstance.data.get( Instance.MACHINE_ID );
		if( machineId != null ) {
			this.logger.fine( "Deploy action for instance " + path + " is cancelled in " + ma.getName() + ". Already associated with a machine." );
			return;
		}

		InstanceStatus initialStatus = scopedInstance.getStatus();
		try {
			// Send the model
			scopedInstance.setStatus( InstanceStatus.DEPLOYING );
			MsgCmdSetScopedInstance msgModel = new MsgCmdSetScopedInstance(
					scopedInstance,
					ma.getApplication().getExternalExports(),
					ma.getApplication().applicationBindings );

			this.messagingMngr.sendMessageSafely( ma, scopedInstance, msgModel );

			// Send the probe files (if any)
			Map probeResources = ResourceUtils.storeInstanceProbeResources( ma.getDirectory(), scopedInstance );
			if( ! probeResources.isEmpty()) {
				MsgCmdUpdateProbeConfiguration msgProbes = new MsgCmdUpdateProbeConfiguration( scopedInstance, probeResources );
				this.messagingMngr.sendMessageSafely( ma, scopedInstance, msgProbes );
			}

			// Prepare the creation
			Map targetProperties = this.targetsMngr.lockAndGetTarget( ma.getApplication(), scopedInstance );
			targetProperties.putAll( scopedInstance.data );

			TargetHandler targetHandler = this.targetHandlerResolver.findTargetHandler( targetProperties );
			Map messagingConfiguration = this.messagingMngr.getMessagingClient().getConfiguration();
			String scopedInstancePath = InstanceHelpers.computeInstancePath( scopedInstance );

			// FIXME: there can be many problems here.
			// Not sure we handle all the possible problems correctly.
			try {
				machineId = targetHandler.createMachine( targetProperties, messagingConfiguration, scopedInstancePath, ma.getName());

			} catch( TargetException e ) {
				this.targetsMngr.unlockTarget( ma.getApplication(), scopedInstance );
				throw e;
			}

			scopedInstance.data.put( Instance.MACHINE_ID, machineId );
			this.logger.fine( "Scoped instance " + path + "'s deployment was successfully requested in " + ma.getName() + ". Machine ID: " + machineId );

			// If the configuration fails, keep the lock on the target.
			// This will prevent us from having ghost VMs with no target.
			targetHandler.configureMachine(
					targetProperties, messagingConfiguration,
					machineId, scopedInstancePath, ma.getName(), scopedInstance );

			this.logger.fine( "Scoped instance " + path + "'s configuration is on its way in " + ma.getName() + "." );

		} catch( TargetException | IOException e ) {
			this.logger.severe( "Failed to deploy scoped instance '" + path + "' in " + ma.getName() + ". " + e.getMessage());
			Utils.logException( this.logger, e );

			// The creation failed, remove the lock for another retry
			synchronized( LOCK ) {
				scopedInstance.data.remove( Instance.TARGET_ACQUIRED );
			}

			// Restore the state and propagate the exception
			scopedInstance.setStatus( initialStatus );
			throw e;
		}
	}


	/**
	 * Undeploys a scoped instance.
	 * @param ma the managed application
	 * @param scopedInstance the instance to undeploy (not null)
	 * @throws IOException if an error occurred with the messaging
	 * @throws TargetException if an error occurred with the target handler
	 */
	private void undeployTarget( ManagedApplication ma, Instance scopedInstance ) throws TargetException, IOException {

		String path = InstanceHelpers.computeInstancePath( scopedInstance );
		this.logger.fine( "Undeploying scoped instance '" + path + "' in " + ma.getName() + "..." );
		InstanceStatus initialStatus = scopedInstance.getStatus();
		String machineId = scopedInstance.data.remove( Instance.MACHINE_ID );
		try {
			// Terminate the machine...
			// ...  and notify other agents this agent was killed.
			this.logger.fine( "Agent '" + path + "' is about to be deleted in " + ma.getName() + "." );
			if( machineId != null ) {
				String scopedInstancePath = InstanceHelpers.computeInstancePath( scopedInstance );
				Map targetProperties = this.targetsMngr.findRawTargetProperties( ma.getApplication(), scopedInstancePath );
				targetProperties.putAll( scopedInstance.data );

				TargetHandler targetHandler = this.targetHandlerResolver.findTargetHandler( targetProperties );
				targetHandler.terminateMachine( targetProperties, machineId );

				this.targetsMngr.unlockTarget( ma.getApplication(), scopedInstance );
				this.messagingMngr.getMessagingClient().propagateAgentTermination( ma.getApplication(), scopedInstance );
			}

			this.logger.fine( "Agent '" + path + "' was successfully deleted in " + ma.getName() + "." );
			for( Instance i : InstanceHelpers.buildHierarchicalList( scopedInstance )) {
				i.setStatus( InstanceStatus.NOT_DEPLOYED );
				// DM won't send old imports upon restart...
				i.getImports().clear();
			}

			// Remove useless data for the configuration backup
			scopedInstance.data.remove( Instance.IP_ADDRESS );
			scopedInstance.data.remove( Instance.TARGET_ACQUIRED );
			scopedInstance.data.remove( Instance.RUNNING_FROM );
			this.logger.fine( "Scoped instance " + path + "'s undeployment was successfully requested in " + ma.getName() + "." );

		} catch( TargetException | IOException e ) {
			scopedInstance.setStatus( initialStatus );
			scopedInstance.data.put( Instance.MACHINE_ID, machineId );

			this.logger.severe( "Failed to undeploy scoped instance '" + path + "' in " + ma.getName() + ". " + e.getMessage());
			Utils.logException( this.logger, e );
			throw e;

		} finally {
			ma.removeAwaitingMessages( scopedInstance );
		}
	}


	/**
	 * Reports a set of collections (for bulk actions).
	 * @param logger
	 * @param exceptions
	 * @param msg
	 * @throws IOException
	 */
	static void processExceptions( Logger logger, List exceptions, String msg ) throws IOException {

		for( Exception e : exceptions )
			Utils.logException( logger, e );

		if( exceptions.size() > 0 ) {
			logger.info( msg );
			throw new IOException( msg );
		}
	}


	/**
	 * Updates a scoped instance to be marked as not deployed.
	 * 

* This includes its children. *

* * @param scopedInstance a non-null scoped instance */ private void markScopedInstanceAsNotDeployed( Instance scopedInstance ) { scopedInstance.data.remove( Instance.IP_ADDRESS ); scopedInstance.data.remove( Instance.MACHINE_ID ); scopedInstance.data.remove( Instance.TARGET_ACQUIRED ); for( Instance i : InstanceHelpers.buildHierarchicalList( scopedInstance )) i.setStatus( InstanceStatus.NOT_DEPLOYED ); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy