/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.wildfly10.itest.nonpc;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.mockito.Mockito.when;
import static org.rhq.core.pluginapi.event.log.LogFileEventResourceComponentHelper.LOG_EVENT_SOURCES_CONFIG_PROP;
import static org.rhq.modules.plugins.wildfly10.JBossProductType.WILDFLY;
import static org.rhq.modules.plugins.wildfly10.helper.HostnameVerification.SKIP;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.HOSTNAME;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.HOSTNAME_VERIFICATION;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.MANAGEMENT_CONNECTION_TIMEOUT;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.PASSWORD;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.PORT;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.SECURE;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.TRUST_STRATEGY;
import static org.rhq.modules.plugins.wildfly10.helper.ServerPluginConfiguration.Property.USER;
import static org.rhq.modules.plugins.wildfly10.helper.TrustStrategy.TRUST_ANY;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.jackson.map.ObjectMapper;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.pluginapi.availability.AvailabilityContext;
import org.rhq.core.pluginapi.component.ComponentInvocationContext;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.system.SystemInfoFactory;
import org.rhq.modules.plugins.wildfly10.StandaloneASComponent;
import org.rhq.modules.plugins.wildfly10.json.Operation;
import org.rhq.modules.plugins.wildfly10.json.Result;
/**
* Check that management operations that might be interrupted by AS server (connection closed before the http response
* is sent) don't make RHQ operations fail.
*
* See https://bugzilla.redhat.com/show_bug.cgi?id=911321
*
* @author Thomas Segismont
*/
@Test(singleThreaded = true)
public class InterruptibleOperationsTest {
private static final String HTTP_HOST = "localhost";
private static final int MIN_DYNAMIC_PORT = 49152;
private static final int MAX_PORT_NUMBER = 65535;
@Mock
private ResourceContext resourceContext;
private StandaloneASComponent serverComponent;
private Server jettyServer;
private ExecutorService executorService;
@BeforeMethod
private void setup() throws Exception {
MockitoAnnotations.initMocks(this);
serverComponent = new TestStandaloneASComponent();
int httpPort = setupJettyServer();
setupResourceContext(httpPort);
executorService = Executors.newSingleThreadExecutor();
when(resourceContext.getComponentInvocationContext()).thenReturn(new MockComponentInvocationContext());
serverComponent.start(resourceContext);
}
private int setupJettyServer() throws Exception {
// Loop until Jetty binds to an available port
for (int httpPort = MIN_DYNAMIC_PORT; httpPort <= MAX_PORT_NUMBER; httpPort++) {
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
HttpServlet testServlet = new TestServlet();
context.addServlet(new ServletHolder(testServlet), "/*");
jettyServer = new Server(new InetSocketAddress(HTTP_HOST, httpPort));
jettyServer.setHandler(context);
try {
jettyServer.start();
return httpPort;
} catch (BindException e) {
// Port already in use
}
}
throw new RuntimeException("Could not find an available port");
}
private void setupResourceContext(int httpPort) {
when(resourceContext.getPluginConfiguration()).thenReturn(pluginConfig(httpPort));
when(resourceContext.getResourceKey()).thenReturn("/TestServer");
when(resourceContext.getSystemInformation()).thenReturn(SystemInfoFactory.createJavaSystemInfo());
when(resourceContext.getAvailabilityContext()).thenReturn(Mockito.mock(AvailabilityContext.class));
}
private Configuration pluginConfig(int httpPort) {
Configuration pluginConfig = new Configuration();
pluginConfig.setSimpleValue(HOSTNAME, "localhost");
pluginConfig.setSimpleValue(PORT, String.valueOf(httpPort));
pluginConfig.setSimpleValue(SECURE, String.valueOf(false));
pluginConfig.setSimpleValue(USER, "pipo");
pluginConfig.setSimpleValue(PASSWORD, "molo");
pluginConfig.setSimpleValue(MANAGEMENT_CONNECTION_TIMEOUT, "-1");
pluginConfig.getMap().put(LOG_EVENT_SOURCES_CONFIG_PROP, new PropertyList());
pluginConfig.setSimpleValue(TRUST_STRATEGY, TRUST_ANY.name);
pluginConfig.setSimpleValue(HOSTNAME_VERIFICATION, SKIP.name);
pluginConfig.setSimpleValue("expectedRuntimeProductName", WILDFLY.PRODUCT_NAME);
return pluginConfig;
}
@AfterMethod
private void tearDown() throws Exception {
try {
if (jettyServer != null) {
jettyServer.stop();
}
} catch (Exception ignore) {
}
if (executorService != null) {
executorService.shutdownNow();
}
}
@Test(timeOut = 60 * 1000)
public void testReloadOperation() throws Exception {
OperationResult operationResult = serverComponent.invokeOperation("reload", new Configuration());
assertEquals(operationResult.getSimpleResult(), "Success");
}
@Test(timeOut = 60 * 1000)
public void testShutdown() throws Exception {
OperationResult operationResult = serverComponent.invokeOperation("shutdown", new Configuration());
assertEquals(operationResult.getSimpleResult(), "Success");
}
private static class MockComponentInvocationContext implements ComponentInvocationContext {
@Override
public boolean isInterrupted() {
return false;
}
@Override
public void markInterrupted() {
}
}
private class RestartJetty implements Runnable {
@Override
public void run() {
try {
jettyServer.stop();
} catch (Exception ignore) {
}
try {
Thread.sleep(SECONDS.toMillis(2));
} catch (Exception ignore) {
}
try {
jettyServer.start();
} catch (Exception ignore) {
}
}
}
private class TestServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Operation operation = objectMapper.readValue(req.getInputStream(), Operation.class);
// Check if we recevied an operation which a real http management interface might interrupt
if (operation.getOperation().equals("reload") || operation.getOperation().equals("shutdown")) {
// Schedule a Jetty restart
executorService.submit(new RestartJetty());
// Then wait until Jetty is shutdown
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ignore) {
}
return;
}
// Standard operation. Return simple success
Result result = new Result();
result.setOutcome("success");
objectMapper.writeValue(resp.getOutputStream(), result);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
// Tweak StandaloneASComponent implementation for this test case
private class TestStandaloneASComponent extends StandaloneASComponent {
@Override
public AvailabilityType getAvailability() {
// Avoid various management requests when component is started
return AvailabilityType.DOWN;
}
@Override
protected boolean waitUntilDown() throws InterruptedException {
// Standard implementation relies on discovery of some resource properties
return true;
}
}
}