Skip to content

Commit

Permalink
ARTEMIS-3808 support start/stop embedded web server via mngmnt
Browse files Browse the repository at this point in the history
It would be useful to be able to cycle the embedded web server if, for
example, one needed to renew the SSL certificates. To support
functionality I made a handful of changes, e.g.:

 - Refactoring WebServerComponent so that all the necessary
   configuration would happen in the start() method.
 - Refactoring WebServerComponentTest to re-use code.
  • Loading branch information
jbertram committed May 23, 2022
1 parent de15423 commit 974bd5f
Show file tree
Hide file tree
Showing 15 changed files with 595 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ public ActiveMQException createException(String msg) {
public ActiveMQException createException(String msg) {
return new ActiveMQRoutingException(msg);
}
},
TIMEOUT_EXCEPTION(223) {
@Override
public ActiveMQException createException(String msg) {
return new ActiveMQTimeoutException(msg);
}
};
private static final Map<Integer, ActiveMQExceptionType> TYPE_MAP;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.api.core;

/**
* An operation timed out.
*/
public final class ActiveMQTimeoutException extends ActiveMQException {

private static final long serialVersionUID = 0;

public ActiveMQTimeoutException(String message) {
super(ActiveMQExceptionType.TIMEOUT_EXCEPTION, message);
}

public ActiveMQTimeoutException() {
super(ActiveMQExceptionType.TIMEOUT_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.marker;

/*
* This is just a "marker interface" so that the broker can find the org.apache.activemq.artemis.component.WebServerComponent
* for management operations (e.g. start & stop).
*/
public interface WebServerComponentMarker {
}
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,8 @@ public static String getDefaultHapolicyBackupStrategy() {
// If SESSION-notifications should be suppressed or not
public static boolean DEFAULT_SUPPRESS_SESSION_NOTIFICATIONS = false;

public static final long DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT = 5000;

/**
* If true then the ActiveMQ Artemis Server will make use of any Protocol Managers that are in available on the classpath. If false then only the core protocol will be available, unless in Embedded mode where users can inject their own Protocol Managers.
*/
Expand Down Expand Up @@ -1786,4 +1788,8 @@ public static boolean getDefaultSuppressSessionNotifications() {
return DEFAULT_SUPPRESS_SESSION_NOTIFICATIONS;
}

public static long getDefaultEmbeddedWebServerRestartTimeout() {
return DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javax.management.MBeanOperationInfo;
import java.util.Map;

import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQAddressDoesNotExistException;

/**
Expand Down Expand Up @@ -1971,5 +1972,20 @@ void replay(@Parameter(name = "startScanDate", desc = "Start date where we will
@Parameter(name = "address", desc = "Name of the address to replay") String address,
@Parameter(name = "target", desc = "Where the replay data should be sent") String target,
@Parameter(name = "filter", desc = "Filter to apply on message selection. Null means everything matching the address") String filter) throws Exception;

@Operation(desc = "stop the embedded web server", impact = MBeanOperationInfo.ACTION)
void stopEmbeddedWebServer() throws Exception;

@Operation(desc = "start the embedded web server", impact = MBeanOperationInfo.ACTION)
void startEmbeddedWebServer() throws Exception;

@Operation(desc = "restart the embedded web server; wait the default " + ActiveMQDefaultConfiguration.DEFAULT_EMBEDDED_WEB_SERVER_RESTART_TIMEOUT + " milliseconds to ensure restart completes successfully", impact = MBeanOperationInfo.ACTION)
void restartEmbeddedWebServer() throws Exception;

@Operation(desc = "restart the embedded web server; wait specified time (in milliseconds) to ensure restart completes successfully", impact = MBeanOperationInfo.ACTION)
void restartEmbeddedWebServer(@Parameter(name = "timeout", desc = "how long to wait (in milliseconds) to ensure restart completes successfully") long timeout) throws Exception;

@Attribute(desc = "Whether the embedded web server is started")
boolean isEmbeddedWebServerStarted();
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
*/
package org.apache.activemq.artemis.core.management.impl;

import org.apache.activemq.artemis.json.JsonArray;
import org.apache.activemq.artemis.json.JsonArrayBuilder;
import org.apache.activemq.artemis.json.JsonObject;
import org.apache.activemq.artemis.json.JsonObjectBuilder;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanNotificationInfo;
Expand All @@ -45,12 +41,16 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQAddressDoesNotExistException;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
import org.apache.activemq.artemis.api.core.JsonUtil;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
Expand Down Expand Up @@ -93,6 +93,7 @@
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
Expand All @@ -106,6 +107,7 @@
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.ServerProducer;
import org.apache.activemq.artemis.core.server.ServerSession;
import org.apache.activemq.artemis.core.server.ServiceComponent;
import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
import org.apache.activemq.artemis.core.server.cluster.ClusterManager;
import org.apache.activemq.artemis.core.server.cluster.ha.HAPolicy;
Expand All @@ -127,7 +129,12 @@
import org.apache.activemq.artemis.core.transaction.TransactionDetailFactory;
import org.apache.activemq.artemis.core.transaction.impl.CoreTransactionDetail;
import org.apache.activemq.artemis.core.transaction.impl.XidImpl;
import org.apache.activemq.artemis.json.JsonArray;
import org.apache.activemq.artemis.json.JsonArrayBuilder;
import org.apache.activemq.artemis.json.JsonObject;
import org.apache.activemq.artemis.json.JsonObjectBuilder;
import org.apache.activemq.artemis.logs.AuditLogger;
import org.apache.activemq.artemis.marker.WebServerComponentMarker;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.ActiveMQBasicSecurityManager;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModuleConfigurator;
Expand Down Expand Up @@ -161,6 +168,8 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active

private final Object userLock = new Object();

private final Object embeddedWebServerLock = new Object();

public ActiveMQServerControlImpl(final PostOffice postOffice,
final Configuration configuration,
final ResourceManager resourceManager,
Expand Down Expand Up @@ -4479,5 +4488,73 @@ public void replay(String startScan, String endScan, String address, String targ

server.replay(startScanDate, endScanDate, address, target, filter);
}

@Override
public void stopEmbeddedWebServer() throws Exception {
synchronized (embeddedWebServerLock) {
getEmbeddedWebServerComponent().stop(true);
}
}

@Override
public void startEmbeddedWebServer() throws Exception {
synchronized (embeddedWebServerLock) {
getEmbeddedWebServerComponent().start();
}
}

@Override
public void restartEmbeddedWebServer() throws Exception {
restartEmbeddedWebServer(ActiveMQDefaultConfiguration.getDefaultEmbeddedWebServerRestartTimeout());
}

@Override
public void restartEmbeddedWebServer(long timeout) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Exception> exception = new AtomicReference<>();
/*
* This needs to be run in its own thread managed by the broker because if it is run on a thread managed by Jetty
* (e.g. if it is invoked from the web console) then the thread will die before Jetty can be restarted.
*/
server.getThreadPool().execute(() -> {
try {
synchronized (embeddedWebServerLock) {
stopEmbeddedWebServer();
startEmbeddedWebServer();
}
} catch (Exception e) {
exception.set(e);
} finally {
latch.countDown();
}
});
if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
throw ActiveMQMessageBundle.BUNDLE.embeddedWebServerRestartTimeout(timeout);
}
if (exception.get() != null) {
throw ActiveMQMessageBundle.BUNDLE.embeddedWebServerRestartFailed(exception.get());
}
}

@Override
public boolean isEmbeddedWebServerStarted() {
try {
return getEmbeddedWebServerComponent().isStarted();
} catch (Exception e) {
if (logger.isTraceEnabled()) {
logger.trace(e.getMessage());
}
return false;
}
}

private ServiceComponent getEmbeddedWebServerComponent() throws ActiveMQIllegalStateException {
for (ActiveMQComponent component : server.getExternalComponents()) {
if (component instanceof WebServerComponentMarker) {
return (ServiceComponent) component;
}
}
throw ActiveMQMessageBundle.BUNDLE.embeddedWebServerNotFound();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.apache.activemq.artemis.api.core.ActiveMQReplicationTimeooutException;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.ActiveMQSessionCreationException;
import org.apache.activemq.artemis.api.core.ActiveMQTimeoutException;
import org.apache.activemq.artemis.api.core.ActiveMQUnexpectedRoutingTypeForAddress;
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
Expand Down Expand Up @@ -522,4 +523,13 @@ IllegalStateException invalidRoutingTypeUpdate(String queueName,

@Message(id = 229240, value = "Connection router {0} rejected the connection", format = Message.Format.MESSAGE_FORMAT)
ActiveMQRemoteDisconnectException connectionRejected(String connectionRouter);

@Message(id = 229241, value = "Embedded web server not found")
ActiveMQIllegalStateException embeddedWebServerNotFound();

@Message(id = 229242, value = "Embedded web server not restarted in {0} milliseconds", format = Message.Format.MESSAGE_FORMAT)
ActiveMQTimeoutException embeddedWebServerRestartTimeout(long timeout);

@Message(id = 229243, value = "Embedded web server restart failed", format = Message.Format.MESSAGE_FORMAT)
ActiveMQException embeddedWebServerRestartFailed(@Cause Exception e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,33 @@ public interface ActiveMQWebLogger extends BasicLogger {
ActiveMQWebLogger LOGGER = Logger.getMessageLogger(ActiveMQWebLogger.class, ActiveMQWebLogger.class.getPackage().getName());

@LogMessage(level = Logger.Level.INFO)
@Message(id = 241001, value = "HTTP Server started at {0}", format = Message.Format.MESSAGE_FORMAT)
@Message(id = 241001, value = "Embedded web server started at {0}", format = Message.Format.MESSAGE_FORMAT)
void webserverStarted(String bind);

@LogMessage(level = Logger.Level.INFO)
@Message(id = 241002, value = "Artemis Jolokia REST API available at {0}", format = Message.Format.MESSAGE_FORMAT)
void jolokiaAvailable(String bind);

@LogMessage(level = Logger.Level.WARN)
@Message(id = 244003, value = "Temporary file not deleted on shutdown: {0}", format = Message.Format.MESSAGE_FORMAT)
void tmpFileNotDeleted(File tmpdir);
@LogMessage(level = Logger.Level.INFO)
@Message(id = 241003, value = "Starting embedded web server", format = Message.Format.MESSAGE_FORMAT)
void startingEmbeddedWebServer();

@LogMessage(level = Logger.Level.INFO)
@Message(id = 241004, value = "Artemis Console available at {0}", format = Message.Format.MESSAGE_FORMAT)
void consoleAvailable(String bind);

@LogMessage(level = Logger.Level.INFO)
@Message(id = 241005, value = "Stopping embedded web server", format = Message.Format.MESSAGE_FORMAT)
void stoppingEmbeddedWebServer();

@LogMessage(level = Logger.Level.INFO)
@Message(id = 241006, value = "Stopped embedded web server", format = Message.Format.MESSAGE_FORMAT)
void stoppedEmbeddedWebServer();

@LogMessage(level = Logger.Level.WARN)
@Message(id = 244003, value = "Temporary file not deleted on shutdown: {0}", format = Message.Format.MESSAGE_FORMAT)
void tmpFileNotDeleted(File tmpdir);

@LogMessage(level = Logger.Level.WARN)
@Message(id = 244005, value = "Web customizer {0} not loaded: {1}", format = Message.Format.MESSAGE_FORMAT)
void customizerNotLoaded(String customizer, Throwable t);
Expand Down

0 comments on commit 974bd5f

Please sign in to comment.