/** * 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.integration.tests.dm; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.inject.Inject; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.ProbeBuilder; import org.ops4j.pax.exam.TestProbeBuilder; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; import org.ops4j.pax.exam.spi.reactors.PerMethod; import net.roboconf.agent.AgentMessagingInterface; import net.roboconf.core.Constants; import net.roboconf.core.internal.tests.TestApplication; import net.roboconf.core.internal.tests.TestApplicationTemplate; import net.roboconf.core.internal.tests.TestUtils; import net.roboconf.core.model.beans.Instance.InstanceStatus; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.dm.internal.test.TestManagerWrapper; import net.roboconf.dm.management.ManagedApplication; import net.roboconf.dm.management.Manager; import net.roboconf.integration.tests.commons.AbstractIntegrationTest; import net.roboconf.integration.tests.commons.ItConfigurationBean; import net.roboconf.integration.tests.commons.internal.ItUtils; import net.roboconf.integration.tests.commons.internal.parameterized.IMessagingConfiguration; import net.roboconf.integration.tests.commons.internal.runners.RoboconfPaxRunner; import net.roboconf.integration.tests.dm.probes.DmTest; import net.roboconf.messaging.rabbitmq.RabbitMqConstants; /** * Checks delayed initialization. * <p> * Configure an agent correctly and the DM incorrectly.<br> * Wait a little bit and reconfigure the DM with the right messaging * credentials. Make sure the agent's model is initialized correctly. * </p> * * <p> * Note: this test raised a lot of problems as it used to fail randomly. * When it failed, it was because AgentMessagingInterface.class was exported * by the probe while the implementation (*.internal.agent) came from the agent's * bundle. When it happened, it threw an IllegalArgumentException (which in fact * hid a ClassCastException - both classes were incompatible because they came from * different class loaders). * </p> * <p> * We (hopefully) solved this issue by configuring the probe, and by customizing it too * (we only embed the current test class and not the others from the same package), * and we add an OSGi import-package declaration in the probe. I am not sure at all * which element solves the problem. Just for history, this problem occurred with two * versions of PAX-exam 4.x. * </p> * * @author Vincent Zurczak - Linagora */ @RunWith( RoboconfPaxRunner.class ) @ExamReactorStrategy( PerMethod.class ) public class DelayedAgentInitializationTest extends DmTest { @Inject protected Manager manager; @Inject protected AgentMessagingInterface agentItf; @ProbeBuilder public TestProbeBuilder probeConfiguration( TestProbeBuilder probe ) { // We need to specify the classes we need // and that come from external modules. probe.addTest( DmTest.class ); probe.addTest( TestApplicationTemplate.class ); probe.addTest( TestApplication.class ); probe.addTest( TestManagerWrapper.class ); probe.addTest( TestUtils.class ); probe.addTest( AbstractIntegrationTest.class ); probe.addTest( IMessagingConfiguration.class ); probe.addTest( ItConfigurationBean.class ); return probe; } @Override @Configuration public Option[] config() throws Exception { List<Option> options = new ArrayList<> (); options.addAll( Arrays.asList( super.config())); // Generic messaging update note: // // Messaging configuration is now externalized to the RabbitMQ-specific configuration file: // net.roboconf.messaging.rabbitmq.cfg // Because the DM and the agent run on the same platform (in this test), this messaging configuration is common // to both of them. As a workaround, we keep the default (valid) messaging configuration, and artificially // close the DM client's connection. // Add the configuration for the agent TestApplication app = new TestApplication(); options.add( editConfigurationFilePut( "etc/net.roboconf.agent.configuration.cfg", "application-name", app.getName())); options.add( editConfigurationFilePut( "etc/net.roboconf.agent.configuration.cfg", "scoped-instance-path", InstanceHelpers.computeInstancePath( app.getMySqlVm()))); options.add( editConfigurationFilePut( "etc/net.roboconf.agent.configuration.cfg", Constants.MESSAGING_TYPE, RabbitMqConstants.FACTORY_RABBITMQ)); // Add an invalid configuration for the DM options.add( editConfigurationFilePut( "etc/net.roboconf.dm.configuration.cfg", "message-server-username", "invalid" )); // Deploy the agent's bundles String roboconfVersion = ItUtils.findRoboconfVersion(); options.add( mavenBundle() .groupId( "net.roboconf" ) .artifactId( "roboconf-plugin-api" ) .version( roboconfVersion ) .start()); options.add( mavenBundle() .groupId( "net.roboconf" ) .artifactId( "roboconf-agent" ) .version( roboconfVersion ) .start()); options.add( mavenBundle() .groupId( "net.roboconf" ) .artifactId( "roboconf-agent-default" ) .version( roboconfVersion ) .start()); return options.toArray( new Option[ options.size()]); } @Test public void run() throws Exception { // Sleep for a while, to let the RabbitMQ client factory arrive. Thread.sleep(2000); // Prepare the manager's wrapper. TestManagerWrapper managerWrapper = new TestManagerWrapper( this.manager ); // Artificially closes the DM-side client, to prevent Agent <-> DM exchanges. managerWrapper.getMessagingClient().closeConnection(); // Make like if the DM had already deployed an application's part TestApplication app = new TestApplication(); app.setDirectory( new File( this.manager.configurationMngr().getWorkingDirectory(), "tmp-test" )); ManagedApplication ma = new ManagedApplication( app ); managerWrapper.addManagedApplication( ma ); // Check the DM Assert.assertEquals( InstanceStatus.NOT_DEPLOYED, app.getMySqlVm().getStatus()); Assert.assertNotNull( managerWrapper.getMessagingClient()); Assert.assertFalse( managerWrapper.getMessagingClient().isConnected()); // Check the agent Assert.assertEquals( app.getName(), this.agentItf.getApplicationName()); Assert.assertNull( this.agentItf.getScopedInstance()); Assert.assertNotNull( this.agentItf.getMessagingClient()); Assert.assertTrue( this.agentItf.getMessagingClient().isConnected()); // Both cannot communicate. // Let's wait a little bit and let's reconfigure the DM with the right credentials. // DM reconfiguration should now use the common RabbitMQ configuration (which *is* correct). this.manager.reconfigure(); // Manager#reconfigure() reloads all the applications from its configuration. // Since we loaded one in-memory, we must restore it ourselves. managerWrapper.addManagedApplication( ma ); // Force the agent to send a heart beat message. this.agentItf.forceHeartbeatSending(); Thread.sleep( 400 ); // Travis containers are sometimes very slow if( this.agentItf.getScopedInstance() == null ) Thread.sleep( 400 ); // The agent should now be configured. Assert.assertEquals( app.getName(), this.agentItf.getApplicationName()); Assert.assertNotNull( this.agentItf.getMessagingClient()); Assert.assertTrue( this.agentItf.getMessagingClient().isConnected()); Assert.assertNotNull( this.agentItf.getScopedInstance()); Assert.assertEquals( app.getMySqlVm(), this.agentItf.getScopedInstance()); // And the DM should have considered the root instance as started. Assert.assertEquals( InstanceStatus.DEPLOYED_STARTED, app.getMySqlVm().getStatus()); } }