/**
* Copyright 2014-2017 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.agent.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;
import net.roboconf.agent.AgentMessagingInterface;
import net.roboconf.agent.internal.misc.AgentConstants;
import net.roboconf.agent.internal.misc.AgentUtils;
import net.roboconf.agent.internal.misc.HeartbeatTask;
import net.roboconf.agent.internal.misc.PluginMock;
import net.roboconf.agent.internal.misc.UserDataUtils;
import net.roboconf.core.Constants;
import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.helpers.ComponentHelpers;
import net.roboconf.core.runtime.IReconfigurable;
import net.roboconf.core.utils.ProcessStore;
import net.roboconf.core.utils.Utils;
import net.roboconf.messaging.api.messages.Message;
import net.roboconf.messaging.api.messages.from_agent_to_dm.MsgNotifMachineDown;
import net.roboconf.messaging.api.reconfigurables.ReconfigurableClientAgent;
import net.roboconf.plugin.api.PluginInterface;
/**
* An implementation of a Roboconf agent.
* @author Vincent Zurczak - Linagora
*/
public class Agent implements AgentMessagingInterface, IReconfigurable {
// Component properties (ipojo)
String applicationName, scopedInstancePath, ipAddress, targetId, messagingType;
String domain = Constants.DEFAULT_DOMAIN;
String networkInterface = AgentConstants.DEFAULT_NETWORK_INTERFACE;
boolean overrideProperties = false, simulatePlugins = true;
// Fields that should be injected (ipojo)
final List<PluginInterface> plugins = new ArrayList<> ();
// Internal fields
private final Logger logger;
private ReconfigurableClientAgent messagingClient;
private Instance scopedInstance;
Timer heartBeatTimer;
// Set as a class attribute to be overridden for tests.
String karafEtc = System.getProperty( Constants.KARAF_ETC );
String karafData = System.getProperty( Constants.KARAF_DATA );
/**
* Constructor.
*/
public Agent() {
this.logger = Logger.getLogger( getClass().getName());
}
/**
* Starts the agent.
* <p>
* It is invoked by iPojo when an instance becomes VALID.
* </p>
*/
public void start() {
this.logger.info( "Agent '" + getAgentId() + "' is about to be launched." );
this.ipAddress = AgentUtils.findIpAddress( this.networkInterface );
this.logger.info( "IP address resolved to " + this.ipAddress );
this.messagingClient = new ReconfigurableClientAgent();
this.messagingClient.setDomain( this.domain );
AgentMessageProcessor messageProcessor = new AgentMessageProcessor( this );
this.messagingClient.associateMessageProcessor( messageProcessor );
// Do we need to override properties with user data?
if( Utils.isEmptyOrWhitespaces( this.targetId )) {
this.logger.warning( "No target ID was specified in the agent configuration. No user data will be retrieved." );
} else if( ! this.overrideProperties ) {
this.logger.fine( "User data are NOT supposed to be used." );
} else {
AgentProperties props = null;
this.logger.fine( "User data are supposed to be used. Retrieving in progress..." );
if( AgentConstants.PLATFORM_EC2.equalsIgnoreCase( this.targetId )
|| AgentConstants.PLATFORM_OPENSTACK.equalsIgnoreCase( this.targetId ))
props = UserDataUtils.findParametersForAmazonOrOpenStack( this.logger );
else if( AgentConstants.PLATFORM_AZURE.equalsIgnoreCase( this.targetId ))
props = UserDataUtils.findParametersForAzure( this.logger );
else if(AgentConstants.PLATFORM_VMWARE.equalsIgnoreCase(this.targetId))
props = UserDataUtils.findParametersForVmware( this.logger );
else
this.logger.warning( "Unknown target ID. No user data will be retrieved." );
if( props != null ) {
String errorMessage = props.validate();
if( errorMessage != null )
this.logger.severe( "An error was found in user data. " + errorMessage );
this.applicationName = props.getApplicationName();
this.scopedInstancePath = props.getScopedInstancePath();
if( ! Utils.isEmptyOrWhitespaces( props.getIpAddress())) {
this.ipAddress = props.getIpAddress();
this.logger.info( "The agent's address was overwritten from user data and set to " + this.ipAddress );
}
try {
this.logger.info( "Reconfiguring the agent with user data." );
UserDataUtils.reconfigureMessaging(
this.karafEtc,
props.getMessagingConfiguration());
} catch(IOException e) {
this.logger.severe("Error in messaging reconfiguration from user data: " + e);
}
}
}
reconfigure();
TimerTask timerTask = new HeartbeatTask( this );
this.heartBeatTimer = new Timer( "Roboconf's Heartbeat Timer @ Agent", true );
this.heartBeatTimer.scheduleAtFixedRate( timerTask, Constants.HEARTBEAT_PERIOD, Constants.HEARTBEAT_PERIOD );
this.logger.info( "Agent '" + getAgentId() + "' was launched." );
}
/**
* Stops the agent.
* <p>
* It is invoked by iPojo when an instance becomes INVALID.
* </p>
*/
public void stop() {
this.logger.info( "Agent '" + getAgentId() + "' is about to be stopped." );
// Stop the timer
if( this.heartBeatTimer != null ) {
this.heartBeatTimer.cancel();
this.heartBeatTimer = null;
}
// Prevent NPE for successive calls to #stop()
if( this.messagingClient == null )
return;
// Send a last message to the DM
try {
if( this.messagingClient.isConnected()) {
this.messagingClient.sendMessageToTheDm( new MsgNotifMachineDown( this.applicationName, this.scopedInstancePath ));
this.logger.fine( "Agent " + getAgentId() + " notified the DM it was about to stop." );
}
} catch( IOException e ) {
this.logger.warning( e.getMessage());
Utils.logException( this.logger, e );
}
// Close the connection
try {
this.messagingClient.getMessageProcessor().stopProcessor();
this.messagingClient.getMessageProcessor().interrupt();
this.messagingClient.closeConnection();
} catch( IOException e ) {
this.logger.warning( e.getMessage());
Utils.logException( this.logger, e );
}
this.logger.info( "Agent '" + getAgentId() + "' was stopped." );
}
/*
* (non-Javadoc)
* @see net.roboconf.agent.AgentMessagingInterface
* #forceHeartbeatSending()
*/
@Override
public void forceHeartbeatSending() {
new HeartbeatTask( this ).run();
}
/**
* @return true if this agent needs the DM to send its model
*/
public boolean needsModel() {
AgentMessageProcessor messageProcessor = null;
if( this.messagingClient != null )
messageProcessor = (AgentMessageProcessor) this.messagingClient.getMessageProcessor();
return messageProcessor == null || messageProcessor.scopedInstance == null;
}
/**
* @return the client for the messaging server
*/
@Override
public ReconfigurableClientAgent getMessagingClient() {
return this.messagingClient;
}
/**
* Finds the right plug-in for an instance.
* @param instance a non-null instance
* @return the plug-in associated with the instance's installer name
*/
public PluginInterface findPlugin( Instance instance ) {
// Find a plug-in
PluginInterface result = null;
if( this.simulatePlugins ) {
result = new PluginMock();
} else {
String installerName = null;
if( instance.getComponent() != null )
installerName = ComponentHelpers.findComponentInstaller( instance.getComponent());
// Run through available plug-ins
for( PluginInterface pi : this.plugins ) {
if( pi.getPluginName().equalsIgnoreCase( installerName )) {
result = pi;
break;
}
}
if( result == null )
this.logger.severe( "No plugin was found for instance '" + instance.getName() + "' with installer '" + installerName + "'." );
}
// Initialize the result, if any
if( result != null )
result.setNames( this.applicationName, this.scopedInstancePath );
return result;
}
/**
* This method lists the available plug-ins and logs it.
*/
public void listPlugins() {
if( this.plugins.isEmpty()) {
this.logger.info( "No plug-in was found for Roboconf's agent." );
} else {
StringBuilder sb = new StringBuilder( "Available plug-ins in Roboconf's agent: " );
for( Iterator<PluginInterface> it = this.plugins.iterator(); it.hasNext(); ) {
sb.append( it.next().getPluginName());
if( it.hasNext())
sb.append( ", " );
}
sb.append( "." );
this.logger.info( sb.toString());
}
}
/**
* This method is invoked by iPojo every time a new plug-in appears.
* @param pi the appearing plugin.
*/
public void pluginAppears( PluginInterface pi ) {
if( pi != null ) {
this.logger.info( "Plugin '" + pi.getPluginName() + "' is now available in Roboconf's agent." );
this.plugins.add( pi );
listPlugins();
}
}
/**
* This method is invoked by iPojo every time a plug-in disappears.
* @param pi the disappearing plugin.
*/
public void pluginDisappears( PluginInterface pi ) {
// May happen if a plug-in could not be instantiated
// (iPojo uses proxies). In this case, it results in a NPE here.
if( pi == null ) {
this.logger.info( "An invalid plugin is removed." );
} else {
this.plugins.remove( pi );
this.logger.info( "Plugin '" + pi.getPluginName() + "' is not available anymore in Roboconf's agent." );
}
listPlugins();
}
/**
* This method is invoked by iPojo every time a plug-in is modified.
* @param pi the modified plugin.
*/
public void pluginWasModified( PluginInterface pi ) {
this.logger.info( "Plugin '" + pi.getPluginName() + "' was modified in Roboconf's agent." );
listPlugins();
}
/**
* This method reconfigures the agent.
* <p>
* It is invoked by iPojo when the configuration changes.
* It may be invoked before the start() method is.
* </p>
*/
@Override
public void reconfigure() {
// This method is invoked when properties change.
// It is not related to life cycle (start/stop).
this.logger.info( "Reconfiguration requested in agent " + getAgentId());
if( this.messagingClient == null ) {
this.logger.info( "The agent has not yet been started. Configuration is dropped." );
return;
}
// Update the messaging connection
this.messagingClient.setApplicationName( this.applicationName );
this.messagingClient.setScopedInstancePath( this.scopedInstancePath );
this.messagingClient.setIpAddress( this.ipAddress );
this.messagingClient.setNeedsModel( needsModel());
this.messagingClient.setDomain( this.domain );
this.messagingClient.switchMessagingType( this.messagingType);
// Deal with injected configurations
AgentUtils.injectConfigurations(
this.karafEtc,
this.applicationName,
this.scopedInstancePath,
this.domain,
this.ipAddress );
this.logger.info( "The agent was successfully (re)configured." );
}
/**
* @return the application name
*/
@Override
public String getApplicationName() {
return this.applicationName;
}
/**
* @return the scoped instance's path
*/
@Override
public String getScopedInstancePath() {
return this.scopedInstancePath;
}
/**
* @return the IP address
*/
public String getIpAddress() {
return this.ipAddress;
}
/**
* @return the messagingType
*/
public String getMessagingType() {
return this.messagingType;
}
/**
* @param applicationName the applicationName to set
*/
public void setApplicationName( String applicationName ) {
this.applicationName = applicationName;
}
/**
* @param scopedInstancePath the scopedInstancePath to set
*/
public void setScopedInstancePath( String scopedInstancePath ) {
this.scopedInstancePath = scopedInstancePath;
}
/**
* @param ipAddress the ipAddress to set
*/
public void setIpAddress( String ipAddress ) {
if(! Utils.isEmptyOrWhitespaces(ipAddress)) {
this.ipAddress = ipAddress;
this.logger.finer( "New IP address set in the agent: " + ipAddress );
}
}
/**
* @param targetId the targetId to set
*/
public void setTargetId( String targetId ) {
this.targetId = targetId;
}
/**
* @param overrideProperties the overrideProperties to set
*/
public void setOverrideProperties( boolean overrideProperties ) {
this.overrideProperties = overrideProperties;
}
/**
* @param simulatePlugins the simulatePlugins to set
*/
public void setSimulatePlugins( boolean simulatePlugins ) {
this.simulatePlugins = simulatePlugins;
}
/**
* @param networkInterface the networkInterface to set
*/
public void setNetworkInterface( String networkInterface ) {
this.networkInterface = networkInterface;
this.logger.info( "New network interface set: " + networkInterface );
if( ! this.overrideProperties ) {
this.logger.info( "Resetting the agent's IP address..." );
this.ipAddress = AgentUtils.findIpAddress( networkInterface );
this.logger.info( "New IP address: " + this.ipAddress );
} else {
this.logger.info( "User data are used. The IP address will not be refreshed." );
}
}
/**
* @param domain the domain to set
*/
public void setDomain( String domain ) {
this.domain = domain;
}
@Override
public String getDomain() {
return this.domain;
}
/**
* @param messagingType the messaging type to set
*/
public void setMessagingType( String messagingType ) {
this.messagingType = messagingType;
}
/**
* @return the agent's ID (a human-readable identifier)
*/
String getAgentId() {
StringBuilder sb = new StringBuilder();
sb.append( Utils.isEmptyOrWhitespaces( this.scopedInstancePath ) ? "?" : this.scopedInstancePath );
if( ! Utils.isEmptyOrWhitespaces( this.applicationName ))
sb.append(" @ ").append(this.applicationName);
return sb.toString();
}
@Override
public Instance getScopedInstance() {
return this.scopedInstance;
}
public void setScopedInstance(Instance scopedInstance) {
this.scopedInstance = scopedInstance;
}
public List<PluginInterface> getPlugins() {
return this.plugins;
}
/*
* (non-Javadoc)
* @see net.roboconf.agent.AgentMessagingInterface
* #agentStatus()
*/
@Override
public String agentStatus() {
StringBuilder sb = new StringBuilder();
// Messages
LinkedBlockingQueue<Message> agentQueue = this.messagingClient.getMessageProcessor().getMessageQueue();
if( agentQueue.isEmpty() ) {
sb.append( "There is no message being processed in agent queue\n" );
} else {
sb.append( "Agent " + getScopedInstancePath() + " (" + getApplicationName() + ")\n" );
sb.append( "The number total of messages in agent queue is : " + agentQueue.size() + "\n" );
sb.append( "The types of messages being processed are : " + "\n");
for( Message msg : agentQueue ) {
sb.append( msg.getClass().getSimpleName() + "\n" );
}
}
// Running processes
Process p = ProcessStore.getProcess(this.applicationName, this.scopedInstancePath);
if( p != null )
sb.append( "Be careful. A recipe is under execution." );
else
sb.append( "No recipe is under execution." );
return sb.toString();
}
}