/** * Copyright 2016-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.dm.internal.api.impl; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; import net.roboconf.core.agents.DataHelpers; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.utils.ProgramUtils; import net.roboconf.core.utils.Utils; import net.roboconf.dm.internal.api.ITargetConfigurator; import net.roboconf.dm.management.api.ITargetHandlerResolver; import net.roboconf.target.api.TargetHandler; import net.roboconf.target.api.TargetHandlerParameters; /** * @author Vincent Zurczak - Linagora */ public class TargetConfiguratorImpl implements ITargetConfigurator { ExecutorService executor; final Map<String,TargetConfiugrationBean> candidates = new ConcurrentHashMap<> (); private ITargetHandlerResolver targetHandlerResolver; @Override public void start() { // Everything runs within a single and separate thread. this.executor = Executors.newSingleThreadExecutor(); } @Override public void stop() { if( this.executor != null ) { this.executor.shutdownNow(); this.executor = null; } } /** * @param targetHandlerResolver the targetHandlerResolver to set */ public void setTargetHandlerResolver( ITargetHandlerResolver targetHandlerResolver ) { this.targetHandlerResolver = targetHandlerResolver; } @Override public void reportCandidate( TargetHandlerParameters parameters, Instance scopedInstance ) { String key = parameters.getApplicationName() + "_" + parameters.getScopedInstancePath(); this.candidates.put( key, new TargetConfiugrationBean( parameters, scopedInstance )); } @Override public void cancelCandidate( TargetHandlerParameters parameters, Instance scopedInstance ) { String key = parameters.getApplicationName() + "_" + parameters.getScopedInstancePath(); this.candidates.remove( key ); } @Override public void verifyCandidates() { // Prevent stupid NPEs if( this.executor == null ) return; // Checks all the candidates for( Map.Entry<String,TargetConfiugrationBean> entry : this.candidates.entrySet()) { Instance scopedInstance = entry.getValue().scopedInstance; TargetHandlerParameters parameters = entry.getValue().parameters; File script = parameters.getTargetConfigurationScript(); // Marked? if( scopedInstance.data.containsKey( Instance.READY_FOR_CFG_MARKER )) { // Remove the marker scopedInstance.data.remove( Instance.READY_FOR_CFG_MARKER ); // If there is a script... // If there is a resolver for target handlers... if( script != null && script.exists() && this.targetHandlerResolver != null ) this.executor.execute( new ConfigurationRunnable( parameters, scopedInstance, this.targetHandlerResolver )); // Remove it from the list of candidates this.candidates.remove( entry.getKey()); } } } /** * @author Vincent Zurczak - Linagora */ static class TargetConfiugrationBean { private final TargetHandlerParameters parameters; private final Instance scopedInstance; /** * Constructor. * @param parameters * @param scopedInstance */ public TargetConfiugrationBean( TargetHandlerParameters parameters, Instance scopedInstance ) { this.parameters = parameters; this.scopedInstance = scopedInstance; } } /** * Just a proxy class to ease testing of script execution. * <p> * This class can be mocked for tests. * </p> * * FIXME: we may need to push such a class as a replacement of ProgramUtils. * @author Vincent Zurczak - Linagora */ static class ProgramUtilsProxy { int executeCommand( final Logger logger, final String[] command, final File workingDir, final Map<String,String> environmentVars, final String applicationName, final String scopedInstancePath) throws IOException, InterruptedException { return ProgramUtils.executeCommand( logger, command, workingDir, environmentVars, applicationName, scopedInstancePath); } } /** * @author Vincent Zurczak - Linagora */ static class ConfigurationRunnable implements Runnable { private final Logger logger = Logger.getLogger( getClass().getName()); private final TargetHandlerParameters parameters; private final ITargetHandlerResolver targetHandlerResolver; private final Instance scopedInstance; ProgramUtilsProxy programUtils; /** * Constructor. * @param parameters * @param scopedInstance * @param targetHandlerResolver */ public ConfigurationRunnable( TargetHandlerParameters parameters, Instance scopedInstance, ITargetHandlerResolver targetHandlerResolver ) { this.parameters = parameters; this.scopedInstance = scopedInstance; this.targetHandlerResolver = targetHandlerResolver; this.programUtils = new ProgramUtilsProxy(); } @Override public void run() { String suffix = this.parameters.getScopedInstancePath() + " in " + this.parameters.getApplicationName(); try { // Retrieve the target handler TargetHandler th = this.targetHandlerResolver.findTargetHandler( this.parameters.getTargetProperties()); // Retrieve the IP address String machineId = this.scopedInstance.data.get( Instance.MACHINE_ID ); if( Utils.isEmptyOrWhitespaces( machineId )) this.logger.warning( "No machine ID was found for " + suffix ); String publicIpAddress = th.retrievePublicIpAddress( this.parameters, machineId ); if( Utils.isEmptyOrWhitespaces( publicIpAddress )) { publicIpAddress = ""; this.logger.warning( "No public IP address could be retrieved for " + suffix ); } // Prepare the execution of the script File script = this.parameters.getTargetConfigurationScript(); if( script.exists()) { script.setExecutable( true ); String[] command = { script.getAbsolutePath()}; String userData = DataHelpers.writeUserDataAsString( this.parameters.getMessagingProperties(), this.parameters.getDomain(), this.parameters.getApplicationName(), this.parameters.getScopedInstancePath()); Map<String,String> vars = new HashMap<> (); vars.put( "IP_ADDRESS", publicIpAddress ); vars.put( "APPLICATION_NAME", this.parameters.getApplicationName()); vars.put( "SCOPED_INSTANCE_PATH", this.parameters.getScopedInstancePath()); vars.put( "DOMAIN", this.parameters.getDomain()); vars.put( "USER_DATA", userData ); this.programUtils.executeCommand( this.logger, command, script.getParentFile(), vars, this.parameters.getApplicationName(), this.parameters.getScopedInstancePath()); } } catch( Throwable t ) { // Wrap ALL the exceptions. // Notice that all the exceptions are not bugs! As an example, it is possible the // target handler was uninstalled. In this case, a TargetException is thrown and caught by this block. this.logger.severe( "Failed to run local configuration script for target " + suffix ); Utils.logException( this.logger, t ); } } } }