Skip to content

Commit

Permalink
Merge pull request #842 from gibello/master
Browse files Browse the repository at this point in the history
Specify a list of IP addresses for embedded target (closes #788 )
  • Loading branch information
vincent-zurczak committed Jul 25, 2017
2 parents 13d4ae8 + 483c8a2 commit afb97d7
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public class Instance implements Serializable {
* Storing this information in a scoped instance is enough.
* </p>
*/
public static final String READY_FOR_CFG_MARKER = "ready.for.local.script.configuation";
public static final String READY_FOR_CFG_MARKER = "ready.for.local.script.configuration";

/**
* A constant to store the timestamp of the first heart beat received for this instance.
Expand Down
34 changes: 34 additions & 0 deletions core/roboconf-target-embedded/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.21.1</version>
</dependency>

<dependency>
<groupId>junit</groupId>
Expand All @@ -77,6 +83,34 @@
</instructions>
</configuration>
</plugin>

<!--
We have to repackage the JAR because the Docker library uses BouncyCasttle...
This project comes with signature files that make the build fail.
The repackaging operation aims at removing these files.
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<configuration>
<target>
<unzip src="${project.build.directory}/${project.build.finalName}.jar" dest="${project.build.directory}/repack" />
<delete file="${project.build.directory}/${project.build.finalName}.jar" />
<delete file="${project.build.directory}/repack/META-INF/BCKEY.SF" />
<delete file="${project.build.directory}/repack/META-INF/BCKEY.DSA" />
<zip basedir="${project.build.directory}/repack" destfile="${project.build.directory}/${project.build.finalName}.jar" />
<delete dir="${project.build.directory}/repack" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.felix</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@

package net.roboconf.target.embedded.internal;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.userdata.UserDataHelpers;
import net.roboconf.core.utils.Utils;
import net.roboconf.target.api.TargetException;
import net.roboconf.target.api.TargetHandler;
import net.roboconf.target.api.TargetHandlerParameters;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.xfer.FileSystemFile;

/**
* A target for embedded systems (e.g. the local host).
Expand All @@ -40,8 +47,14 @@
public class EmbeddedHandler implements TargetHandler {

public static final String TARGET_ID = "embedded";
private final Map<String,Boolean> machineIdToRunning = new HashMap<> ();

public static final String IP_ADDRESSES = "embedded.ip";
public static final String SCP_USER = "scp.user";
public static final String SCP_KEYFILE = "scp.keyfile";
public static final String SCP_AGENT_CONFIG_DIR = "scp.agent.configdir";
static final String AGENT_CONFIG_DEFAULT_DIR = "/etc/roboconf-agent";
static final String USER_DATA_FILE = "parameters.properties";
private final Map<String,String> machineIdToIp = new HashMap<> ();
protected Map<String,Boolean> ipTable = new HashMap<> ();

@Override
public String getTargetId() {
Expand All @@ -51,40 +64,138 @@ public String getTargetId() {

@Override
public String createMachine( TargetHandlerParameters parameters ) throws TargetException {

if(parameters != null && parameters.getTargetProperties() != null) {
String ips = parameters.getTargetProperties().get(IP_ADDRESSES);
if(ips != null) {
String iplist[] = ips.trim().split("\\s*,\\s*");
for(String ip : iplist) ipTable.put(ip, false);
}
}
String machineId = parameters.getScopedInstancePath() + " (" + TARGET_ID + ")";
this.machineIdToRunning.put( machineId, Boolean.TRUE );
this.machineIdToIp.put( machineId, "" );
return machineId;
}


@Override
public void terminateMachine( TargetHandlerParameters parameters, String machineId )
throws TargetException {
this.machineIdToRunning.remove( machineId );
String ip = this.machineIdToIp.remove( machineId );
if(! Utils.isEmptyOrWhitespaces(ip)) releaseIpAddress(ip);
}


@Override
public void configureMachine( TargetHandlerParameters parameters, String machineId, Instance scopedInstance )
throws TargetException {

// It may require to be configured from the DM => add the right marker
// It may require to be post-configured from the DM => add the right marker
scopedInstance.data.put( Instance.READY_FOR_CFG_MARKER, "true" );

String ip = acquireIpAddress();
if(! Utils.isEmptyOrWhitespaces(ip)) {
try {
sendConfiguration(ip, parameters);
} catch (IOException e) {
throw new TargetException(e);
}
this.machineIdToIp.put(machineId, ip);
}
}


@Override
public boolean isMachineRunning( TargetHandlerParameters parameters, String machineId )
throws TargetException {
return this.machineIdToRunning.containsKey( machineId );
return this.machineIdToIp.containsKey( machineId );
}


@Override
public String retrievePublicIpAddress( TargetHandlerParameters parameters, String machineId )
throws TargetException {
// This handler cannot determine the IP address
String ip = this.machineIdToIp.get(machineId);
return Utils.isEmptyOrWhitespaces(ip) ? null : ip;
}

/**
* Acquires an IP address among available ones.
* @return The acquired IP address
*/
protected String acquireIpAddress() {
for(Map.Entry<String, Boolean> entry : ipTable.entrySet()) {
if(! entry.getValue()) {
entry.setValue(true);
return entry.getKey();
}
}
return null;
}

/**
* Release a (previously acquired) IP address.
* @param ip The IP address to release
*/
protected void releaseIpAddress(String ip) {
if(ipTable.get(ip) != null) ipTable.put(ip, false);
}

/**
* Send configuration to remote host using scp. A user-data file will be generated,
* sent to the remote host (in agent configuration directory), then the remote agent configuration file
* will be updated to point on the new user-data file ("parameters" property).
* @param ip IP address of remote host
* @param parameters Target handler parameters (with user-data inside)
* @throws IOException
*/
protected void sendConfiguration(String ip, TargetHandlerParameters parameters) throws IOException {
SSHClient ssh = new SSHClient();
File tmpDir = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString());
tmpDir.mkdir();
tmpDir.deleteOnExit(); // In case deletion fails
try {
ssh.loadKnownHosts();
ssh.connect(ip);

String user = parameters.getTargetProperties().get(SCP_USER);
if(user == null) user = "ubuntu"; // default on several (ubuntu) systems, including IaaS VMs
String keyfile = parameters.getTargetProperties().get(SCP_KEYFILE);

if(keyfile == null) ssh.authPublickey(user); // use ~/.ssh/id_rsa and ~/.ssh/id_dsa
else ssh.authPublickey(user, keyfile); // load key from specified file (eg .pem).

String userData = UserDataHelpers.writeUserDataAsString(
parameters.getMessagingProperties(),
parameters.getDomain(),
parameters.getApplicationName(),
parameters.getScopedInstancePath());

// Generate local user-data file, then upload it
String agentConfigDir = parameters.getTargetProperties().get(SCP_AGENT_CONFIG_DIR);
if(agentConfigDir == null) agentConfigDir = AGENT_CONFIG_DEFAULT_DIR;
File userdataFile = new File(tmpDir, USER_DATA_FILE);
Utils.writeStringInto(userData, userdataFile);
ssh.newSCPFileTransfer().upload(new FileSystemFile(userdataFile), agentConfigDir);

File localAgentConfig = new File(tmpDir, "net.roboconf.agent.configuration.cfg");
File remoteAgentConfig = new File(agentConfigDir, "net.roboconf.agent.configuration.cfg");
// Download remote agent config file...
ssh.newSCPFileTransfer().download(remoteAgentConfig.getCanonicalPath(), new FileSystemFile(tmpDir));

// ... Replace "parameters" property to point on user data file...
String config = Utils.readFileContent(localAgentConfig);
config = config.replaceFirst("(?m)^\\s*parameters\\s*[:=]\\s*(.*)$",
"\nparameters = " + remoteAgentConfig.getCanonicalPath());
Utils.writeStringInto(config, localAgentConfig);

// ... Then upload agent config file back
ssh.newSCPFileTransfer().upload(new FileSystemFile(localAgentConfig), agentConfigDir);

} finally {
try {
ssh.disconnect();
ssh.close();
tmpDir.delete();
} catch(IOException ignore) { /* ignore */ }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
package net.roboconf.target.embedded.internal;

import java.util.HashMap;

import org.junit.Assert;
import org.junit.Test;
import java.util.Map;

import net.roboconf.core.model.beans.Instance;
import net.roboconf.target.api.TargetHandlerParameters;

import org.junit.Assert;
import org.junit.Test;

/**
* @author Vincent Zurczak - Linagora
*/
Expand All @@ -50,16 +51,19 @@ public void testTargetEmbedded() throws Exception {
target.terminateMachine( parameters, "anything" );

Assert.assertFalse( target.isMachineRunning( null, "nothing (" + EmbeddedHandler.TARGET_ID + ")" ));

Assert.assertNotNull( target.createMachine( new TargetHandlerParameters()
.applicationName( "app" )
.domain( "domain" )
.scopedInstancePath( "nothing" )));

Assert.assertTrue( target.isMachineRunning( null, "nothing (" + EmbeddedHandler.TARGET_ID + ")" ));


Assert.assertNotNull( target.createMachine( new TargetHandlerParameters()
.targetProperties( new HashMap<String,String>( 0 ))
.messagingProperties( new HashMap<String,String>( 0 ))));

Assert.assertEquals(target.ipTable.size(), 0);
Instance scopedInstance = new Instance();
Assert.assertEquals( 0, scopedInstance.data.size());
target.configureMachine(
Expand All @@ -73,4 +77,47 @@ public void testTargetEmbedded() throws Exception {
target.terminateMachine( parameters, null );
target.terminateMachine( null, "anything" );
}

@SuppressWarnings("serial")
@Test
public void testIpList() throws Exception {
EmbeddedHandler target = new EmbeddedHandler();
Assert.assertEquals(target.ipTable.size(), 0);
Assert.assertNotNull( target.createMachine( new TargetHandlerParameters()
.applicationName( "app" )
.domain( "domain" )
.scopedInstancePath( "nothing" )
.targetProperties(new HashMap<String, String>() {{ put(EmbeddedHandler.IP_ADDRESSES, "192.168.1.1, 192.168.1.2"); }})));

Assert.assertEquals(target.ipTable.size(), 2);
String ip = target.acquireIpAddress();
Assert.assertTrue(target.ipTable.get(ip));
target.releaseIpAddress(ip);
Assert.assertFalse(target.ipTable.get(ip));
}

/**
* Test to run by hand, after setting IP and key file.
* @throws Exception
*/
public void toRunByHand() throws Exception {
// Set before testing (IP should point on a VM with roboconf agent).
String ip = "54.171.159.33";
String keyfile = "/home/gibello/Linagora/EC2Linagora/aws-linagora.pem";

EmbeddedHandler handler = new EmbeddedHandler();
TargetHandlerParameters parameters = new TargetHandlerParameters();
Map<String, String> messagingProperties = new HashMap<>();
messagingProperties.put("messaging.type", "http");
parameters.setMessagingProperties(messagingProperties);
parameters.setDomain("test-domain");
parameters.setApplicationName("test-application");
parameters.setScopedInstancePath("/test/instance");

Map<String, String> targetProperties = new HashMap<>();
targetProperties.put(EmbeddedHandler.SCP_KEYFILE, keyfile);
parameters.setTargetProperties(targetProperties);

handler.sendConfiguration(ip, parameters);
}
}

0 comments on commit afb97d7

Please sign in to comment.